diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/2d23d.js | 83 | ||||
-rw-r--r-- | js/2d23dcustom.js | 90 | ||||
-rw-r--r-- | js/2pi.js | 100 | ||||
-rw-r--r-- | js/AutoAudio.js | 213 | ||||
-rw-r--r-- | js/AutoHarmonograph.js | 107 | ||||
-rw-r--r-- | js/AutoImages.js | 240 | ||||
-rw-r--r-- | js/AutoVideos.js | 265 | ||||
-rw-r--r-- | js/Harmonograph.js | 206 | ||||
-rw-r--r-- | js/NameGenerator.js | 149 | ||||
-rw-r--r-- | js/ant.js | 135 | ||||
-rw-r--r-- | js/ballbounce.js | 54 | ||||
-rw-r--r-- | js/clock.js | 116 | ||||
-rw-r--r-- | js/h.js | 203 | ||||
-rw-r--r-- | js/header_links.js | 5 | ||||
-rw-r--r-- | js/latexit.js | 110 | ||||
-rw-r--r-- | js/magnets.js | 83 | ||||
-rw-r--r-- | js/magnets_old_version.js | 83 | ||||
-rw-r--r-- | js/mandelbrot.js | 179 | ||||
-rw-r--r-- | js/mazesolver.js | 457 | ||||
-rw-r--r-- | js/modularcircles.js | 54 | ||||
-rw-r--r-- | js/modularpascal.js | 55 | ||||
-rw-r--r-- | js/p5.js | 29765 | ||||
-rw-r--r-- | js/processing.js | 21748 | ||||
-rw-r--r-- | js/riffwave.js | 130 | ||||
-rw-r--r-- | js/shaperoller.js | 172 | ||||
-rw-r--r-- | js/tree.js | 24 | ||||
-rw-r--r-- | js/treegenerator.js | 37 |
27 files changed, 54863 insertions, 0 deletions
diff --git a/js/2d23d.js b/js/2d23d.js new file mode 100644 index 0000000..c3ac565 --- /dev/null +++ b/js/2d23d.js @@ -0,0 +1,83 @@ +var angle = 0; +var state = 0; +var rotations = 0; +var waiting = false; +var iterations = 0; +var radius = 200; + +function checkRotations() +{ + if (rotations > 0) + { + rotations = 0; + angle = 0; + waiting = true; + } +} + +function setup() +{ + createCanvas(2 * radius + 100, 2 * radius + 100); + ellipseMode(CENTER); + rectMode(CENTER); + noFill(); + frameRate(100); +} + +function draw() +{ + if (waiting) + { + iterations++; + + if (iterations > 150) + { + iterations = 0; + waiting = false; + background(255, 255, 255); + state++; + } + return; + } + var ca = cos(angle) * radius + radius + 50; + var sa = sin(angle) * radius + radius + 50; + + angle += 0.1/6; + + if (angle > TWO_PI) + rotations++; + + checkRotations(); + + angle %= TWO_PI; + + if (state == 0) + ellipse(ca, sa, 50, 50); + + else if (state == 1) + rect(ca, sa, 50, 50); + + else if (state == 2) + triangle(ca, sa, ca+50, sa, ca, sa+50); + + + else if (state == 3) + triangle(ca, sa, ca+25, sa+25, ca, sa+50); + + + else if (state == 4) + ellipse(ca, sa, 100, 50); + + + else if (state == 5) + line(ca, sa, ca+50, sa); + + + else if (state == 6) + line(ca, sa, ca, sa+50); + + else if (state >= 7) + arc(ca, sa, 50, 50, 0, (HALF_PI + state) % TWO_PI); + + +}
\ No newline at end of file diff --git a/js/2d23dcustom.js b/js/2d23dcustom.js new file mode 100644 index 0000000..3b55fd9 --- /dev/null +++ b/js/2d23dcustom.js @@ -0,0 +1,90 @@ +var RETURN = 13; +var SHIFT = 16; +var ret_pressed = false; +var drawing; +var radius = 200; +var angle = 0; +var done = false; +var straightLine = false; +var slFirstPos = []; + +function setup() +{ + createCanvas(100, 100); + drawing = createGraphics(100, 100); + stroke(255, 0, 0); + line(0, 0, 99, 0); + line(0, 0, 0, 99); + line(0, 99, 99, 99); + line(99, 0, 99, 99); + stroke(0); +} + +function outOfBounds(x) +{ + return constrain(x, 0, 100) != x; +} + +function mouseDragged() +{ + if (outOfBounds(pmouseX) || outOfBounds(mouseX) || + outOfBounds(pmouseY) || outOfBounds(mouseY)) + return; + stroke(0, 0, 0); + drawing.line(pmouseX, pmouseY, mouseX, mouseY); + line(pmouseX, pmouseY, mouseX, mouseY); +} + +function mouseClicked() +{ + + if (straightLine) + { + line(slFirstPos[0], slFirstPos[1], mouseX, mouseY); + drawing.line(slFirstPos[0], slFirstPos[1], mouseX, mouseY); + straightLine = false; + return; + } + slFirstPos = [mouseX, mouseY]; +} + +function keyPressed() +{ + if (keyCode == RETURN) + { + if (straightLine) + return; + if (ret_pressed) + return; + ret_pressed = true; + createCanvas(600, 600); + } + else if (keyCode == SHIFT) + { + straightLine = true; + } +} + +function draw() +{ + cursor(ARROW); + if (!ret_pressed) + return; + + if (done) + return; + + if (angle > TWO_PI) + return; + + stroke(255, 0, 0); + tint(255, 0, 0); + var ca = cos(angle) * radius + radius + 50; + var sa = sin(angle) * radius + radius + 50; + + + image(drawing, ca, sa); + + angle += 0.1/6; + +}
\ No newline at end of file diff --git a/js/2pi.js b/js/2pi.js new file mode 100644 index 0000000..54d59d7 --- /dev/null +++ b/js/2pi.js @@ -0,0 +1,100 @@ +var vertices = []; + +function mouseDragged() +{ + if (document.getElementById("info").innerHTML != "") + return; + if (mouseX < 0 || mouseX > width || mouseY < 0 || mouseY > width) + return; + stroke(0); + vertices.push([mouseX, mouseY]); + if (vertices.length > 1) + line(mouseX, mouseY, vertices[vertices.length-2][0], vertices[vertices.length-2][1]); +} + +function circumference() +{ + var total = 0; + for (var i = 1; i < vertices.length; i++) + { + total += dist(vertices[i-1][0], vertices[i-1][1], vertices[i][0], vertices[i][1]); + } + return total; +} + +function center() +{ + var centerX = 0; + var centerY = 0; + for (var i = 0; i < vertices.length; i++) + { + centerX += vertices[i][0] / vertices.length; + centerY += vertices[i][1] / vertices.length; + } + return [centerX, centerY]; +} + +function twoPiApprox() +{ + var o = center(); + var c = circumference(); + + ellipseMode(CENTER); + fill(0, 0, 200); + noStroke(); + ellipse(o[0], o[1], 5, 5); + + var avgR = 0; + var minLocation; + var maxLocation; + for (var i = 0; i < vertices.length; i++) + { + var d = dist(vertices[i][0], vertices[i][1], o[0], o[1]); + avgR += d / vertices.length; + + } + + stroke(0, 255, 0); + noFill(); + line(o[0], o[1], o[0]+avgR, o[1]); + stroke(255, 0, 0); + ellipse(o[0], o[1], 2*avgR, 2*avgR); + stroke(255, 200, 0); + ellipse(o[0], o[1], c/PI, c/PI); + + + return c/(2*avgR); + +} + +function setup() +{ + createCanvas(500, 500); + stroke(255, 0, 0); + line(0, 0, width-1, 0); + line(width-1, 0, width-1, height-1); + line(0, height-1, width-1, height-1); + line(0, 0, 0, height-1); +} + +function calculate() +{ + var approx = twoPiApprox(); + var avgPi = approx; + var percentError; + if (avgPi > PI) + { + percentError = 100*(avgPi/PI - 1); + } + else + { + percentError = 100*(PI/avgPi - 1); + } + var div = document.getElementById("info"); + div.innerHTML = "Your approximation | True value<br> 2π = " + 2 * avgPi + " | " + TWO_PI + "<br> π = " + + + avgPi + " | " + PI + ".<br>" + percentError + "% error.<br>" + + "The blue dot is the center of your shape. The green line is the average radius of the shape.<br>" + + "The red circle is a circle with the same radius as your shape, and the orange circle is a<br>" + + "circle with the same circumference as your shape. The closer they are, the better your<br>" + + "approximation of π"; +}
\ No newline at end of file diff --git a/js/AutoAudio.js b/js/AutoAudio.js new file mode 100644 index 0000000..cc6b554 --- /dev/null +++ b/js/AutoAudio.js @@ -0,0 +1,213 @@ +var length; +var single = ["Math.sqrt", "Math.cos", "Math.sin"]; //Operations on a single number +var singleweights = {}; +var binary = ["*", "+", "-", "/"]; //Operations for 2 numbers +var binaryweights = {}; +var varlist = ["x"]; +var numlist = ["Constant"]; +numlist = numlist.concat(varlist); +var numberweights = {"Constant":1}; +var numberweight = 1; +var singleweight = 1; +var eqlength; +var notify; +var functionp = document.getElementById('Function'); + +for(var i = 0; i < single.length; i++) +{ + singleweights[single[i]] = 1; +} + +for(var i = 0; i < binary.length; i++) +{ + binaryweights[binary[i]] = 1; +} + +for(var i = 0; i < varlist.length; i++) +{ + numberweights[varlist[i]] = 1; +} + +function randItem(l) +{ + return l[Math.floor(Math.random() * l.length)]; +} + +function countChar(string, letter) +{ + var amount = 0; + for (var i = 0; i < string.length; i++) + { + if (string[i] == letter) + { + amount++; + } + } + return amount; +} + +function rmvmath(str) +{ + //A function that removes all the Math.'s in a string + var newstr = ''; + for(var i = 0; i < str.length - 5; i++) + { + if(str[i] + str[i+1] + str[i+2] + str[i+3] + str[i+4] !== 'Math.') + { + newstr += str[i] + } + else + { + i += 4; + } + } + return newstr; +}; + +function randEquation() +{ + var hasx = false; + var equation; + var lasttype; + var thistype; + var chanceend; + var length; + var what; + var number; + + + while (!hasx) + { + //Types: b for binary, s for single, f for first, n for number + equation = ''; + lasttype = 'f'; + thistype = 0; + hasx = false; + chanceend = 0; + length = 1; //Number of operations done so far + + while (true) + { + chanceend = Math.pow((1.0 - (1.0 / length)), eqlength); + if (lasttype == 'n') + { + number = Math.random(); + if (number < chanceend) + { + break; + } + equation = '(' + equation + ')' + randItem(binary); + lasttype = 'b'; + } + else if (lasttype == 's' || lasttype == 'b' || lasttype == 'f') + { + equation += '('; + thistype = Math.random(); + if (thistype < singleweight / (singleweight + numberweight)) + { + equation += randItem(single); + lasttype = 's'; + } + else + { + what = randItem(numlist); + if (what == 'Constant') + { + equation += (Math.random()*100+100).toString(); + } + else + { + equation += what; + if (what == 'x') + { + hasx = true; + } + } + lasttype = 'n'; + equation += ')'; + } + } + length++; + } + } + while (countChar(equation, '(') > countChar(equation, ')')) + { + equation += ')'; + } + return equation; +} + +function evalEquation(eq, x) +{ + try + { + eval('var result = ' + eq); + return result; + } + catch(err) + { + return 0; + } +} + +function create() +{ + var date = new Date(); + var start = date.getTime(); + var form = document.getElementById('Options'); + var errtag = document.getElementById('Error'); + errtag.innerHTML = ''; + for (var i = 0; i < form.elements.length; i++) + { + if (form.elements[i].value == '') + { + errtag.innerHTML = 'Please enter a valid number.'; + return; + } + } + + length = parseInt(form.elements[1].value); + notify = form.elements[2].checked; + + singleweight = parseFloat(form.elements[4].value); + numberweight = parseFloat(form.elements[5].value); + eqlength = parseFloat(form.elements[6].value); + + var data = []; + var equation = randEquation(); + + for (var i=0; i<length * 10000; i++) + { + var value = evalEquation(equation, i); + value %= 1; + data[i] = Math.abs(255 * value); + } + var wave = new RIFFWAVE(data); + audio = document.getElementById('Audio'); + audio.src = wave.dataURI; + date = new Date(); + end = date.getTime(); + var timeparagraph = document.getElementById('Time'); + var timetaken = Math.round((end-start)/1000); + if (timetaken == 1) + { + timeparagraph.innerHTML = 'The time it took was 1 second.'; + } + else + { + timeparagraph.innerHTML = 'The time it took was ' + timetaken + ' seconds.'; + } + + functionp.innerHTML = '$Function: $' + rmvmath(equation); + LatexIT.render('*',false); + + if(notify) + { + alert('Your audio has finished.'); + } + + audio.play(); + + + +} diff --git a/js/AutoHarmonograph.js b/js/AutoHarmonograph.js new file mode 100644 index 0000000..b4158db --- /dev/null +++ b/js/AutoHarmonograph.js @@ -0,0 +1,107 @@ +var t = 0; +var pp; +var Ax, Ay, fx, fy, dx, dy, basepx, basepy; +var AMAX = 1000; +var DMIN = 0.0001; +var DMAX = 0.0003; +var FMAX = 0.1; +var PMAX = 6.28; +var ADIF = AMAX / 10; +var DDIF = DMAX / 10; +var FDIF = FMAX / 10; +var PDIF = PMAX / 10; + + +function sinExp(A, t, f, p, d) +{ + return A*sin(t*f + p) * exp(-d*t); +} + +var Pendulum = function() +{ + this.xy = floor(random(2)); + if (this.xy === 0) + this.p = random(-PDIF, PDIF)+basepx; + else + this.p = random(-PDIF, PDIF)+basepy; + + pendulums.push(this); +} + +Pendulum.prototype.swing = function() +{ + if (this.xy === 0) + { + var x = sinExp(Ay, t, fx, this.p, dx); + var y = 0; + } + else + { + var x = 0; + var y = sinExp(Ax, t, fy, this.p, dy); + } + return [x, y]; +} + +var pendulums = []; + + +function calculate() +{ + var xsum = 0; + var ysum = 0; + var sw; + for (var i = 0; i < pendulums.length; i++) + { + sw = pendulums[i].swing(); + xsum += sw[0]; + ysum += sw[1]; + } + return [xsum/pendulums.length, ysum/pendulums.length]; +} + + +function setup() +{ + document.getElementById("npendulums").value = 10; + document.getElementById("DMAX").value = DMAX; +} + +function start() +{ + + createCanvas(800, 800); + frameRate(1000); + DMAX = document.getElementById("DMAX").value; + basepx = random(PMAX); + basepy = random(PMAX); + Ax = random(AMAX); + Ay = random(AMAX); + fx = random(FMAX); + fy = random(FMAX); + dx = random(DMIN, DMAX); + dy = random(DMIN, DMAX); + + pendulums = []; + + for (var i = 0; i < document.getElementById("npendulums").value; i++) + new Pendulum(); + +} + +function saveCanvas() +{ + save("AutoHarmonograph.png"); +} + +function draw() +{ + translate(width/2, height/2); + curr = calculate(); + if (t !== 0) + line(pp[0], pp[1], curr[0], curr[1]); + + + t++; + pp = curr; +} diff --git a/js/AutoImages.js b/js/AutoImages.js new file mode 100644 index 0000000..cd3ff66 --- /dev/null +++ b/js/AutoImages.js @@ -0,0 +1,240 @@ + +var canvas = document.getElementById('Canvas'); +var ctx = canvas.getContext('2d'); + +var single = ["Math.sqrt", "Math.cos", "Math.sin"]; //Operations on a single number +var singleweights = {}; +var binary = ["*", "+", "-", "/"]; //Operations for 2 numbers +var binaryweights = {}; +var varlist = ["x", "y"]; +var numlist = ["Constant"]; +numlist = numlist.concat(varlist); +var numberweights = {"Constant":1} +var numberweight; +var singleweight; +var eqlength; +var xsize; +var ysize; +var functionp = document.getElementById('Function'); +var notify; +var latex; +var mathjax; + +for(var i = 0; i < single.length; i++) +{ + singleweights[single[i]] = 1; +} + +for(var i = 0; i < binary.length; i++) +{ + binaryweights[binary[i]] = 1; +} + +for(var i = 0; i < varlist.length; i++) +{ + numberweights[varlist[i]] = 1; +} + +function rmvmath(str) +{ + //A function that removes all the Math.'s in a string + var newstr = ''; + for(var i = 0; i < str.length - 5; i++) + { + if(str[i] + str[i+1] + str[i+2] + str[i+3] + str[i+4] !== 'Math.') + { + newstr += str[i] + } + else + { + i += 4; + } + } + return newstr; +}; + +function randItem(l) +{ + return l[Math.floor(Math.random() * l.length)]; +}; + +function countChar(string, letter) +{ + var amount = 0; + for (var i = 0; i < string.length; i++) + { + if (string[i] === letter) + { + amount++; + } + } + return amount; +}; + + + +function randEquation() +{ + var hasx = false; + var hasy = false; + var equation; + var lasttype; + var thistype; + var chanceend; + var length; + var what; + var number; + + while (!(hasx && hasy)) + { + //Types: b for binary, s for single, f for first, n for number + equation = ''; + lasttype = 'f'; + thistype = 0; + hasx = false; + hasy = false; + chanceend = 0; + length = 1; //Number of operations done so far + + while (true) + { + chanceend = Math.pow((1.0 - (1.0 / length)), eqlength); + if (lasttype === 'n') + { + number = Math.random(); + if (number < chanceend) + { + break; + } + equation = '(' + equation + ')' + randItem(binary); + lasttype = 'b'; + } + else if (lasttype === 's' || lasttype === 'b' || lasttype === 'f') + { + equation += '('; + thistype = Math.random(); + if (thistype < singleweight / (singleweight + numberweight)) + { + equation += randItem(single); + lasttype = 's'; + } + else + { + what = randItem(numlist); + if (what === 'Constant') + { + equation += (Math.random(100, 200)).toString(); + } + else + { + equation += what; + if (what === 'x') + { + hasx = true; + } + else if (what === 'y') + { + hasy = true; + } + } + lasttype = 'n'; + equation += ')'; + } + } + length++; + } + } + while (countChar(equation, '(') > countChar(equation, ')')) + { + equation += ')'; + } + return equation; +}; + + +function create() +{ + var date = new Date(); + var start = date.getTime(); + + form = document.getElementById('Options'); + + var errtag = document.getElementById('Error'); + errtag.innerHTML = ''; + + for (var i = 0; i < form.elements.length; i++) + { + if (form.elements[i].value === '') + { + errtag.innerHTML = 'Please enter a valid number.'; + return; + } + } + + xsize = parseInt(form.elements[1].value); + ysize = parseInt(form.elements[2].value); + notify = form.elements[3].checked; + + singleweight = parseFloat(form.elements[5].value); + numberweight = parseFloat(form.elements[6].value); + eqlength = parseFloat(form.elements[7].value); + + canvas.width = xsize; + canvas.height = ysize; + var imgData = ctx.createImageData(xsize, ysize); + var requation = randEquation(); + var gequation = randEquation(); + var bequation = randEquation(); + var x; + var y; + var r; + var g; + var b; + eval( + 'for (var i=0;i<imgData.data.length;i+=4)'+ + '{'+ + ' x = (i/4) % xsize;'+ + ' y = Math.floor((i/4) / xsize);'+ + ' r = (' + requation + ') % 255;'+ + ' g = (' + gequation + ') % 255;'+ + ' b = (' + bequation + ') % 255;'+ + ' r = Math.abs(Math.round(r));' + + ' g = Math.abs(Math.round(g));' + + ' b = Math.abs(Math.round(b));' + + + ' imgData.data[i] = r;' + + ' imgData.data[i+1] = g;' + + ' imgData.data[i+2] = b;' + + ' imgData.data[i+3] = 255;' + + '}'); + ctx.putImageData(imgData,0,0); + var date = new Date(); + var end = date.getTime(); + var timeparagraph = document.getElementById('Time'); + var timetaken = Math.round((end-start)/1000); + if (timetaken === 1) + { + timeparagraph.innerHTML = 'The time it took was 1 second.'; + } + else + { + timeparagraph.innerHTML = 'The time it took was ' + timetaken + ' seconds.'; + } + + + functionp.innerHTML = '$Functions: \\newline\\newline Red: $' + rmvmath(requation) + '$\\newline\\newline Green: $' + rmvmath(gequation) + '$\\newline\\newline Blue: $' + rmvmath(bequation) + '$'; + LatexIT.render('*',false); + if(notify) + { + alert('Your image has finished.'); + } + document.getElementById('Download').innerHTML = "Download"; +}; + + +function download() { + var dt = canvas.toDataURL('image/png'); + this.href = dt; +}; +var downloadLink = document.getElementById('Download'); +downloadLink.addEventListener('click', download, false); diff --git a/js/AutoVideos.js b/js/AutoVideos.js new file mode 100644 index 0000000..bd3a744 --- /dev/null +++ b/js/AutoVideos.js @@ -0,0 +1,265 @@ + +var single = ["Math.sqrt", "Math.cos", "Math.sin"]; //Operations on a single number +var singleweights = {}; +var binary = ["*", "+", "-", "/"]; //Operations for 2 numbers +var binaryweights = {}; +var varlist = ["x", "y", "t"]; +var numlist = ["Constant"]; +numlist = numlist.concat(varlist); +var numberweights = {"Constant":1}; +var numberweight; +var singleweight; +var eqlength; +var functionp = document.getElementById('Function'); +var notify; + +for(var i = 0; i < single.length; i++) +{ + singleweights[single[i]] = 1; +} + +for(var i = 0; i < binary.length; i++) +{ + binaryweights[binary[i]] = 1; +} + +for(var i = 0; i < varlist.length; i++) +{ + numberweights[varlist[i]] = 1; +} + +function rmvmath(str) +{ + //A function that removes all the Math.'s in a string + var newstr = ''; + for(var i = 0; i < str.length - 5; i++) + { + if(str[i] + str[i+1] + str[i+2] + str[i+3] + str[i+4] !== 'Math.') + { + newstr += str[i] + } + else + { + i += 4; + } + } + return newstr; +}; + +function randItem(l) +{ + return l[Math.floor(Math.random() * l.length)]; +}; + +function countChar(string, letter) +{ + var amount = 0; + for (var i = 0; i < string.length; i++) + { + if (string[i] == letter) + { + amount++; + } + } + return amount; +}; + + +function randEquation() +{ + var hasx = false; + var hasy = false; + var hast = false; + var equation; + var lasttype; + var thistype; + var chanceend; + var length; + var what; + var number; + + + while (!(hasx && hasy && hast)) + { + //Types: b for binary, s for single, f for first, n for number + equation = ''; + lasttype = 'f'; + thistype = 0; + hasx = false; + hasy = false; + hast = false; + chanceend = 0; + length = 1; //Number of operations done so far + + while (true) + { + chanceend = Math.pow((1.0 - (1.0 / length)), eqlength); + if (lasttype == 'n') + { + number = Math.random(); + if (number < chanceend) + { + break; + } + equation = '(' + equation + ')' + randItem(binary); + lasttype = 'b'; + } + else if (lasttype == 's' || lasttype == 'b' || lasttype == 'f') + { + equation += '('; + thistype = Math.random(); + if (thistype < singleweight / (singleweight + numberweight)) + { + equation += randItem(single); + lasttype = 's'; + } + else + { + what = randItem(numlist); + if (what == 'Constant') + { + equation += (Math.random(100, 200)).toString(); + } + else + { + equation += what; + if (what == 'x') + { + hasx = true; + } + else if (what == 'y') + { + hasy = true; + } + else if (what == 't') + { + hast = true; + } + } + lasttype = 'n'; + equation += ')'; + } + } + length++; + } + } + while (countChar(equation, '(') > countChar(equation, ')')) + { + equation += ')'; + } + return equation; +}; + +function evalEquation(eq, x, y, t) +{ + try + { + eval('var result = ' + eq); + return result; + } + catch(err) + { + return 0; + } +}; +function create() +{ + var d = new Date(); + var start = d.getTime(); + var xsize; + var ysize; + var length; + var framerate; + + var form = document.getElementById('Options'); + + var errtag = document.getElementById('Error'); + errtag.innerHTML = ''; + + for (var i = 0; i < form.elements.length; i++) + { + if (form.elements[i].value == '') + { + errtag.innerHTML = 'Please enter a valid number.'; + return; + } + } + + xsize = parseInt(form.elements[1].value); + ysize = parseInt(form.elements[2].value); + length = parseFloat(form.elements[3].value); + framerate = parseInt(form.elements[4].value); + notify = form.elements[5].checked; + + singleweight = parseFloat(form.elements[7].value); + numberweight = parseFloat(form.elements[8].value); + eqlength = parseFloat(form.elements[9].value); + + var requation = randEquation(); + var gequation = randEquation(); + var bequation = randEquation(); + var x; + var y; + var r; + var g; + var b; + var encoder = new Whammy.Video(framerate); + var video = document.getElementById('Video'); + var canvas = document.getElementById('Canvas'); + video.width = xsize; + video.height = ysize; + canvas.width = xsize; + canvas.height = ysize; + var ctx = canvas.getContext('2d'); + var apx = ctx.getImageData(0, 0, xsize, ysize); + var data = apx.data; + + + eval( + 'for(var t = 0; t < Math.round(framerate * length); t++)'+ + '{'+ + ' for(var i = 0; i < data.length; i+=4)'+ + ' {'+ + ' x = (i/4) % xsize;'+ + ' y = Math.floor((i/4) / xsize);'+ + ' r = Math.abs(Math.round((' + requation + ') % 255));'+ + ' g = Math.abs(Math.round((' + gequation + ') % 255));'+ + ' b = Math.abs(Math.round((' + bequation + ') % 255));'+ + ' data[i] = r;'+ + ' data[i+1] = g;'+ + ' data[i+2] = b;'+ + ' data[i+3] = 255;'+ + ' }'+ + ' apx.data = data;'+ + ' ctx.putImageData(apx, 0, 0);'+ + ' encoder.add(ctx);'+ + '}' + ); + var output = encoder.compile(); + var url = webkitURL.createObjectURL(output); + video.src = url; + + ctx.drawImage(video, 0, 0); + d = new Date(); + var end = d.getTime(); + var timeparagraph = document.getElementById('Time'); + var timetaken = Math.round((end-start)/1000); + if (timetaken == 1) + { + timeparagraph.innerHTML = 'The time it took was 1 second.'; + } + else + { + timeparagraph.innerHTML = 'The time it took was ' + timetaken + ' seconds.'; + } + + functionp.innerHTML = '$Functions: \\newline\\newline Red: $' + rmvmath(requation) + '$\\newline\\newline Green: $' + rmvmath(gequation) + '$\\newline\\newline Blue: $' + rmvmath(bequation) + '$'; + LatexIT.render('*',false); + + if(notify) + { + alert('Your video has finished.'); + } + + +}; diff --git a/js/Harmonograph.js b/js/Harmonograph.js new file mode 100644 index 0000000..a2e12a6 --- /dev/null +++ b/js/Harmonograph.js @@ -0,0 +1,206 @@ +var t = 0; +var pp; +var AMAX = 1000; +var DMIN = 0.0001; +var DMAX = 0.0003; +var FMAX = 0.1; +var PMAX = 6.28; +var ADIF = AMAX / 10; +var DDIF = DMAX / 10; +var FDIF = FMAX / 10; +var PDIF = PMAX / 10; +var npendulums = 0; +var started = false; + +function sinExp(A, t, f, p, d) +{ + + + return A*sin(t*f + p) * exp(-d*t); +} + +var Pendulum = function(a, d, f, p, xy) +{ + this.a = a; + this.d = d; + this.f = f; + this.p = p; + this.xy = xy; + pendulums.push(this); +} + +Pendulum.prototype.swing = function() +{ + //document.write(this.a + " " + t + " " + this.f + " " + this.p + " " + this.d + " " + this.xy + "<br>"); + if (this.xy == 0) + { + var x = sinExp(this.a, t, this.f, this.p, this.d); + var y = 0; + } + else + { + var x = 0; + var y = sinExp(this.a, t, this.f, this.p, this.d); + } + + return [x, y]; +} + +var pendulums = []; + + +function calculate() +{ + var xsum = 0; + var ysum = 0; + var sw; + for (var i = 0; i < pendulums.length; i++) + { + sw = pendulums[i].swing(); + //document.write(sw + "<br>"); + xsum += sw[0]; + ysum += sw[1]; + } + return [xsum/pendulums.length, ysum/pendulums.length]; +} + +function addPendulum() +{ + var txt = document.createElement("p"); + txt.innerHTML = "Phase:"; + var p = document.createElement("input"); + p.type = "number"; + p.value = PI; + p.id = "p" + npendulums; + txt.id = "pt" + npendulums; + document.body.appendChild(txt); + document.body.appendChild(p); + + txt = document.createElement("p"); + txt.innerHTML = "Amplitude:"; + txt.id = "at" + npendulums; + var a = document.createElement("input"); + a.type = "number"; + a.value = 500; + a.id = "A" + npendulums; + document.body.appendChild(txt); + document.body.appendChild(a); + + txt = document.createElement("p"); + txt.innerHTML = "Damping:"; + txt.id = "dt" + npendulums; + var d = document.createElement("input"); + d.type = "number"; + d.value = 0.0003; + d.id = "d" + npendulums; + document.body.appendChild(txt); + document.body.appendChild(d); + + + txt = document.createElement("p"); + txt.innerHTML = "Frequency:"; + var f = document.createElement("input"); + f.type = "number"; + f.value = 0.1; + f.id = "f" + npendulums; + txt.id = "ft" + npendulums; + document.body.appendChild(txt); + document.body.appendChild(f); + + txt = document.createElement("p"); + txt.innerHTML = "Is it a Y pendulum? (you should have at least one X and Y pendulum) "; + var xy = document.createElement("input"); + xy.type = "checkbox"; + xy.value = 1; + xy.id = "xy" + npendulums; + txt.id = "xyt" + npendulums; + document.body.appendChild(txt); + document.body.appendChild(xy); + var br = document.createElement("br"); + br.id = "br" + npendulums; + document.body.appendChild(br); + + + txt = document.createTextNode("Delete this pendulum"); + var delBtn = document.createElement("button"); + delBtn.setAttribute("onclick", "deletePendulum(" + npendulums + ");"); + delBtn.appendChild(txt); + delBtn.id = "db" + npendulums; + document.body.appendChild(delBtn); + npendulums++; +} + +function deletePendulum(x) +{ + pendulums.splice(x, 1); + document.body.removeChild(document.getElementById("p" +x)); + document.body.removeChild(document.getElementById("d" +x)); + document.body.removeChild(document.getElementById("f" +x)); + document.body.removeChild(document.getElementById("A" +x)); + document.body.removeChild(document.getElementById("xy" +x)); + + document.body.removeChild(document.getElementById("pt" +x)); + document.body.removeChild(document.getElementById("dt" +x)); + document.body.removeChild(document.getElementById("ft" +x)); + document.body.removeChild(document.getElementById("at" +x)); + document.body.removeChild(document.getElementById("xyt" +x)); + + document.body.removeChild(document.getElementById("db" +x)); + document.body.removeChild(document.getElementById("br" +x)); + + for (var i = x+1; i < npendulums; i++) + { + document.getElementById("p"+i).id = "p"+(i-1); + document.getElementById("d"+i).id = "d"+(i-1); + document.getElementById("f"+i).id = "f"+(i-1); + document.getElementById("A"+i).id = "A"+(i-1); + document.getElementById("pt"+i).id = "pt"+(i-1); + document.getElementById("dt"+i).id = "dt"+(i-1); + document.getElementById("ft"+i).id = "ft"+(i-1); + document.getElementById("at"+i).id = "at"+(i-1); + document.getElementById("xy"+i).id = "xy"+(i-1); + document.getElementById("xyt"+i).id = "xyt"+(i-1); + document.getElementById("db"+i).id = "db"+(i-1); + document.getElementById("db"+(i-1)).setAttribute("onclick", "deletePendulum(" + (i-1) + ");"); + document.getElementById("br"+i).id = "br"+(i-1); + } + npendulums--; +} + + +function start() +{ + started = true; + createCanvas(800, 800); + frameRate(1000); + for (var i = 0; i < npendulums; i++) + { + //document.write("Hello"); + var a = document.getElementById("A" + i).value; + var f = document.getElementById("f" + i).value; + var p = document.getElementById("p" + i).value; + var d = document.getElementById("d" + i).value; + var xy = document.getElementById("xy"+i).checked; + new Pendulum(parseFloat(a), parseFloat(d), parseFloat(f), parseFloat(p), xy); + } + +} + +function saveCanvas() +{ + save("AutoHarmonograph.png"); +} + +function draw() +{ + if(!started) + return; + translate(width/2, height/2); + curr = calculate(); + if (t !== 0) + line(pp[0], pp[1], curr[0], curr[1]); + + + t++; + pp = curr; +}
\ No newline at end of file diff --git a/js/NameGenerator.js b/js/NameGenerator.js new file mode 100644 index 0000000..44e3eaa --- /dev/null +++ b/js/NameGenerator.js @@ -0,0 +1,149 @@ + +var trigrams = {}; +var trigramKeyList; +var sumStartsWith = {}; + +function loadTrigrams(responseText) +{ + var lines = responseText.split('\n'); + for (var i = 0; i < lines.length; i++) + { + var trigram = lines[i].substring(0, 3); + var value = parseInt(lines[i].substring(4, lines[i].length)); + if (trigram == '' || isNaN(value)) + continue; + trigrams[trigram] = value; + } + + trigramKeyList = Object.keys(trigrams); + + document.getElementById("loading").innerHTML = ""; +} + +function start() +{ + var xhttp = new XMLHttpRequest(); + + xhttp.onreadystatechange = function() + { + if (xhttp.readyState == 4 && xhttp.status == 200) + { + loadTrigrams(xhttp.responseText); + } + }; + xhttp.open("GET", "https://raw.githubusercontent.com/pommicket/NameGenerator/master/trigrams.txt", true); + xhttp.send(); +} + +function pickFirst2() +{ + + var count = 0; + var sum = 0; + + for (var i = 0; i < trigramKeyList.length; i++) + { + if (trigramKeyList[i][0] == ' ') + { + sum += trigrams[trigramKeyList[i]]; + } + } + + var selected = Math.random() * sum; + + + for (var i = 0; i < trigramKeyList.length; i++) + { + if (trigramKeyList[i][0] == ' ') + { + count += trigrams[trigramKeyList[i]]; + if (selected < count) + return trigramKeyList[i].substring(1); + } + } + + return "ERROR"; +} + +function nextChar(name) +{ + var last2 = name.substring(name.length-2); + var total = 0; + if (sumStartsWith[last2] == NaN || sumStartsWith[last2] == undefined) + { + for (var i = 0; i < trigramKeyList.length; i++) + { + if (trigramKeyList[i].substring(0, 2) == last2) + total += trigrams[trigramKeyList[i]]; + } + sumStartsWith[last2] = total; + } + else + { + total = sumStartsWith[last2]; + } + var selected = Math.random() * total; + var count = 0; + + for (var i = 0; i < trigramKeyList.length; i++) + { + if (trigramKeyList[i].substring(0, 2) == last2) + { + count += trigrams[trigramKeyList[i]]; + if (selected < count) + return trigramKeyList[i][2]; + } + } + + return "ERROR"; +} + + +function generateName() +{ + var first = pickFirst2(); + var name = first; + var next = ''; + var length = 0; + do + { + name += next; + next = nextChar(name); + } + while (next != ' '); + + name = name[0].toUpperCase() + name.substring(1); + + + return name; +} + + +function createNames() +{ + document.getElementById("button").disabled = true; + var nameStr = ''; + var numNames = document.getElementById("numNames").value; + var nameDiv = document.getElementById("names"); + + window.setTimeout(50, function() {document.getElementById("loading").innerHTML = "Loading...";}); + + nameDiv.innerHTML = ""; + + for (var i = 0; i < numNames; i++) + nameStr += generateName() + "<br>"; + + if (document.getElementById("outputNames").checked) + nameDiv.innerHTML = nameStr; + + document.getElementById("loading").innerHTML = ""; + + var dload; + dload = document.getElementById("download"); + dload.innerHTML = "Download names (.txt)"; + var txt = nameStr.replace(/<br>/g, "\n"); + dload.href = "data:text/plain;charset=utf-8," + encodeURI(txt); + document.getElementById("button").disabled = false; +} + +start();
\ No newline at end of file diff --git a/js/ant.js b/js/ant.js new file mode 100644 index 0000000..f6ef4c6 --- /dev/null +++ b/js/ant.js @@ -0,0 +1,135 @@ +var cells = []; +var direction = 'u'; +var antPos; +var running = false; + +function turnright(d) +{ + switch(d) + { + case 'u': + return 'r'; + case 'd': + return 'l'; + case 'r': + return 'd'; + case 'l': + return 'u'; + } +} + +function turnleft(d) +{ + return turnright(turnright(turnright(d))); +} + + +function setup() +{ + frameRate(100); + createCanvas(500, 500); + antPos = [width/2, height/2]; + + for (var i = 0; i < height; i++) + { + cells.push([]); + for (var j = 0; j < width; j++) + { + cells[i].push(1); + } + } + stroke(255, 0, 0); + point(antPos[0], antPos[1]); +} + + + +function start() +{ + running = true; +} + +function stop() +{ + running = false; +} + + + +function resetPos() +{ + drawCell(antPos[0], antPos[1]); + antPos[0] = width/2; + antPos[1] = height/2; + stroke(255, 0, 0); + point(antPos[0], antPos[1]); +} + +function drawCell(x, y) +{ + stroke(cells[y][x]*255); + point(x, y); +} + +function draw() +{ + if (!running) + return; + + for (var i = 0; i < parseInt(document.getElementById("speed").value); i++) + { + + if (antPos[0] < 0 || antPos[0] >= width || antPos[1] < 0 || antPos[1] >= height) + { + running = false; + } + + if (!running) + return; + + if (cells[antPos[1]][antPos[0]] == 1) + direction = turnright(direction); + else + direction = turnleft(direction); + + + cells[antPos[1]][antPos[0]] = 1-cells[antPos[1]][antPos[0]]; + drawCell(antPos[0], antPos[1]); + + if (direction == 'u') + antPos[1]--; + else if (direction == 'l') + antPos[0]--; + else if (direction == 'd') + antPos[1]++; + else if (direction == 'r') + antPos[0]++; + + stroke(255, 0, 0); + point(antPos[0], antPos[1]); + } +} + +function mouseDragged() +{ + if (mouseX < 0 || mouseX >= width || mouseY < 0 || mouseY >= height) + return; + cells[mouseY][mouseX] = 1-cells[mouseY][mouseX]; + drawCell(mouseX, mouseY); +} + +function clearCells() +{ + cells = []; + for (var i = 0; i < height; i++) + { + cells.push([]); + for (var j = 0; j < width; j++) + { + cells[i].push(1); + } + } + background(255); + stroke(255, 0, 0); + point(antPos[0], antPos[1]); +}
\ No newline at end of file diff --git a/js/ballbounce.js b/js/ballbounce.js new file mode 100644 index 0000000..15c3a53 --- /dev/null +++ b/js/ballbounce.js @@ -0,0 +1,54 @@ +var WIDTH = 500; +var HEIGHT = 500; +var ball_radius = 25; +var ball_pos = [WIDTH/2, 0]; +var ball_vel = [0, 0]; +var score = 0; +var started = false; +function setup() +{ + createCanvas(WIDTH, HEIGHT); + reset(); +} + +function reset() +{ + background(255, 255, 255); + stroke(255, 0, 0); + noFill(); + rect(0, 0, WIDTH-1, HEIGHT-1); + noStroke(); +} + +function draw() +{ + + fill(0,0,0); + ellipse(ball_pos[0], ball_pos[1], ball_radius*2, ball_radius*2); + fill(255, 0, 0); + if (abs(ball_pos[0] - mouseX) < ball_radius && mouseY > ball_pos[1]) + { + if (dist(ball_pos[0], ball_pos[1], mouseX, mouseY) < ball_radius) + { + ball_vel = [(ball_pos[0] - mouseX) / 10, -4]; + ball_pos[1] -= 10; + score++; + started = true; + } + } + if (started) + ball_vel[1] += 0.1; + + ball_pos[0] += ball_vel[0]; + ball_pos[1] += ball_vel[1]; + + if (ball_pos[1] > HEIGHT) + { + alert("You lost. Score: " + score); + reset(); + ball_pos = [WIDTH/2, 0]; + ball_vel = [0, 0]; + score = 0; + started = false; + } +}
\ No newline at end of file diff --git a/js/clock.js b/js/clock.js new file mode 100644 index 0000000..314d002 --- /dev/null +++ b/js/clock.js @@ -0,0 +1,116 @@ +var WIDTH = 600; +var HEIGHT = 600; +var CIRCLE_SIZE = 20; +var SECOND_CIRCLE = 100; +var MINUTE_CIRCLE = 200; +var HOUR_CIRCLE = 300; +var DAY_CIRCLE = 500; +function setup() +{ +createCanvas(600, 600); + ellipseMode(CENTER); +} + +function leapYear() +{ + return year() % 4 == 0 && (!(year() % 100 == 0) || year() % 400 == 0) ? 1 : 0; +} + + +function day365() +{ + var m = month(); + var currday = 0; + if (m == 0) + return day(); + currday += 31; + if (m == 1) + return day()+currday; + currday += 28 + leapYear(); + if (m == 2) + return day()+currday; + currday += 31; + if (m == 3) + return day()+currday; + currday += 30; + if (m == 4) + return day()+currday; + currday += 31; + if (m == 5) + return day()+currday; + currday += 30; + if (m == 6) + return day()+currday; + currday += 31; + if (m == 7) + return day()+currday; + currday += 31; + if (m == 8) + return day()+currday; + currday += 30; + if (m == 9) + return day()+currday; + currday += 31; + if (m == 10) + return day()+currday; + currday += 30; + if (m == 11) + return day()+currday; + currday += 31; + if (m == 12) + return day()+currday; + return -1; +} + +function draw() +{ + background(255, 255, 255); + var date = new Date(); + var ms = date.getTime() % 1000; + var s = second(); + var h = hour(); + var m = minute(); + var secs = s + ms/1000; + var mins = m + secs / 60; + var hrs = h + mins / 60; + var dy = day365() + hrs / 24; + + hours = TWO_PI * (hrs / 24) - HALF_PI; + minutes = TWO_PI * (mins / 60) - HALF_PI; + seconds = TWO_PI * (secs / 60) - HALF_PI; + date = TWO_PI * (dy / (365 + leapYear())) - HALF_PI; + noFill(); + + stroke(secs*4, 0, 0); + ellipse(WIDTH / 2, HEIGHT / 2, SECOND_CIRCLE, SECOND_CIRCLE); + stroke(0, mins*4, 0); + ellipse(WIDTH / 2, HEIGHT / 2, MINUTE_CIRCLE, MINUTE_CIRCLE); + stroke(0, 0, hrs*4); + ellipse(WIDTH / 2, HEIGHT / 2, HOUR_CIRCLE, HOUR_CIRCLE); + stroke(0, 0, 0); + ellipse(WIDTH / 2, HEIGHT / 2, DAY_CIRCLE, DAY_CIRCLE); + + noStroke(); + + var dayX = (cos(date) * DAY_CIRCLE / 2) + WIDTH / 2; + var dayY = (sin(date) * DAY_CIRCLE / 2) + HEIGHT / 2; + + var hourX = (cos(hours) * HOUR_CIRCLE / 2) + WIDTH / 2; + var hourY = (sin(hours) * HOUR_CIRCLE / 2) + HEIGHT / 2; + + var minuteX = (cos(minutes) * MINUTE_CIRCLE / 2) + WIDTH / 2; + var minuteY = (sin(minutes) * MINUTE_CIRCLE / 2) + HEIGHT / 2; + + var secondX = (cos(seconds) * SECOND_CIRCLE / 2) + WIDTH / 2; + var secondY = (sin(seconds) * SECOND_CIRCLE / 2) + HEIGHT / 2; + + fill(secs*4, 0, 0); + ellipse(secondX, secondY, CIRCLE_SIZE, CIRCLE_SIZE); + fill(0, mins*4, 0); + ellipse(minuteX, minuteY, CIRCLE_SIZE, CIRCLE_SIZE); + fill(0, 0, hrs*4); + ellipse(hourX, hourY, CIRCLE_SIZE, CIRCLE_SIZE); + fill(0, 0, 0); + ellipse(dayX, dayY, CIRCLE_SIZE, CIRCLE_SIZE); + textSize(30); +}
\ No newline at end of file @@ -0,0 +1,203 @@ +try +{ +var lineLength = 5; +var canvas; +var ctx; +var w; +var h; +var endpoints; +var allendpoints; +var delay; +var iterateTimeout; +var stopped = false; +var running = false; +var chance; + +function line(x1, y1, x2, y2) +{ + ctx.beginPath(); + ctx.moveTo(x1+0.5, y1); + ctx.lineTo(x2+0.5, y2); + ctx.stroke(); +} + +function Endpoint(x, y, direction) +{ + this.x = x; + this.y = y; + this.direction = direction; +} + +function equals(other) +{ + return this.x == other.x && this.y == other.y; +} + +function nequals(other) +{ + return !this.equals(other); +} + +function multiple(e) +{ + var count = 0; + for (var i = 0; i < allendpoints.length; i++) + { + if (allendpoints[i].equals(e)) + { + count++; + + if (count > 1) + return true; + } + } + + return false; + +} + +function rmvAll(e) +{ + var newArray = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i].nequals(e)) + newArray.push(endpoints[i]); + endpoints = newArray; +} + + +function iterate() +{ + var indexOfThis = endpoints.indexOf(this); + endpoints.splice(indexOfThis, 1); + + if (this.direction == 'h') + { + line(this.x-lineLength, this.y, this.x+lineLength, this.y); + var e1 = new Endpoint(this.x-lineLength, this.y, 'v'); + var e2 = new Endpoint(this.x+lineLength, this.y, 'v'); + + + } + else + { + line(this.x, this.y-lineLength, this.x, this.y+lineLength); + var e1 = new Endpoint(this.x, this.y-lineLength, 'h'); + var e2 = new Endpoint(this.x, this.y+lineLength, 'h'); + + } + + if (Math.random() < chance) + endpoints.push(e1); + if (Math.random() < chance) + endpoints.push(e2); + allendpoints.push(e1); + allendpoints.push(e2); + +} + +Endpoint.prototype.equals=equals; +Endpoint.prototype.nequals=nequals; +Endpoint.prototype.iterate=iterate; + +function iterateAll() +{ + + var cpy = endpoints.slice(); + + for (var i = 0; i < cpy.length; i++) + if (multiple(cpy[i])) + rmvAll(cpy[i]); + + cpy = endpoints.slice(); + for (var i = 0; i < cpy.length; i++) + cpy[i].iterate(); + + iterateTimeout = setTimeout(iterateAll, delay); +} + +function stop() +{ + running = false; + if (!stopped) + { + clearTimeout(iterateTimeout); + var download = document.createElement('a'); + download.innerHTML = 'Download H'; + download.href = canvas.toDataURL(); + download.download = 'h.png'; + download.id = 'download'; + document.body.appendChild(download); + stopped = true; + } +} + +function start() +{ + if (!running) + { + running = true; + + + w = canvas.width; + h = canvas.height; + endpoints = []; + allendpoints = []; + var a = new Endpoint(w/2, h/2, 'h'); + endpoints.push(a); + a.iterate(); + iterateAll(); + } +} + + +function beginH() +{ +try{ + if (!stopped) + { + canvas = document.getElementById('canvas'); + + var w = parseInt(document.getElementById('w').value); + var h = parseInt(document.getElementById('h').value); + canvas.width = w; + canvas.height = h; + + ctx = canvas.getContext('2d'); + ctx.strokeStyle = "#000"; + ctx.fillStyle = '#fff'; + + ctx.fillRect(0, 0, w, h); + + lineLength= document.getElementById('lineLength').value; + lineLength = parseInt(lineLength); + + delay = parseInt(document.getElementById('delay').value); + + document.body.appendChild(canvas); + document.body.appendChild(document.createElement('br')); + + chance = parseFloat(document.getElementById('chance').value); + + + start(); + } + else + { + document.body.removeChild(document.getElementById('download')); + iterateAll(); + stopped = false; + } + } +catch(e) +{ + document.write(e); +} +} + + +} +catch(e) +{ + document.write(e); +}
\ No newline at end of file diff --git a/js/header_links.js b/js/header_links.js new file mode 100644 index 0000000..3b5b3dd --- /dev/null +++ b/js/header_links.js @@ -0,0 +1,5 @@ +document.getElementById("header_links_div").innerHTML = '<a class="header_link" href="index.html">Home</a> ' + +'<a class="header_link" href="all.html">All</a> ' + +'<a class="header_link" href="mathematical.html">Mathematical Demonstrations</a> ' + +'<a class="header_link" href="games.html">Games</a> ' + +'<a class="header_link" href="apps.html">Android Apps</a> ';
\ No newline at end of file diff --git a/js/latexit.js b/js/latexit.js new file mode 100644 index 0000000..f6d76b9 --- /dev/null +++ b/js/latexit.js @@ -0,0 +1,110 @@ +/* +* LaTeX IT - JavaScript to Convert Latex within an HTML page into Equations +* Copyright (C) 2009 William Bateman, 2008 Waipot Ngamsaad + +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +var LatexIT = { + mode : 'gif', + imgnum : 0, + isFirefox:false, + init : function() { + // We need to review the support for SVG. Latest released versions are not supporting this as they should + // if(document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1")) + // this.mode='svg'; + + // browser name + // svg support in FireFox is not allowing two images to occur currently on the same line. + + var ua = navigator.userAgent.toLowerCase(); + if(ua.indexOf("firefox")!=-1) + { + // this.isFirefox = true; + } + }, + + pre : function(txt) { + if ( !txt.match(/<img.*?>/i) ) + { + //Clean code + txt=txt.replace(/<br>/mgi,""); + txt=txt.replace(/<br \/>/mgi,""); + //Create img tag + // txt = " <img src=\"http://latex.codecogs.com/"+this.mode+".latex?"+ txt +"\" /> "; + // txt = " <object type=\"image/svg+xml\" width=\"20\" data=\"http://latex.codecogs.com/"+this.mode+".latex?"+ txt +"\" /> "; + + if(this.mode=='svg') + { + // Best for Firefox + if(this.isFirefox) + txt = " <object type=\"image/svg+xml\" data=\"http://latex.codecogs.com/"+this.mode+".latex?"+ txt +"\" class=\"latex\" style=\"margin:0; padding:0; border:0\" /> "; + else // Best for Chrome + // txt = " <object type=\"image/svg+xml\" data=\"http://latex.codecogs.com/"+this.mode+".latex?"+ txt +"\" class=\"latex\" /> "; + txt = " <img src=\"http://latex.codecogs.com/"+this.mode+".latex?"+ txt +"\" alt=\""+ txt +"\" title=\""+ txt +"\" border=\"0\" class=\"latex\" /> "; + } + else + txt = " <img src=\"http://latex.codecogs.com/"+this.mode+".latex?"+ txt +"\" alt=\""+txt+"\" border=\"0\" class=\"latex\" /> "; + } + return txt; + }, + + latex : function(txt) { + var html, htmlinline; + if(this.isFirefox) { + html=" <object type=\"image/svg+xml\" data=\"http://latex.codecogs.com/"+this.mode+".latex?$2\" class=\"latex\" /> "; + htmlinline=" <object type=\"image/svg+xml\" data=\"http://latex.codecogs.com/"+this.mode+".latex?\\inline $2\" class=\"latex\" /> "; + } + else { + html=" <img src=\"http://latex.codecogs.com/"+this.mode+".latex?$2\" border=\"0\" class=\"latex\" /> "; + htmlinline=" <img src=\"http://latex.codecogs.com/"+this.mode+".latex?\\inline $2\" border=\"0\" class=\"latex\" /> "; + } + + + txt=txt.replace(/(^\$|[^\\]\$)(.*?[^\\])\$/gm, htmlinline); + txt=txt.replace(/(^\\|[^\\]\\)\[(.*?[^\\])\\\]/mg," <br/>"+html+"<br/> "); + txt=txt.replace(/\\\$/mg,"\$"); + txt=txt.replace(/\\\\(\[|\])/mg,"$1"); + + return txt; + }, + + render : function(tag, latexmode) { + var eqn = window.document.getElementsByTagName(tag); + for (var i=0; i<eqn.length; i++) { + if(latexmode) + eqn[i].innerHTML = LatexIT.latex(eqn[i].innerHTML); + else if (eqn[i].getAttribute("lang") == "latex" || eqn[i].getAttribute("xml:lang") == "latex") + eqn[i].innerHTML = LatexIT.pre(eqn[i].innerHTML); + } + }, + + add : function(tag, latexmode) + { + if(typeof(latexmode)=='undefined') latexmode=false; + if(window.addEventListener) + window.addEventListener('load', new Function('LatexIT.render("'+tag+'", '+latexmode+')'),false); + else + window.attachEvent('onload', new Function('LatexIT.render("'+tag+'", '+latexmode+')') ); + }, + + scale : function(e,scale) + { + e.width =(e.width*scale); + e.height=(e.height*scale); + } +}; + +LatexIT.init(); +LatexIT.add('*');
\ No newline at end of file diff --git a/js/magnets.js b/js/magnets.js new file mode 100644 index 0000000..7d82507 --- /dev/null +++ b/js/magnets.js @@ -0,0 +1,83 @@ +var magnets = []; +var MAGNET_SIZE = 10; +var gameOver = false; +var safe_zone_radius = 300; +var Magnet = function() +{ + do + { + this.x = Math.floor(Math.random() * width); + this.y = Math.floor(Math.random() * height); + } + while(dist(this.x, this.y, mouseX, mouseY) < safe_zone_radius); + magnets.push(this); +} + +Magnet.prototype.drawIt = function() +{ + noStroke(); + fill(150, 0, 0); + ellipse(this.x, this.y, MAGNET_SIZE, MAGNET_SIZE); + + + if (abs(this.x - mouseX) < MAGNET_SIZE / 2 && abs(this.y - mouseY) < MAGNET_SIZE / 2) + { + gameOver = true; + alert("Score: " + magnets.length); + magnets = []; + gameOver = false; + safe_zone_radius = 300; + } + + + + this.x -= 50 * cos(atan2(this.y-mouseY, this.x-mouseX)) / dist(this.x, this.y, mouseX, mouseY); + this.y -= 50 * sin(atan2(this.y-mouseY, this.x-mouseX)) / dist(this.x, this.y, mouseX, mouseY); + + + +} + +function setup() +{ + createCanvas(500, 500); + ellipseMode(CENTER); +} + +function draw() +{ + if (gameOver) + return; + + background(255); + + stroke(255, 0, 0); + noFill(); + rect(0, 0, width-1, height-1); + fill(0); + noStroke(); + textSize(20); + text("Score: " + magnets.length, 5, 20); + fill(0, 0, 150); + ellipse(mouseX, mouseY, MAGNET_SIZE, MAGNET_SIZE); + noFill(); + stroke(0, 255, 0); + safe_zone_radius *= 0.9995; + ellipse(mouseX, mouseY, safe_zone_radius, safe_zone_radius); + + if ((mouseX > width || mouseY > height) && magnets.length > 0) + { + gameOver = true; + alert("You went out of bounds. Score: " + magnets.length); + magnets = []; + gameOver = false; + safe_zone_radius = 300; + } + + if (frameCount % 100 === 0 && mouseX < width && mouseY < height) + new Magnet(); + for (var i = 0; i < magnets.length; i++) + { + magnets[i].drawIt(); + } +} diff --git a/js/magnets_old_version.js b/js/magnets_old_version.js new file mode 100644 index 0000000..6169725 --- /dev/null +++ b/js/magnets_old_version.js @@ -0,0 +1,83 @@ +var magnets = []; +var MAGNET_SIZE = 10; +var gameOver = false; +var safe_zone_radius = 300; +var Magnet = function() +{ + do + { + this.x = Math.floor(Math.random() * width); + this.y = Math.floor(Math.random() * height); + } + while(dist(this.x, this.y, width/2, height/2) < safe_zone_radius); + magnets.push(this); +} + +Magnet.prototype.drawIt = function() +{ + noStroke(); + fill(150, 0, 0); + ellipse(this.x, this.y, MAGNET_SIZE, MAGNET_SIZE); + + + if (abs(this.x - mouseX) < MAGNET_SIZE / 2 && abs(this.y - mouseY) < MAGNET_SIZE / 2) + { + gameOver = true; + alert("Score: " + magnets.length); + magnets = []; + gameOver = false; + safe_zone_radius = 300; + } + + + + this.x -= 50 * cos(atan2(this.y-mouseY, this.x-mouseX)) / dist(this.x, this.y, mouseX, mouseY); + this.y -= 50 * sin(atan2(this.y-mouseY, this.x-mouseX)) / dist(this.x, this.y, mouseX, mouseY); + + + +} + +function setup() +{ + createCanvas(500, 500); + ellipseMode(CENTER); +} + +function draw() +{ + if (gameOver) + return; + + background(255); + + stroke(255, 0, 0); + noFill(); + rect(0, 0, width-1, height-1); + fill(0); + noStroke(); + textSize(20); + text("Score: " + magnets.length, 5, 20); + fill(0, 0, 150); + ellipse(mouseX, mouseY, MAGNET_SIZE, MAGNET_SIZE); + noFill(); + stroke(0, 255, 0); + safe_zone_radius *= 0.9995; + ellipse(width/2, height/2, safe_zone_radius, safe_zone_radius); + + if ((mouseX > width || mouseY > height) && magnets.length > 0) + { + gameOver = true; + alert("You went out of bounds. Score: " + magnets.length); + magnets = []; + gameOver = false; + safe_zone_radius = 300; + } + + if (frameCount % 100 === 0 && mouseX < width && mouseY < height) + new Magnet(); + for (var i = 0; i < magnets.length; i++) + { + magnets[i].drawIt(); + } +} diff --git a/js/mandelbrot.js b/js/mandelbrot.js new file mode 100644 index 0000000..cdfc467 --- /dev/null +++ b/js/mandelbrot.js @@ -0,0 +1,179 @@ +var increment = 0.01; +var iterations = 30; +var startI = -2.5; +var startJ = -2.5; +var power = 2; +var canvas = document.getElementById("canvas"); +var ctx = canvas.getContext("2d"); +var width = canvas.width; +var height = canvas.height; +function reciprocal(re, im) +{ + return [re/(re*re+im*im), -im/(im*im+re*re)]; +} + +function add(re1, im1, re2, im2) +{ + return [re1+re2, im1+im2]; +} + +function multiply(re1, im1, re2, im2) +{ + return [re1*re2-im1*im2, re1*im2+re2*im1]; +} + +function cpower(re, im, power) +{ + if (power < 0) + { + var r = reciprocal(re, im); + return cpower(r[0], r[1], -power); + } + var x = [re, im]; + var i = 1; + while (i < power) + { + x = multiply(x[0], x[1], re, im); + i++; + } + return x; + +} + +function iterate(z, power, c) +{ + var x = cpower(z[0], z[1], power); + return add(x[0], x[1], c[0], c[1]); +} + +function cabs(z) +{ + return Math.sqrt(z[0]*z[0]+z[1]*z[1]); +} + +function num_iterations(power, c, max_iterations) +{ + var iterations = 0; + var z = [0, 0]; + while (iterations < max_iterations && cabs(z) < 2) + { + z = iterate(z, power, c); + iterations++; + } + return iterations; +} + +function draw_mandelbrot() +{ + var size = width*increment; + + + + var endI = startI+size; + var endJ = startJ+size; + + var imgData = ctx.createImageData(width, height); + + for (var i = startI; i < endI; i+=increment) + { + for (var j = startJ; j < endJ; j+=increment) + { + var ipos = i*(1.0/increment)-startI*(1.0/increment); + var jpos = j*(1.0/increment)-startJ*(1.0/increment); + jpos = Math.floor(Math.round(jpos)); + ipos = Math.floor(Math.round(ipos)); + + var x = num_iterations(power, [i, j], iterations)/iterations * 255 + imgData.data[4*(jpos*width+ipos)] = x; + imgData.data[1+4*(jpos*width+ipos)] = x; + imgData.data[2+4*(jpos*width+ipos)] = x; + imgData.data[3+4*(jpos*width+ipos)] = 255; + + + } + } + ctx.putImageData(imgData, 0, 0); +} + +function draw() +{ + +} + +function map(val, startA, endA, startB, endB) +{ + return (endB-startB)*((val-startA)/(endA-startA))+startB; +} + +function mousePressed(e) +{ + var x; + var y; + if (e.pageX || e.pageY) { + x = e.pageX; + y = e.pageY; + } + else { + x = e.clientX; + y = e.clientY; + } + x -= canvas.offsetLeft; + y -= canvas.offsetTop; + if (x < 0 || x > width || y < 0 || y > height) + return; + + if (e.button == 0) + { + increment /= 2; + startI = map(x, 0, width, startI, startI+width*increment); + startJ = map(y, 0, height, startJ, startJ+width*increment); + + draw_mandelbrot(); + } +} + +function keyPressed(e) +{ + + var key = String.fromCharCode(e.keyCode); + if (key == "A") + { + increment /= 2; + startI = startI+width*increment/2; + startJ = startJ+height*increment/2; + + draw_mandelbrot(); + } + if (key == "Q") + { + increment *= 2; + startI = startI-width*increment/2; + startJ = startJ-height*increment/2; + draw_mandelbrot(); + } + if (key == "P") + { + power++; + draw_mandelbrot(); + } + if (key == "L") + { + power--; + draw_mandelbrot(); + } + if (key == "I") + { + iterations = Math.floor(iterations*1.5); + draw_mandelbrot(); + } + if (key == "K") + { + iterations = Math.floor(iterations/1.5); + draw_mandelbrot(); + } +} +canvas.addEventListener("mousedown", mousePressed); +document.body.addEventListener("keydown", keyPressed); + +draw_mandelbrot(); +
\ No newline at end of file diff --git a/js/mazesolver.js b/js/mazesolver.js new file mode 100644 index 0000000..aa43ed3 --- /dev/null +++ b/js/mazesolver.js @@ -0,0 +1,457 @@ + +var canvas = document.getElementById('Canvas'); +var form = document.getElementById('Form'); + +canvas.onmousedown = function(e) { if (e.button === 1) return false; } + +var X; +var Y; +var TILEWIDTH; + +var started = false; + +var tiles = []; +for (var i = 0; i < Y; i++) +{ + tiles.push([]); + for (var j = 0; j < X; j++) + tiles[i].push(false); +} + +var tilesToGoal = []; +for (var i = 0; i < Y; i++) +{ + tilesToGoal.push([]); + for (var j = 0; j < X; j++) + tilesToGoal[i].push(false); +} + + +var ctx = canvas.getContext('2d'); + +var mouseDown = false; + +var startPlaced = false; +var endPlaced = false; +var begun = false; + +var start; //(location) +var end; //(location) + +var doesItWorkParagraph = document.getElementById('DoesItWork'); + +function reset() +{ + TILEWIDTH = Math.ceil(canvas.width / X); + if (canvas.width !== TILEWIDTH * X) + canvas.width = TILEWIDTH * X; + if (canvas.height !== TILEWIDTH * X) + canvas.height = TILEWIDTH * X; + startPlaced = false; + endPlaced = false; + begun = false; + start = []; + end = []; + tiles = []; + for (var i = 0; i < Y; i++) + { + tiles.push([]); + for (var j = 0; j < X; j++) + tiles[i].push(false); + } + + tilesToGoal = []; + for (var i = 0; i < Y; i++) + { + tilesToGoal.push([]); + for (var j = 0; j < X; j++) + tilesToGoal[i].push(false); + } +} + +function maxIndex(l) +{ + if (l === []) + return -1; + + var highest = l[0]; + var highestindex = 0; + + for (var i = 1; i < l.length; i++) + if (l[i] > highest) + { + highest = l[i]; + highestindex = i; + } + return highestindex; +} + +function to1d(l) +{ + //Turns a 2d array into a 1d array + var newl = []; + for (var y = 0; y < l.length; y++) + for (var x = 0; x < l[y].length; x++) + newl.push(l[y][x]); + return newl; +} + +function equals2d(array1, array2) +{ + return equals(to1d(array1), to1d(array2)); +} +function maxValue(l) +{ + if (l === []) + return -1; + + var highest = l[0]; + + for (var i = 1; i < l.length; i++) + if (l[i] > highest) + highest = l[i]; + + return highest; +} + + + +function copy2d(array) +{ + var newarray = []; + for (var i = 0; i < array.length; i++) + newarray.push(array[i].slice()) + + return newarray; +} + +function equals(array1, array2) +{ + for (var i = 0; i < array1.length; i++) + if (array1[i] !== array2[i]) + return false; + return true; +} + +function index2d(array, x) +{ + for (var i = 0; i < array.length; i++) + if (equals(array[i], x)) + return i; + return -1; +} + +function rmvArray(array2d, array) +{ + var newarray = []; + for (var i = 0; i < array2d.length; i++) + if (!(equals(array2d[i], array))) + newarray.push(array2d[i]); + + return newarray; +} + +function getTilesToGoal() +{ + + for (var y = 0; y < tiles.length; y++) + for (var x = 0; x < tiles[y].length; x++) + tilesToGoal[y][x] = tiles[y][x] ? -1 : -2 + + + + var oldTTG; + var highestValue; + var surroundingTiles; + + tilesToGoal[end[1]][end[0]] = 0; + var list = [[1, 2], [0, 0], [4, 5]]; + var n = 0; + + do + { + n++; + oldTTG = copy2d(tilesToGoal); + highestValue = maxValue(to1d(tilesToGoal)); + + for (var y = 0; y < tilesToGoal.length; y++) + + for (var x = 0; x < tilesToGoal[y].length; x++) + + if (tilesToGoal[y][x] === highestValue) + { + + surroundingTiles = [[x+1, y], [x, y+1], [x-1, y], [x, y-1]]; + if (y === tilesToGoal.length - 1) surroundingTiles = rmvArray(surroundingTiles, [x, y+1]); + if (y === 0) surroundingTiles = rmvArray(surroundingTiles, [x, y-1]); + if (x === tilesToGoal[y].length - 1) surroundingTiles = rmvArray(surroundingTiles, [x+1, y]); + if (x === 0) surroundingTiles = rmvArray(surroundingTiles, [x-1, y]); + + for (var i = 0; i < surroundingTiles.length; i++) + { + if ((!tiles[surroundingTiles[i][1]][surroundingTiles[i][0]]) && (tilesToGoal[surroundingTiles[i][1]][surroundingTiles[i][0]] < 0)) + tilesToGoal[surroundingTiles[i][1]][surroundingTiles[i][0]] = highestValue + 1 + } + } + if (tilesToGoal[start[1]][start[0]] > 0) + break; + } + while (!(equals2d(tilesToGoal, oldTTG))); + + + + /*for (var y = 0; y < tilesToGoal.length; y++) + { + for (var x = 0; x < tilesToGoal[y].length; x++) + document.write(tilesToGoal[y][x] + ' '); + + document.write('<br>'); + }*/ + + + if (tilesToGoal[start[1]][start[0]] < 0) + return false; + + return true; +} + +function remove(element) +{ + element.parentNode.removeChild(element); +} + +function startCreation() +{ + var size = form.elements[0].value; + X = size; + Y = size; + var button = document.getElementById('StartButton'); + button.innerHTML = 'Solve Maze'; + button.onclick = function(){try{begin();}catch(err){document.write(err)}}; + remove(form); + canvas.width = 500; + canvas.height = 500; + reset(); + clear(); + +} + +function begin() +{ + + if (!(startPlaced && endPlaced)) + { + doesItWorkParagraph.innerHTML = 'You must choose a start and end location (right-click).'; + reset(); + clear(); + return; + } + + + var mazeWorks = getTilesToGoal(); + if (mazeWorks === false) + { + doesItWorkParagraph.innerHTML = 'Maze cannot be solved.'; + reset(); + clear(); + return; + } + + started = true; + + var location = start; + var x; + var y; + var surroundingTiles; + + while (!(equals(location, end))) + { + x = location[0]; + y = location[1]; + + circle(x * TILEWIDTH + TILEWIDTH / 2, y * TILEWIDTH + TILEWIDTH / 2, parseInt(TILEWIDTH / 2.5), '#ffaaaa'); + surroundingTiles = [[x+1, y], [x, y+1], [x-1, y], [x, y-1]]; + if (y === tilesToGoal.length - 1) surroundingTiles = rmvArray(surroundingTiles, [x, y+1]); + if (y === 0) surroundingTiles = rmvArray(surroundingTiles, [x, y-1]); + if (x === tilesToGoal[y].length - 1) surroundingTiles = rmvArray(surroundingTiles, [x+1, y]); + if (x === 0) surroundingTiles = rmvArray(surroundingTiles, [x-1, y]); + + for (var i = 0; i < surroundingTiles.length; i++) + if ((tilesToGoal[surroundingTiles[i][1]][surroundingTiles[i][0]] < tilesToGoal[y][x]) && tilesToGoal[surroundingTiles[i][1]][surroundingTiles[i][0]] >= 0) + { + location = [surroundingTiles[i][0], surroundingTiles[i][1]]; + break; //(inner for loop) + } + + } + + x = location[0]; + y = location[1]; + + circle(x * TILEWIDTH + TILEWIDTH / 2, y * TILEWIDTH + TILEWIDTH / 2, parseInt(TILEWIDTH / 2.5), '#ffaaaa'); + + reset(); + +} + + + + +function mouseMoved(event) +{ + + if ((!mouseDown) || started) + return; + + var button = -1; + + if ("which" in event) + if (event.which == 2) button = 1; else if (event.which == 3) button = 2; else button = 0; + else if ("button" in event) + button = event.button; + else + button = 0; + + + var x = event.offsetX; + var y = event.offsetY; + + var tilex = Math.floor(x / TILEWIDTH); + var tiley = Math.floor(y / TILEWIDTH); + + if (button === 1) + { + rect(tilex * TILEWIDTH, tiley * TILEWIDTH, TILEWIDTH, TILEWIDTH, '#dddddd'); + tiles[tiley][tilex] = false; + if (equals([tilex, tiley], start)) + { + start = []; + startPlaced = false; + } + if (equals([tilex, tiley], end)) + { + end = []; + endPlaced = false; + } + return; + } + else if (button === 2) + return; + + var notClickedOnStart = (!startPlaced) || (!((tilex === start[0]) && (tiley === start[1]))); + + var notClickedOnEnd = (!endPlaced) || (!((tilex === end[0]) && (tiley === end[1]))); + + if (notClickedOnStart && notClickedOnEnd) + { + rect(tilex * TILEWIDTH, tiley * TILEWIDTH, TILEWIDTH, TILEWIDTH, '#aaaaaa'); + tiles[tiley][tilex] = true; + } + +} + +function clear() +{ + rect(0, 0, canvas.width, canvas.height, '#dddddd'); + started = false; +} + +function mousePressed(event) +{ + + if (started) + { + clear(); + return; + } + + var button = -1; + + if ("which" in event) + if (event.which == 2) button = 1; else if (event.which == 3) button = 2; else button = 0; + else if ("button" in event) + button = event.button; + else + button = 0; + + if (button !== 2) + { + mouseDown = true; + mouseMoved(event); + } + else + { + var x = event.offsetX; + var y = event.offsetY; + + var tilex = Math.floor(x / TILEWIDTH); + var tiley = Math.floor(y / TILEWIDTH); + if ((!startPlaced) && (!tiles[tiley][tilex])) + { + start = [tilex, tiley]; + rect(tilex * TILEWIDTH, tiley * TILEWIDTH, TILEWIDTH, TILEWIDTH, '#00ff00'); + startPlaced = true; + } + else if ((!endPlaced) && (!tiles[tiley][tilex])) + { + end = [tilex, tiley]; + rect(tilex * TILEWIDTH, tiley * TILEWIDTH, TILEWIDTH, TILEWIDTH, '#ffff00'); + endPlaced = true; + return; + } + } +} + +function mouseReleased(event) +{ + mouseDown = false; +} + + + +canvas.addEventListener('mousemove', mouseMoved, false); +canvas.addEventListener('mousedown', mousePressed, false); +canvas.addEventListener('mouseup', mouseReleased, false); + +function circle(x, y, r, colour) +{ + ctx.fillStyle = colour; + ctx.beginPath(); + ctx.arc(x, y, r, 0, 2*Math.PI); + ctx.fill(); +}; + + +function rect(x, y, w, h, colour) +{ + ctx.fillStyle = colour; + ctx.fillRect(x, y, w, h); +}; + + +function text(str, x, y) +{ + ctx.font = "20px Helvetica"; + ctx.fillStyle = '#000000'; + ctx.fillText(str, x, y); +} + +function line(x1, y1, x2, y2) +{ + ctx.strokeStyle = '#000000'; + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); +}; + +function line(x1, y1, x2, y2, color) +{ + ctx.strokeStyle = color + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); +}; + +rect(0, 0, canvas.width, canvas.height, '#dddddd'); diff --git a/js/modularcircles.js b/js/modularcircles.js new file mode 100644 index 0000000..8b588f9 --- /dev/null +++ b/js/modularcircles.js @@ -0,0 +1,54 @@ +function setup()
+{
+ createCanvas(600, 600);
+}
+
+function nPoints()
+{
+ return document.getElementById("npoints").value;
+}
+
+function shouldMul()
+{
+ return document.getElementById("should_mul").checked;
+}
+
+function amount()
+{
+ return parseFloat(document.getElementById("amount").value);
+}
+
+function getPos(number)
+{
+ angle = 2*PI * number/(nPoints());
+ return [cos(angle)*250+300, sin(angle)*250+300];
+}
+
+function draw()
+{
+ if (shouldMul())
+ document.getElementById("amount").step = 0.1;
+ else
+ document.getElementById("amount").step = 1;
+ background(255);
+ ellipseMode(CENTER);
+ noStroke();
+ fill(0);
+ for (var i = 0; i < nPoints(); i++)
+ ellipse(getPos(i)[0], getPos(i)[1], 3, 3);
+
+ stroke(0);
+ for (var i = 0; i < nPoints(); i++)
+ {
+ if (shouldMul())
+ {
+ stroke(map(map((amount()*i)%nPoints(), 0, nPoints(), 0, 256) - map(i, 0, nPoints(), 0, 256), -256, 256, 0, 256), map(i, 0, nPoints(), 0, 256), map((amount()*i)%nPoints(), 0, nPoints(), 0, 256));
+ line(getPos(i)[0], getPos(i)[1], getPos((amount()*i)%nPoints())[0], getPos((amount()*i)%nPoints())[1]);
+ }
+ else
+ {
+ stroke(0, map(i, 0, nPoints(), 0, 256), map((amount()+i)%nPoints(), 0, nPoints(), 0, 256));
+ line(getPos(i)[0], getPos(i)[1], getPos((amount()+i)%nPoints())[0], getPos((amount()+i)%nPoints())[1]);
+ }
+ }
+}
\ No newline at end of file diff --git a/js/modularpascal.js b/js/modularpascal.js new file mode 100644 index 0000000..80f65d3 --- /dev/null +++ b/js/modularpascal.js @@ -0,0 +1,55 @@ +var triangle = [];
+var colors = [];
+var currentX;
+function getX()
+{
+ return document.getElementById("mod").value;
+}
+
+
+
+function updateTriangle()
+{
+ var x = getX();
+ triangle = [];
+ currentX = x;
+ for (var i = 0; i < height/2; i++)
+ {
+ triangle.push([]);
+ triangle[i].push(1);
+ for (var j = 1; j < i; j++)
+ triangle[i].push((triangle[i-1][j-1]+triangle[i-1][j])%x);
+ triangle[i].push(1);
+ }
+ colors = [];
+ for (var i = 0; i < x; i++)
+ colors.push([random(255), random(255), random(255)]);
+ background(255);
+ noStroke();
+ for (var i = 0; i < height/2; i++)
+ {
+ for (var j = 0; j <= i; j++)
+ {
+ fill(colors[triangle[i][j]][0], colors[triangle[i][j]][1], colors[triangle[i][j]][2]);
+ rect(getPos(i, j)[0], getPos(i, j)[1], 2, 2);
+
+ }
+ }
+
+}
+
+function setup()
+{
+ createCanvas(512, 512);
+ updateTriangle();
+}
+
+function getPos(row, column)
+{
+ return [width/2 - row + 2 * column, row*2];
+}
+
+function draw()
+{
+
+}
diff --git a/js/p5.js b/js/p5.js new file mode 100644 index 0000000..43d7136 --- /dev/null +++ b/js/p5.js @@ -0,0 +1,29765 @@ +/*! p5.js v0.4.23 March 04, 2016 */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.p5 = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ + +},{}],2:[function(_dereq_,module,exports){ +// Run-time checking of preconditions. + +'use strict'; + +// Precondition function that checks if the given predicate is true. +// If not, it will throw an error. +exports.argument = function(predicate, message) { + if (!predicate) { + throw new Error(message); + } +}; + +// Precondition function that checks if the given assertion is true. +// If not, it will throw an error. +exports.assert = exports.argument; + +},{}],3:[function(_dereq_,module,exports){ +// Drawing utility functions. + +'use strict'; + +// Draw a line on the given context from point `x1,y1` to point `x2,y2`. +function line(ctx, x1, y1, x2, y2) { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); +} + +exports.line = line; + +},{}],4:[function(_dereq_,module,exports){ +// Glyph encoding + +'use strict'; + +var cffStandardStrings = [ + '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', + 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', + 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', + 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', + 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', + 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', + 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', + 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', + 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', + 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', + 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', + 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', + 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', + 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', + 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', + 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', + 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', + 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', + 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', + 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', + 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', + 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', + 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', + 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', + 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', + 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', + 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', + 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', + 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', + 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', + '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; + +var cffStandardEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', + 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', + 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', + 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', + 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', + 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', + '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', + 'lslash', 'oslash', 'oe', 'germandbls']; + +var cffExpertEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', + 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', + 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', + 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', + 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', + 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', + 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', + 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', + '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', + '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', + 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', + 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', + 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', + 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', + 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', + 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', + 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', + 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; + +var standardNames = [ + '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', + 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', + 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', + 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', + 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', + 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', + 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', + 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', + 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', + 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', + 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', + 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', + 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', + 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', + 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', + 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', + 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', + 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', + 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', + 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', + 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', + 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + +// This is the encoding used for fonts created from scratch. +// It loops through all glyphs and finds the appropriate unicode value. +// Since it's linear time, other encodings will be faster. +function DefaultEncoding(font) { + this.font = font; +} + +DefaultEncoding.prototype.charToGlyphIndex = function(c) { + var code = c.charCodeAt(0); + var glyphs = this.font.glyphs; + if (glyphs) { + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + if (glyph.unicodes[j] === code) { + return i; + } + } + } + } else { + return null; + } +}; + +function CmapEncoding(cmap) { + this.cmap = cmap; +} + +CmapEncoding.prototype.charToGlyphIndex = function(c) { + return this.cmap.glyphIndexMap[c.charCodeAt(0)] || 0; +}; + +function CffEncoding(encoding, charset) { + this.encoding = encoding; + this.charset = charset; +} + +CffEncoding.prototype.charToGlyphIndex = function(s) { + var code = s.charCodeAt(0); + var charName = this.encoding[code]; + return this.charset.indexOf(charName); +}; + +function GlyphNames(post) { + var i; + switch (post.version) { + case 1: + this.names = exports.standardNames.slice(); + break; + case 2: + this.names = new Array(post.numberOfGlyphs); + for (i = 0; i < post.numberOfGlyphs; i++) { + if (post.glyphNameIndex[i] < exports.standardNames.length) { + this.names[i] = exports.standardNames[post.glyphNameIndex[i]]; + } else { + this.names[i] = post.names[post.glyphNameIndex[i] - exports.standardNames.length]; + } + } + + break; + case 2.5: + this.names = new Array(post.numberOfGlyphs); + for (i = 0; i < post.numberOfGlyphs; i++) { + this.names[i] = exports.standardNames[i + post.glyphNameIndex[i]]; + } + + break; + case 3: + this.names = []; + break; + } +} + +GlyphNames.prototype.nameToGlyphIndex = function(name) { + return this.names.indexOf(name); +}; + +GlyphNames.prototype.glyphIndexToName = function(gid) { + return this.names[gid]; +}; + +function addGlyphNames(font) { + var glyph; + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + glyph = font.glyphs.get(glyphIndex); + glyph.addUnicode(parseInt(c)); + } + + for (i = 0; i < font.glyphs.length; i += 1) { + glyph = font.glyphs.get(i); + if (font.cffEncoding) { + glyph.name = font.cffEncoding.charset[i]; + } else { + glyph.name = font.glyphNames.glyphIndexToName(i); + } + } +} + +exports.cffStandardStrings = cffStandardStrings; +exports.cffStandardEncoding = cffStandardEncoding; +exports.cffExpertEncoding = cffExpertEncoding; +exports.standardNames = standardNames; +exports.DefaultEncoding = DefaultEncoding; +exports.CmapEncoding = CmapEncoding; +exports.CffEncoding = CffEncoding; +exports.GlyphNames = GlyphNames; +exports.addGlyphNames = addGlyphNames; + +},{}],5:[function(_dereq_,module,exports){ +// The Font object + +'use strict'; + +var path = _dereq_('./path'); +var sfnt = _dereq_('./tables/sfnt'); +var encoding = _dereq_('./encoding'); +var glyphset = _dereq_('./glyphset'); + +// A Font represents a loaded OpenType font file. +// It contains a set of glyphs and methods to draw text on a drawing context, +// or to get a path representing the text. +function Font(options) { + options = options || {}; + + // OS X will complain if the names are empty, so we put a single space everywhere by default. + this.familyName = options.familyName || ' '; + this.styleName = options.styleName || ' '; + this.designer = options.designer || ' '; + this.designerURL = options.designerURL || ' '; + this.manufacturer = options.manufacturer || ' '; + this.manufacturerURL = options.manufacturerURL || ' '; + this.license = options.license || ' '; + this.licenseURL = options.licenseURL || ' '; + this.version = options.version || 'Version 0.1'; + this.description = options.description || ' '; + this.copyright = options.copyright || ' '; + this.trademark = options.trademark || ' '; + this.unitsPerEm = options.unitsPerEm || 1000; + this.ascender = options.ascender; + this.descender = options.descender; + this.supported = true; + this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); + this.encoding = new encoding.DefaultEncoding(this); + this.tables = {}; +} + +// Check if the font has a glyph for the given character. +Font.prototype.hasChar = function(c) { + return this.encoding.charToGlyphIndex(c) !== null; +}; + +// Convert the given character to a single glyph index. +// Note that this function assumes that there is a one-to-one mapping between +// the given character and a glyph; for complex scripts this might not be the case. +Font.prototype.charToGlyphIndex = function(s) { + return this.encoding.charToGlyphIndex(s); +}; + +// Convert the given character to a single Glyph object. +// Note that this function assumes that there is a one-to-one mapping between +// the given character and a glyph; for complex scripts this might not be the case. +Font.prototype.charToGlyph = function(c) { + var glyphIndex = this.charToGlyphIndex(c); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; +}; + +// Convert the given text to a list of Glyph objects. +// Note that there is no strict one-to-one mapping between characters and +// glyphs, so the list of returned glyphs can be larger or smaller than the +// length of the given string. +Font.prototype.stringToGlyphs = function(s) { + var glyphs = []; + for (var i = 0; i < s.length; i += 1) { + var c = s[i]; + glyphs.push(this.charToGlyph(c)); + } + + return glyphs; +}; + +Font.prototype.nameToGlyphIndex = function(name) { + return this.glyphNames.nameToGlyphIndex(name); +}; + +Font.prototype.nameToGlyph = function(name) { + var glyphIndex = this.nametoGlyphIndex(name); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; +}; + +Font.prototype.glyphIndexToName = function(gid) { + if (!this.glyphNames.glyphIndexToName) { + return ''; + } + + return this.glyphNames.glyphIndexToName(gid); +}; + +// Retrieve the value of the kerning pair between the left glyph (or its index) +// and the right glyph (or its index). If no kerning pair is found, return 0. +// The kerning value gets added to the advance width when calculating the spacing +// between glyphs. +Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { + leftGlyph = leftGlyph.index || leftGlyph; + rightGlyph = rightGlyph.index || rightGlyph; + var gposKerning = this.getGposKerningValue; + return gposKerning ? gposKerning(leftGlyph, rightGlyph) : + (this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0); +}; + +// Helper function that invokes the given callback for each glyph in the given text. +// The callback gets `(glyph, x, y, fontSize, options)`. +Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { + if (!this.supported) { + return; + } + + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + options = options || {}; + var kerning = options.kerning === undefined ? true : options.kerning; + var fontScale = 1 / this.unitsPerEm * fontSize; + var glyphs = this.stringToGlyphs(text); + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs[i]; + callback(glyph, x, y, fontSize, options); + if (glyph.advanceWidth) { + x += glyph.advanceWidth * fontScale; + } + + if (kerning && i < glyphs.length - 1) { + var kerningValue = this.getKerningValue(glyph, glyphs[i + 1]); + x += kerningValue * fontScale; + } + } +}; + +// Create a Path object that represents the given text. +// +// text - The text to create. +// x - Horizontal position of the beginning of the text. (default: 0) +// y - Vertical position of the *baseline* of the text. (default: 0) +// fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) +// Options is an optional object that contains: +// - kerning - Whether to take kerning information into account. (default: true) +// +// Returns a Path object. +Font.prototype.getPath = function(text, x, y, fontSize, options) { + var fullPath = new path.Path(); + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize); + fullPath.extend(glyphPath); + }); + + return fullPath; +}; + +// Draw the text on the given drawing context. +// +// ctx - A 2D drawing context, like Canvas. +// text - The text to create. +// x - Horizontal position of the beginning of the text. (default: 0) +// y - Vertical position of the *baseline* of the text. (default: 0) +// fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) +// Options is an optional object that contains: +// - kerning - Whether to take kerning information into account. (default: true) +Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { + this.getPath(text, x, y, fontSize, options).draw(ctx); +}; + +// Draw the points of all glyphs in the text. +// On-curve points will be drawn in blue, off-curve points will be drawn in red. +// +// ctx - A 2D drawing context, like Canvas. +// text - The text to create. +// x - Horizontal position of the beginning of the text. (default: 0) +// y - Vertical position of the *baseline* of the text. (default: 0) +// fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) +// Options is an optional object that contains: +// - kerning - Whether to take kerning information into account. (default: true) +Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawPoints(ctx, gX, gY, gFontSize); + }); +}; + +// Draw lines indicating important font measurements for all glyphs in the text. +// Black lines indicate the origin of the coordinate system (point 0,0). +// Blue lines indicate the glyph bounding box. +// Green line indicates the advance width of the glyph. +// +// ctx - A 2D drawing context, like Canvas. +// text - The text to create. +// x - Horizontal position of the beginning of the text. (default: 0) +// y - Vertical position of the *baseline* of the text. (default: 0) +// fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) +// Options is an optional object that contains: +// - kerning - Whether to take kerning information into account. (default: true) +Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawMetrics(ctx, gX, gY, gFontSize); + }); +}; + +// Validate +Font.prototype.validate = function() { + var warnings = []; + var _this = this; + + function assert(predicate, message) { + if (!predicate) { + warnings.push(message); + } + } + + function assertStringAttribute(attrName) { + assert(_this[attrName] && _this[attrName].trim().length > 0, 'No ' + attrName + ' specified.'); + } + + // Identification information + assertStringAttribute('familyName'); + assertStringAttribute('weightName'); + assertStringAttribute('manufacturer'); + assertStringAttribute('copyright'); + assertStringAttribute('version'); + + // Dimension information + assert(this.unitsPerEm > 0, 'No unitsPerEm specified.'); +}; + +// Convert the font object to a SFNT data structure. +// This structure contains all the necessary tables and metadata to create a binary OTF file. +Font.prototype.toTables = function() { + return sfnt.fontToTable(this); +}; + +Font.prototype.toBuffer = function() { + var sfntTable = this.toTables(); + var bytes = sfntTable.encode(); + var buffer = new ArrayBuffer(bytes.length); + var intArray = new Uint8Array(buffer); + for (var i = 0; i < bytes.length; i++) { + intArray[i] = bytes[i]; + } + + return buffer; +}; + +// Initiate a download of the OpenType font. +Font.prototype.download = function() { + var fileName = this.familyName.replace(/\s/g, '') + '-' + this.styleName + '.otf'; + var buffer = this.toBuffer(); + + window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; + window.requestFileSystem(window.TEMPORARY, buffer.byteLength, function(fs) { + fs.root.getFile(fileName, {create: true}, function(fileEntry) { + fileEntry.createWriter(function(writer) { + var dataView = new DataView(buffer); + var blob = new Blob([dataView], {type: 'font/opentype'}); + writer.write(blob); + + writer.addEventListener('writeend', function() { + // Navigating to the file will download it. + location.href = fileEntry.toURL(); + }, false); + }); + }); + }, + + function(err) { + throw err; + }); +}; + +exports.Font = Font; + +},{"./encoding":4,"./glyphset":7,"./path":10,"./tables/sfnt":25}],6:[function(_dereq_,module,exports){ +// The Glyph object + +'use strict'; + +var check = _dereq_('./check'); +var draw = _dereq_('./draw'); +var path = _dereq_('./path'); + +function getPathDefinition(glyph, path) { + var _path = path || { commands: [] }; + return { + configurable: true, + + get: function() { + if (typeof _path === 'function') { + _path = _path(); + } + + return _path; + }, + + set: function(p) { + _path = p; + } + }; +} + +// A Glyph is an individual mark that often corresponds to a character. +// Some glyphs, such as ligatures, are a combination of many characters. +// Glyphs are the basic building blocks of a font. +// +// The `Glyph` class contains utility methods for drawing the path and its points. +function Glyph(options) { + // By putting all the code on a prototype function (which is only declared once) + // we reduce the memory requirements for larger fonts by some 2% + this.bindConstructorValues(options); +} + +Glyph.prototype.bindConstructorValues = function(options) { + this.index = options.index || 0; + + // These three values cannnot be deferred for memory optimization: + this.name = options.name || null; + this.unicode = options.unicode || undefined; + this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; + + // But by binding these values only when necessary, we reduce can + // the memory requirements by almost 3% for larger fonts. + if (options.xMin) { + this.xMin = options.xMin; + } + + if (options.yMin) { + this.yMin = options.yMin; + } + + if (options.xMax) { + this.xMax = options.xMax; + } + + if (options.yMax) { + this.yMax = options.yMax; + } + + if (options.advanceWidth) { + this.advanceWidth = options.advanceWidth; + } + + // The path for a glyph is the most memory intensive, and is bound as a value + // with a getter/setter to ensure we actually do path parsing only once the + // path is actually needed by anything. + Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); +}; + +Glyph.prototype.addUnicode = function(unicode) { + if (this.unicodes.length === 0) { + this.unicode = unicode; + } + + this.unicodes.push(unicode); +}; + +// Convert the glyph to a Path we can draw on a drawing context. +// +// x - Horizontal position of the glyph. (default: 0) +// y - Vertical position of the *baseline* of the glyph. (default: 0) +// fontSize - Font size, in pixels (default: 72). +Glyph.prototype.getPath = function(x, y, fontSize) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + var scale = 1 / this.path.unitsPerEm * fontSize; + var p = new path.Path(); + var commands = this.path.commands; + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type === 'M') { + p.moveTo(x + (cmd.x * scale), y + (-cmd.y * scale)); + } else if (cmd.type === 'L') { + p.lineTo(x + (cmd.x * scale), y + (-cmd.y * scale)); + } else if (cmd.type === 'Q') { + p.quadraticCurveTo(x + (cmd.x1 * scale), y + (-cmd.y1 * scale), + x + (cmd.x * scale), y + (-cmd.y * scale)); + } else if (cmd.type === 'C') { + p.curveTo(x + (cmd.x1 * scale), y + (-cmd.y1 * scale), + x + (cmd.x2 * scale), y + (-cmd.y2 * scale), + x + (cmd.x * scale), y + (-cmd.y * scale)); + } else if (cmd.type === 'Z') { + p.closePath(); + } + } + + return p; +}; + +// Split the glyph into contours. +// This function is here for backwards compatibility, and to +// provide raw access to the TrueType glyph outlines. +Glyph.prototype.getContours = function() { + if (this.points === undefined) { + return []; + } + + var contours = []; + var currentContour = []; + for (var i = 0; i < this.points.length; i += 1) { + var pt = this.points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +}; + +// Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. +Glyph.prototype.getMetrics = function() { + var commands = this.path.commands; + var xCoords = []; + var yCoords = []; + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type !== 'Z') { + xCoords.push(cmd.x); + yCoords.push(cmd.y); + } + + if (cmd.type === 'Q' || cmd.type === 'C') { + xCoords.push(cmd.x1); + yCoords.push(cmd.y1); + } + + if (cmd.type === 'C') { + xCoords.push(cmd.x2); + yCoords.push(cmd.y2); + } + } + + var metrics = { + xMin: Math.min.apply(null, xCoords), + yMin: Math.min.apply(null, yCoords), + xMax: Math.max.apply(null, xCoords), + yMax: Math.max.apply(null, yCoords), + leftSideBearing: 0 + }; + metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); + return metrics; +}; + +// Draw the glyph on the given context. +// +// ctx - The drawing context. +// x - Horizontal position of the glyph. (default: 0) +// y - Vertical position of the *baseline* of the glyph. (default: 0) +// fontSize - Font size, in pixels (default: 72). +Glyph.prototype.draw = function(ctx, x, y, fontSize) { + this.getPath(x, y, fontSize).draw(ctx); +}; + +// Draw the points of the glyph. +// On-curve points will be drawn in blue, off-curve points will be drawn in red. +// +// ctx - The drawing context. +// x - Horizontal position of the glyph. (default: 0) +// y - Vertical position of the *baseline* of the glyph. (default: 0) +// fontSize - Font size, in pixels (default: 72). +Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { + + function drawCircles(l, x, y, scale) { + var PI_SQ = Math.PI * 2; + ctx.beginPath(); + for (var j = 0; j < l.length; j += 1) { + ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); + ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, PI_SQ, false); + } + + ctx.closePath(); + ctx.fill(); + } + + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + var scale = 1 / this.path.unitsPerEm * fontSize; + + var blueCircles = []; + var redCircles = []; + var path = this.path; + for (var i = 0; i < path.commands.length; i += 1) { + var cmd = path.commands[i]; + if (cmd.x !== undefined) { + blueCircles.push({x: cmd.x, y: -cmd.y}); + } + + if (cmd.x1 !== undefined) { + redCircles.push({x: cmd.x1, y: -cmd.y1}); + } + + if (cmd.x2 !== undefined) { + redCircles.push({x: cmd.x2, y: -cmd.y2}); + } + } + + ctx.fillStyle = 'blue'; + drawCircles(blueCircles, x, y, scale); + ctx.fillStyle = 'red'; + drawCircles(redCircles, x, y, scale); +}; + +// Draw lines indicating important font measurements. +// Black lines indicate the origin of the coordinate system (point 0,0). +// Blue lines indicate the glyph bounding box. +// Green line indicates the advance width of the glyph. +// +// ctx - The drawing context. +// x - Horizontal position of the glyph. (default: 0) +// y - Vertical position of the *baseline* of the glyph. (default: 0) +// fontSize - Font size, in pixels (default: 72). +Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { + var scale; + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + scale = 1 / this.path.unitsPerEm * fontSize; + ctx.lineWidth = 1; + + // Draw the origin + ctx.strokeStyle = 'black'; + draw.line(ctx, x, -10000, x, 10000); + draw.line(ctx, -10000, y, 10000, y); + + // This code is here due to memory optimization: by not using + // defaults in the constructor, we save a notable amount of memory. + var xMin = this.xMin || 0; + var yMin = this.yMin || 0; + var xMax = this.xMax || 0; + var yMax = this.yMax || 0; + var advanceWidth = this.advanceWidth || 0; + + // Draw the glyph box + ctx.strokeStyle = 'blue'; + draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); + draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); + draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); + draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); + + // Draw the advance width + ctx.strokeStyle = 'green'; + draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); +}; + +exports.Glyph = Glyph; + +},{"./check":2,"./draw":3,"./path":10}],7:[function(_dereq_,module,exports){ +// The GlyphSet object + +'use strict'; + +var _glyph = _dereq_('./glyph'); + +// A GlyphSet represents all glyphs available in the font, but modelled using +// a deferred glyph loader, for retrieving glyphs only once they are absolutely +// necessary, to keep the memory footprint down. +function GlyphSet(font, glyphs) { + this.font = font; + this.glyphs = {}; + if (Array.isArray(glyphs)) { + for (var i = 0; i < glyphs.length; i++) { + this.glyphs[i] = glyphs[i]; + } + } + + this.length = (glyphs && glyphs.length) || 0; +} + +GlyphSet.prototype.get = function(index) { + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + + return this.glyphs[index]; +}; + +GlyphSet.prototype.push = function(index, loader) { + this.glyphs[index] = loader; + this.length++; +}; + +function glyphLoader(font, index) { + return new _glyph.Glyph({index: index, font: font}); +} + +/** + * Generate a stub glyph that can be filled with all metadata *except* + * the "points" and "path" properties, which must be loaded only once + * the glyph's path is actually requested for text shaping. + */ + +function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { + return function() { + var glyph = new _glyph.Glyph({index: index, font: font}); + + glyph.path = function() { + parseGlyph(glyph, data, position); + var path = buildPath(font.glyphs, glyph); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + return glyph; + }; +} + +function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { + return function() { + var glyph = new _glyph.Glyph({index: index, font: font}); + + glyph.path = function() { + var path = parseCFFCharstring(font, glyph, charstring); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + return glyph; + }; +} + +exports.GlyphSet = GlyphSet; +exports.glyphLoader = glyphLoader; +exports.ttfGlyphLoader = ttfGlyphLoader; +exports.cffGlyphLoader = cffGlyphLoader; + +},{"./glyph":6}],8:[function(_dereq_,module,exports){ +// opentype.js +// https://github.com/nodebox/opentype.js +// (c) 2015 Frederik De Bleser +// opentype.js may be freely distributed under the MIT license. + +/* global ArrayBuffer, DataView, Uint8Array, XMLHttpRequest */ + +'use strict'; + +var encoding = _dereq_('./encoding'); +var _font = _dereq_('./font'); +var glyph = _dereq_('./glyph'); +var parse = _dereq_('./parse'); +var path = _dereq_('./path'); + +var cmap = _dereq_('./tables/cmap'); +var cff = _dereq_('./tables/cff'); +var glyf = _dereq_('./tables/glyf'); +var gpos = _dereq_('./tables/gpos'); +var head = _dereq_('./tables/head'); +var hhea = _dereq_('./tables/hhea'); +var hmtx = _dereq_('./tables/hmtx'); +var kern = _dereq_('./tables/kern'); +var loca = _dereq_('./tables/loca'); +var maxp = _dereq_('./tables/maxp'); +var _name = _dereq_('./tables/name'); +var os2 = _dereq_('./tables/os2'); +var post = _dereq_('./tables/post'); + +// File loaders ///////////////////////////////////////////////////////// + +// Convert a Node.js Buffer to an ArrayBuffer +function toArrayBuffer(buffer) { + var arrayBuffer = new ArrayBuffer(buffer.length); + var data = new Uint8Array(arrayBuffer); + for (var i = 0; i < buffer.length; i += 1) { + data[i] = buffer[i]; + } + + return arrayBuffer; +} + +function loadFromFile(path, callback) { + var fs = _dereq_('fs'); + fs.readFile(path, function(err, buffer) { + if (err) { + return callback(err.message); + } + + callback(null, toArrayBuffer(buffer)); + }); +} + +function loadFromUrl(url, callback) { + var request = new XMLHttpRequest(); + request.open('get', url, true); + request.responseType = 'arraybuffer'; + request.onload = function() { + if (request.status !== 200) { + return callback('Font could not be loaded: ' + request.statusText); + } + + return callback(null, request.response); + }; + + request.send(); +} + +// Public API /////////////////////////////////////////////////////////// + +// Parse the OpenType file data (as an ArrayBuffer) and return a Font object. +// If the file could not be parsed (most likely because it contains Postscript outlines) +// we return an empty Font object with the `supported` flag set to `false`. +function parseBuffer(buffer) { + var indexToLocFormat; + var hmtxOffset; + var glyfOffset; + var locaOffset; + var cffOffset; + var kernOffset; + var gposOffset; + + // OpenType fonts use big endian byte ordering. + // We can't rely on typed array view types, because they operate with the endianness of the host computer. + // Instead we use DataViews where we can specify endianness. + + var font = new _font.Font(); + var data = new DataView(buffer, 0); + + var version = parse.getFixed(data, 0); + if (version === 1.0) { + font.outlinesFormat = 'truetype'; + } else { + version = parse.getTag(data, 0); + if (version === 'OTTO') { + font.outlinesFormat = 'cff'; + } else { + throw new Error('Unsupported OpenType version ' + version); + } + } + + var numTables = parse.getUShort(data, 4); + + // Offset into the table records. + var p = 12; + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var offset = parse.getULong(data, p + 8); + switch (tag) { + case 'cmap': + font.tables.cmap = cmap.parse(data, offset); + font.encoding = new encoding.CmapEncoding(font.tables.cmap); + if (!font.encoding) { + font.supported = false; + } + + break; + case 'head': + font.tables.head = head.parse(data, offset); + font.unitsPerEm = font.tables.head.unitsPerEm; + indexToLocFormat = font.tables.head.indexToLocFormat; + break; + case 'hhea': + font.tables.hhea = hhea.parse(data, offset); + font.ascender = font.tables.hhea.ascender; + font.descender = font.tables.hhea.descender; + font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; + break; + case 'hmtx': + hmtxOffset = offset; + break; + case 'maxp': + font.tables.maxp = maxp.parse(data, offset); + font.numGlyphs = font.tables.maxp.numGlyphs; + break; + case 'name': + font.tables.name = _name.parse(data, offset); + font.familyName = font.tables.name.fontFamily; + font.styleName = font.tables.name.fontSubfamily; + break; + case 'OS/2': + font.tables.os2 = os2.parse(data, offset); + break; + case 'post': + font.tables.post = post.parse(data, offset); + font.glyphNames = new encoding.GlyphNames(font.tables.post); + break; + case 'glyf': + glyfOffset = offset; + break; + case 'loca': + locaOffset = offset; + break; + case 'CFF ': + cffOffset = offset; + break; + case 'kern': + kernOffset = offset; + break; + case 'GPOS': + gposOffset = offset; + break; + } + p += 16; + } + + if (glyfOffset && locaOffset) { + var shortVersion = indexToLocFormat === 0; + var locaTable = loca.parse(data, locaOffset, font.numGlyphs, shortVersion); + font.glyphs = glyf.parse(data, glyfOffset, locaTable, font); + hmtx.parse(data, hmtxOffset, font.numberOfHMetrics, font.numGlyphs, font.glyphs); + encoding.addGlyphNames(font); + } else if (cffOffset) { + cff.parse(data, cffOffset, font); + encoding.addGlyphNames(font); + } else { + font.supported = false; + } + + if (font.supported) { + if (kernOffset) { + font.kerningPairs = kern.parse(data, kernOffset); + } else { + font.kerningPairs = {}; + } + + if (gposOffset) { + gpos.parse(data, gposOffset, font); + } + } + + return font; +} + +// Asynchronously load the font from a URL or a filesystem. When done, call the callback +// with two arguments `(err, font)`. The `err` will be null on success, +// the `font` is a Font object. +// +// We use the node.js callback convention so that +// opentype.js can integrate with frameworks like async.js. +function load(url, callback) { + var isNode = typeof window === 'undefined'; + var loadFn = isNode ? loadFromFile : loadFromUrl; + loadFn(url, function(err, arrayBuffer) { + if (err) { + return callback(err); + } + + var font = parseBuffer(arrayBuffer); + if (!font.supported) { + return callback('Font is not supported (is this a Postscript font?)'); + } + + return callback(null, font); + }); +} + +exports._parse = parse; +exports.Font = _font.Font; +exports.Glyph = glyph.Glyph; +exports.Path = path.Path; +exports.parse = parseBuffer; +exports.load = load; + +},{"./encoding":4,"./font":5,"./glyph":6,"./parse":9,"./path":10,"./tables/cff":12,"./tables/cmap":13,"./tables/glyf":14,"./tables/gpos":15,"./tables/head":16,"./tables/hhea":17,"./tables/hmtx":18,"./tables/kern":19,"./tables/loca":20,"./tables/maxp":21,"./tables/name":22,"./tables/os2":23,"./tables/post":24,"fs":1}],9:[function(_dereq_,module,exports){ +// Parsing utility functions + +'use strict'; + +// Retrieve an unsigned byte from the DataView. +exports.getByte = function getByte(dataView, offset) { + return dataView.getUint8(offset); +}; + +exports.getCard8 = exports.getByte; + +// Retrieve an unsigned 16-bit short from the DataView. +// The value is stored in big endian. +exports.getUShort = function(dataView, offset) { + return dataView.getUint16(offset, false); +}; + +exports.getCard16 = exports.getUShort; + +// Retrieve a signed 16-bit short from the DataView. +// The value is stored in big endian. +exports.getShort = function(dataView, offset) { + return dataView.getInt16(offset, false); +}; + +// Retrieve an unsigned 32-bit long from the DataView. +// The value is stored in big endian. +exports.getULong = function(dataView, offset) { + return dataView.getUint32(offset, false); +}; + +// Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. +// The value is stored in big endian. +exports.getFixed = function(dataView, offset) { + var decimal = dataView.getInt16(offset, false); + var fraction = dataView.getUint16(offset + 2, false); + return decimal + fraction / 65535; +}; + +// Retrieve a 4-character tag from the DataView. +// Tags are used to identify tables. +exports.getTag = function(dataView, offset) { + var tag = ''; + for (var i = offset; i < offset + 4; i += 1) { + tag += String.fromCharCode(dataView.getInt8(i)); + } + + return tag; +}; + +// Retrieve an offset from the DataView. +// Offsets are 1 to 4 bytes in length, depending on the offSize argument. +exports.getOffset = function(dataView, offset, offSize) { + var v = 0; + for (var i = 0; i < offSize; i += 1) { + v <<= 8; + v += dataView.getUint8(offset + i); + } + + return v; +}; + +// Retrieve a number of bytes from start offset to the end offset from the DataView. +exports.getBytes = function(dataView, startOffset, endOffset) { + var bytes = []; + for (var i = startOffset; i < endOffset; i += 1) { + bytes.push(dataView.getUint8(i)); + } + + return bytes; +}; + +// Convert the list of bytes to a string. +exports.bytesToString = function(bytes) { + var s = ''; + for (var i = 0; i < bytes.length; i += 1) { + s += String.fromCharCode(bytes[i]); + } + + return s; +}; + +var typeOffsets = { + byte: 1, + uShort: 2, + short: 2, + uLong: 4, + fixed: 4, + longDateTime: 8, + tag: 4 +}; + +// A stateful parser that changes the offset whenever a value is retrieved. +// The data is a DataView. +function Parser(data, offset) { + this.data = data; + this.offset = offset; + this.relativeOffset = 0; +} + +Parser.prototype.parseByte = function() { + var v = this.data.getUint8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; + +Parser.prototype.parseChar = function() { + var v = this.data.getInt8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; + +Parser.prototype.parseCard8 = Parser.prototype.parseByte; + +Parser.prototype.parseUShort = function() { + var v = this.data.getUint16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseCard16 = Parser.prototype.parseUShort; +Parser.prototype.parseSID = Parser.prototype.parseUShort; +Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; + +Parser.prototype.parseShort = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseF2Dot14 = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseULong = function() { + var v = exports.getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseFixed = function() { + var v = exports.getFixed(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseOffset16List = +Parser.prototype.parseUShortList = function(count) { + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = exports.getUShort(dataView, offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return offsets; +}; + +Parser.prototype.parseString = function(length) { + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + var string = ''; + this.relativeOffset += length; + for (var i = 0; i < length; i++) { + string += String.fromCharCode(dataView.getUint8(offset + i)); + } + + return string; +}; + +Parser.prototype.parseTag = function() { + return this.parseString(4); +}; + +// LONGDATETIME is a 64-bit integer. +// JavaScript and unix timestamps traditionally use 32 bits, so we +// only take the last 32 bits. +Parser.prototype.parseLongDateTime = function() { + var v = exports.getULong(this.data, this.offset + this.relativeOffset + 4); + this.relativeOffset += 8; + return v; +}; + +Parser.prototype.parseFixed = function() { + var v = exports.getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v / 65536; +}; + +Parser.prototype.parseVersion = function() { + var major = exports.getUShort(this.data, this.offset + this.relativeOffset); + + // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 + // This returns the correct number if minor = 0xN000 where N is 0-9 + var minor = exports.getUShort(this.data, this.offset + this.relativeOffset + 2); + this.relativeOffset += 4; + return major + minor / 0x1000 / 10; +}; + +Parser.prototype.skip = function(type, amount) { + if (amount === undefined) { + amount = 1; + } + + this.relativeOffset += typeOffsets[type] * amount; +}; + +exports.Parser = Parser; + +},{}],10:[function(_dereq_,module,exports){ +// Geometric objects + +'use strict'; + +// A bézier path containing a set of path commands similar to a SVG path. +// Paths can be drawn on a context using `draw`. +function Path() { + this.commands = []; + this.fill = 'black'; + this.stroke = null; + this.strokeWidth = 1; +} + +Path.prototype.moveTo = function(x, y) { + this.commands.push({ + type: 'M', + x: x, + y: y + }); +}; + +Path.prototype.lineTo = function(x, y) { + this.commands.push({ + type: 'L', + x: x, + y: y + }); +}; + +Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { + this.commands.push({ + type: 'C', + x1: x1, + y1: y1, + x2: x2, + y2: y2, + x: x, + y: y + }); +}; + +Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { + this.commands.push({ + type: 'Q', + x1: x1, + y1: y1, + x: x, + y: y + }); +}; + +Path.prototype.close = Path.prototype.closePath = function() { + this.commands.push({ + type: 'Z' + }); +}; + +// Add the given path or list of commands to the commands of this path. +Path.prototype.extend = function(pathOrCommands) { + if (pathOrCommands.commands) { + pathOrCommands = pathOrCommands.commands; + } + + Array.prototype.push.apply(this.commands, pathOrCommands); +}; + +// Draw the path to a 2D context. +Path.prototype.draw = function(ctx) { + ctx.beginPath(); + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + ctx.moveTo(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + ctx.lineTo(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + ctx.closePath(); + } + } + + if (this.fill) { + ctx.fillStyle = this.fill; + ctx.fill(); + } + + if (this.stroke) { + ctx.strokeStyle = this.stroke; + ctx.lineWidth = this.strokeWidth; + ctx.stroke(); + } +}; + +// Convert the Path to a string of path data instructions +// See http://www.w3.org/TR/SVG/paths.html#PathData +// Parameters: +// - decimalPlaces: The amount of decimal places for floating-point values (default: 2) +Path.prototype.toPathData = function(decimalPlaces) { + decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; + + function floatToString(v) { + if (Math.round(v) === v) { + return '' + Math.round(v); + } else { + return v.toFixed(decimalPlaces); + } + } + + function packValues() { + var s = ''; + for (var i = 0; i < arguments.length; i += 1) { + var v = arguments[i]; + if (v >= 0 && i > 0) { + s += ' '; + } + + s += floatToString(v); + } + + return s; + } + + var d = ''; + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + d += 'M' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + d += 'L' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + d += 'Z'; + } + } + + return d; +}; + +// Convert the path to a SVG <path> element, as a string. +// Parameters: +// - decimalPlaces: The amount of decimal places for floating-point values (default: 2) +Path.prototype.toSVG = function(decimalPlaces) { + var svg = '<path d="'; + svg += this.toPathData(decimalPlaces); + svg += '"'; + if (this.fill & this.fill !== 'black') { + if (this.fill === null) { + svg += ' fill="none"'; + } else { + svg += ' fill="' + this.fill + '"'; + } + } + + if (this.stroke) { + svg += ' stroke="' + this.stroke + '" stroke-width="' + this.strokeWidth + '"'; + } + + svg += '/>'; + return svg; +}; + +exports.Path = Path; + +},{}],11:[function(_dereq_,module,exports){ +// Table metadata + +'use strict'; + +var check = _dereq_('./check'); +var encode = _dereq_('./types').encode; +var sizeOf = _dereq_('./types').sizeOf; + +function Table(tableName, fields, options) { + var i; + for (i = 0; i < fields.length; i += 1) { + var field = fields[i]; + this[field.name] = field.value; + } + + this.tableName = tableName; + this.fields = fields; + if (options) { + var optionKeys = Object.keys(options); + for (i = 0; i < optionKeys.length; i += 1) { + var k = optionKeys[i]; + var v = options[k]; + if (this[k] !== undefined) { + this[k] = v; + } + } + } +} + +Table.prototype.sizeOf = function() { + var v = 0; + for (var i = 0; i < this.fields.length; i += 1) { + var field = this.fields[i]; + var value = this[field.name]; + if (value === undefined) { + value = field.value; + } + + if (typeof value.sizeOf === 'function') { + v += value.sizeOf(); + } else { + var sizeOfFunction = sizeOf[field.type]; + check.assert(typeof sizeOfFunction === 'function', 'Could not find sizeOf function for field' + field.name); + v += sizeOfFunction(value); + } + } + + return v; +}; + +Table.prototype.encode = function() { + return encode.TABLE(this); +}; + +exports.Table = Table; + +},{"./check":2,"./types":26}],12:[function(_dereq_,module,exports){ +// The `CFF` table contains the glyph outlines in PostScript format. +// https://www.microsoft.com/typography/OTSPEC/cff.htm +// http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/cff.pdf +// http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/type2.pdf + +'use strict'; + +var encoding = _dereq_('../encoding'); +var glyphset = _dereq_('../glyphset'); +var parse = _dereq_('../parse'); +var path = _dereq_('../path'); +var table = _dereq_('../table'); + +// Custom equals function that can also check lists. +function equals(a, b) { + if (a === b) { + return true; + } else if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + + for (var i = 0; i < a.length; i += 1) { + if (!equals(a[i], b[i])) { + return false; + } + } + + return true; + } else { + return false; + } +} + +// Parse a `CFF` INDEX array. +// An index array consists of a list of offsets, then a list of objects at those offsets. +function parseCFFIndex(data, start, conversionFn) { + //var i, objectOffset, endOffset; + var offsets = []; + var objects = []; + var count = parse.getCard16(data, start); + var i; + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + for (i = 0; i < offsets.length - 1; i += 1) { + var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); + if (conversionFn) { + value = conversionFn(value); + } + + objects.push(value); + } + + return {objects: objects, startOffset: start, endOffset: endOffset}; +} + +// Parse a `CFF` DICT real value. +function parseFloatOperand(parser) { + var s = ''; + var eof = 15; + var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; + while (true) { + var b = parser.parseByte(); + var n1 = b >> 4; + var n2 = b & 15; + + if (n1 === eof) { + break; + } + + s += lookup[n1]; + + if (n2 === eof) { + break; + } + + s += lookup[n2]; + } + + return parseFloat(s); +} + +// Parse a `CFF` DICT operand. +function parseOperand(parser, b0) { + var b1; + var b2; + var b3; + var b4; + if (b0 === 28) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + return b1 << 8 | b2; + } + + if (b0 === 29) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + b3 = parser.parseByte(); + b4 = parser.parseByte(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } + + if (b0 === 30) { + return parseFloatOperand(parser); + } + + if (b0 >= 32 && b0 <= 246) { + return b0 - 139; + } + + if (b0 >= 247 && b0 <= 250) { + b1 = parser.parseByte(); + return (b0 - 247) * 256 + b1 + 108; + } + + if (b0 >= 251 && b0 <= 254) { + b1 = parser.parseByte(); + return -(b0 - 251) * 256 - b1 - 108; + } + + throw new Error('Invalid b0 ' + b0); +} + +// Convert the entries returned by `parseDict` to a proper dictionary. +// If a value is a list of one, it is unpacked. +function entriesToObject(entries) { + var o = {}; + for (var i = 0; i < entries.length; i += 1) { + var key = entries[i][0]; + var values = entries[i][1]; + var value; + if (values.length === 1) { + value = values[0]; + } else { + value = values; + } + + if (o.hasOwnProperty(key)) { + throw new Error('Object ' + o + ' already has key ' + key); + } + + o[key] = value; + } + + return o; +} + +// Parse a `CFF` DICT object. +// A dictionary contains key-value pairs in a compact tokenized format. +function parseCFFDict(data, start, size) { + start = start !== undefined ? start : 0; + var parser = new parse.Parser(data, start); + var entries = []; + var operands = []; + size = size !== undefined ? size : data.length; + + while (parser.relativeOffset < size) { + var op = parser.parseByte(); + + // The first byte for each dict item distinguishes between operator (key) and operand (value). + // Values <= 21 are operators. + if (op <= 21) { + // Two-byte operators have an initial escape byte of 12. + if (op === 12) { + op = 1200 + parser.parseByte(); + } + + entries.push([op, operands]); + operands = []; + } else { + // Since the operands (values) come before the operators (keys), we store all operands in a list + // until we encounter an operator. + operands.push(parseOperand(parser, op)); + } + } + + return entriesToObject(entries); +} + +// Given a String Index (SID), return the value of the string. +// Strings below index 392 are standard CFF strings and are not encoded in the font. +function getCFFString(strings, index) { + if (index <= 390) { + index = encoding.cffStandardStrings[index]; + } else { + index = strings[index - 391]; + } + + return index; +} + +// Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. +// This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. +function interpretDict(dict, meta, strings) { + var newDict = {}; + + // Because we also want to include missing values, we start out from the meta list + // and lookup values in the dict. + for (var i = 0; i < meta.length; i += 1) { + var m = meta[i]; + var value = dict[m.op]; + if (value === undefined) { + value = m.value !== undefined ? m.value : null; + } + + if (m.type === 'SID') { + value = getCFFString(strings, value); + } + + newDict[m.name] = value; + } + + return newDict; +} + +// Parse the CFF header. +function parseCFFHeader(data, start) { + var header = {}; + header.formatMajor = parse.getCard8(data, start); + header.formatMinor = parse.getCard8(data, start + 1); + header.size = parse.getCard8(data, start + 2); + header.offsetSize = parse.getCard8(data, start + 3); + header.startOffset = start; + header.endOffset = start + 4; + return header; +} + +var TOP_DICT_META = [ + {name: 'version', op: 0, type: 'SID'}, + {name: 'notice', op: 1, type: 'SID'}, + {name: 'copyright', op: 1200, type: 'SID'}, + {name: 'fullName', op: 2, type: 'SID'}, + {name: 'familyName', op: 3, type: 'SID'}, + {name: 'weight', op: 4, type: 'SID'}, + {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, + {name: 'italicAngle', op: 1202, type: 'number', value: 0}, + {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, + {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, + {name: 'paintType', op: 1205, type: 'number', value: 0}, + {name: 'charstringType', op: 1206, type: 'number', value: 2}, + {name: 'fontMatrix', op: 1207, type: ['real', 'real', 'real', 'real', 'real', 'real'], value: [0.001, 0, 0, 0.001, 0, 0]}, + {name: 'uniqueId', op: 13, type: 'number'}, + {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, + {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, + {name: 'xuid', op: 14, type: [], value: null}, + {name: 'charset', op: 15, type: 'offset', value: 0}, + {name: 'encoding', op: 16, type: 'offset', value: 0}, + {name: 'charStrings', op: 17, type: 'offset', value: 0}, + {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]} +]; + +var PRIVATE_DICT_META = [ + {name: 'subrs', op: 19, type: 'offset', value: 0}, + {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, + {name: 'nominalWidthX', op: 21, type: 'number', value: 0} +]; + +// Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. +// The top dictionary contains the essential metadata for the font, together with the private dictionary. +function parseCFFTopDict(data, strings) { + var dict = parseCFFDict(data, 0, data.byteLength); + return interpretDict(dict, TOP_DICT_META, strings); +} + +// Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. +function parseCFFPrivateDict(data, start, size, strings) { + var dict = parseCFFDict(data, start, size); + return interpretDict(dict, PRIVATE_DICT_META, strings); +} + +// Parse the CFF charset table, which contains internal names for all the glyphs. +// This function will return a list of glyph names. +// See Adobe TN #5176 chapter 13, "Charsets". +function parseCFFCharset(data, start, nGlyphs, strings) { + var i; + var sid; + var count; + var parser = new parse.Parser(data, start); + + // The .notdef glyph is not included, so subtract 1. + nGlyphs -= 1; + var charset = ['.notdef']; + + var format = parser.parseCard8(); + if (format === 0) { + for (i = 0; i < nGlyphs; i += 1) { + sid = parser.parseSID(); + charset.push(getCFFString(strings, sid)); + } + } else if (format === 1) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard8(); + for (i = 0; i <= count; i += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else if (format === 2) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard16(); + for (i = 0; i <= count; i += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else { + throw new Error('Unknown charset format ' + format); + } + + return charset; +} + +// Parse the CFF encoding data. Only one encoding can be specified per font. +// See Adobe TN #5176 chapter 12, "Encodings". +function parseCFFEncoding(data, start, charset) { + var i; + var code; + var enc = {}; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + var nCodes = parser.parseCard8(); + for (i = 0; i < nCodes; i += 1) { + code = parser.parseCard8(); + enc[code] = i; + } + } else if (format === 1) { + var nRanges = parser.parseCard8(); + code = 1; + for (i = 0; i < nRanges; i += 1) { + var first = parser.parseCard8(); + var nLeft = parser.parseCard8(); + for (var j = first; j <= first + nLeft; j += 1) { + enc[j] = code; + code += 1; + } + } + } else { + throw new Error('Unknown encoding format ' + format); + } + + return new encoding.CffEncoding(enc, charset); +} + +// Take in charstring code and return a Glyph object. +// The encoding is described in the Type 2 Charstring Format +// https://www.microsoft.com/typography/OTSPEC/charstr2.htm +function parseCFFCharstring(font, glyph, code) { + var c1x; + var c1y; + var c2x; + var c2y; + var p = new path.Path(); + var stack = []; + var nStems = 0; + var haveWidth = false; + var width = font.defaultWidthX; + var open = false; + var x = 0; + var y = 0; + + function newContour(x, y) { + if (open) { + p.closePath(); + } + + p.moveTo(x, y); + open = true; + } + + function parseStems() { + var hasWidthArg; + + // The number of stem operators on the stack is always even. + // If the value is uneven, that means a width is specified. + hasWidthArg = stack.length % 2 !== 0; + if (hasWidthArg && !haveWidth) { + width = stack.shift() + font.nominalWidthX; + } + + nStems += stack.length >> 1; + stack.length = 0; + haveWidth = true; + } + + function parse(code) { + var b1; + var b2; + var b3; + var b4; + var codeIndex; + var subrCode; + var jpx; + var jpy; + var c3x; + var c3y; + var c4x; + var c4y; + + var i = 0; + while (i < code.length) { + var v = code[i]; + i += 1; + switch (v) { + case 1: // hstem + parseStems(); + break; + case 3: // vstem + parseStems(); + break; + case 4: // vmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + font.nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + newContour(x, y); + break; + case 5: // rlineto + while (stack.length > 0) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 6: // hlineto + while (stack.length > 0) { + x += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 7: // vlineto + while (stack.length > 0) { + y += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + x += stack.shift(); + p.lineTo(x, y); + } + + break; + case 8: // rrcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 10: // callsubr + codeIndex = stack.pop() + font.subrsBias; + subrCode = font.subrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 11: // return + return; + case 12: // flex operators + v = code[i]; + i += 1; + switch (v) { + case 35: // flex + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + y = c4y + stack.shift(); // dy6 + stack.shift(); // flex depth + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 34: // hflex + // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- + c1x = x + stack.shift(); // dx1 + c1y = y; // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = y; // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 36: // hflex1 + // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 37: // flex1 + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + if (Math.abs(c4x - x) > Math.abs(c4y - y)) { + x = c4x + stack.shift(); + } else { + y = c4y + stack.shift(); + } + + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + default: + console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); + stack.length = 0; + } + break; + case 14: // endchar + if (stack.length > 0 && !haveWidth) { + width = stack.shift() + font.nominalWidthX; + haveWidth = true; + } + + if (open) { + p.closePath(); + open = false; + } + + break; + case 18: // hstemhm + parseStems(); + break; + case 19: // hintmask + case 20: // cntrmask + parseStems(); + i += (nStems + 7) >> 3; + break; + case 21: // rmoveto + if (stack.length > 2 && !haveWidth) { + width = stack.shift() + font.nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + x += stack.pop(); + newContour(x, y); + break; + case 22: // hmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + font.nominalWidthX; + haveWidth = true; + } + + x += stack.pop(); + newContour(x, y); + break; + case 23: // vstemhm + parseStems(); + break; + case 24: // rcurveline + while (stack.length > 2) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + break; + case 25: // rlinecurve + while (stack.length > 6) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + break; + case 26: // vvcurveto + if (stack.length % 2) { + x += stack.shift(); + } + + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x; + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 27: // hhcurveto + if (stack.length % 2) { + y += stack.shift(); + } + + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y; + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 28: // shortint + b1 = code[i]; + b2 = code[i + 1]; + stack.push(((b1 << 24) | (b2 << 16)) >> 16); + i += 2; + break; + case 29: // callgsubr + codeIndex = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 30: // vhcurveto + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 31: // hvcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + default: + if (v < 32) { + console.log('Glyph ' + glyph.index + ': unknown operator ' + v); + } else if (v < 247) { + stack.push(v - 139); + } else if (v < 251) { + b1 = code[i]; + i += 1; + stack.push((v - 247) * 256 + b1 + 108); + } else if (v < 255) { + b1 = code[i]; + i += 1; + stack.push(-(v - 251) * 256 - b1 - 108); + } else { + b1 = code[i]; + b2 = code[i + 1]; + b3 = code[i + 2]; + b4 = code[i + 3]; + i += 4; + stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); + } + } + } + } + + parse(code); + + glyph.advanceWidth = width; + return p; +} + +// Subroutines are encoded using the negative half of the number space. +// See type 2 chapter 4.7 "Subroutine operators". +function calcCFFSubroutineBias(subrs) { + var bias; + if (subrs.length < 1240) { + bias = 107; + } else if (subrs.length < 33900) { + bias = 1131; + } else { + bias = 32768; + } + + return bias; +} + +// Parse the `CFF` table, which contains the glyph outlines in PostScript format. +function parseCFFTable(data, start, font) { + font.tables.cff = {}; + var header = parseCFFHeader(data, start); + var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); + var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); + var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); + var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); + font.gsubrs = globalSubrIndex.objects; + font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); + + var topDictData = new DataView(new Uint8Array(topDictIndex.objects[0]).buffer); + var topDict = parseCFFTopDict(topDictData, stringIndex.objects); + font.tables.cff.topDict = topDict; + + var privateDictOffset = start + topDict['private'][1]; + var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict['private'][0], stringIndex.objects); + font.defaultWidthX = privateDict.defaultWidthX; + font.nominalWidthX = privateDict.nominalWidthX; + + if (privateDict.subrs !== 0) { + var subrOffset = privateDictOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset); + font.subrs = subrIndex.objects; + font.subrsBias = calcCFFSubroutineBias(font.subrs); + } else { + font.subrs = []; + font.subrsBias = 0; + } + + // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. + var charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.objects.length; + + var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); + if (topDict.encoding === 0) { // Standard encoding + font.cffEncoding = new encoding.CffEncoding(encoding.cffStandardEncoding, charset); + } else if (topDict.encoding === 1) { // Expert encoding + font.cffEncoding = new encoding.CffEncoding(encoding.cffExpertEncoding, charset); + } else { + font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); + } + + // Prefer the CMAP encoding to the CFF encoding. + font.encoding = font.encoding || font.cffEncoding; + + font.glyphs = new glyphset.GlyphSet(font); + for (var i = 0; i < font.nGlyphs; i += 1) { + var charString = charStringsIndex.objects[i]; + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + } +} + +// Convert a string to a String ID (SID). +// The list of strings is modified in place. +function encodeString(s, strings) { + var sid; + + // Is the string in the CFF standard strings? + var i = encoding.cffStandardStrings.indexOf(s); + if (i >= 0) { + sid = i; + } + + // Is the string already in the string index? + i = strings.indexOf(s); + if (i >= 0) { + sid = i + encoding.cffStandardStrings.length; + } else { + sid = encoding.cffStandardStrings.length + strings.length; + strings.push(s); + } + + return sid; +} + +function makeHeader() { + return new table.Table('Header', [ + {name: 'major', type: 'Card8', value: 1}, + {name: 'minor', type: 'Card8', value: 0}, + {name: 'hdrSize', type: 'Card8', value: 4}, + {name: 'major', type: 'Card8', value: 1} + ]); +} + +function makeNameIndex(fontNames) { + var t = new table.Table('Name INDEX', [ + {name: 'names', type: 'INDEX', value: []} + ]); + t.names = []; + for (var i = 0; i < fontNames.length; i += 1) { + t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); + } + + return t; +} + +// Given a dictionary's metadata, create a DICT structure. +function makeDict(meta, attrs, strings) { + var m = {}; + for (var i = 0; i < meta.length; i += 1) { + var entry = meta[i]; + var value = attrs[entry.name]; + if (value !== undefined && !equals(value, entry.value)) { + if (entry.type === 'SID') { + value = encodeString(value, strings); + } + + m[entry.op] = {name: entry.name, type: entry.type, value: value}; + } + } + + return m; +} + +// The Top DICT houses the global font attributes. +function makeTopDict(attrs, strings) { + var t = new table.Table('Top DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(TOP_DICT_META, attrs, strings); + return t; +} + +function makeTopDictIndex(topDict) { + var t = new table.Table('Top DICT INDEX', [ + {name: 'topDicts', type: 'INDEX', value: []} + ]); + t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; + return t; +} + +function makeStringIndex(strings) { + var t = new table.Table('String INDEX', [ + {name: 'strings', type: 'INDEX', value: []} + ]); + t.strings = []; + for (var i = 0; i < strings.length; i += 1) { + t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); + } + + return t; +} + +function makeGlobalSubrIndex() { + // Currently we don't use subroutines. + return new table.Table('Global Subr INDEX', [ + {name: 'subrs', type: 'INDEX', value: []} + ]); +} + +function makeCharsets(glyphNames, strings) { + var t = new table.Table('Charsets', [ + {name: 'format', type: 'Card8', value: 0} + ]); + for (var i = 0; i < glyphNames.length; i += 1) { + var glyphName = glyphNames[i]; + var glyphSID = encodeString(glyphName, strings); + t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); + } + + return t; +} + +function glyphToOps(glyph) { + var ops = []; + var path = glyph.path; + ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); + var x = 0; + var y = 0; + for (var i = 0; i < path.commands.length; i += 1) { + var dx; + var dy; + var cmd = path.commands[i]; + if (cmd.type === 'Q') { + // CFF only supports bézier curves, so convert the quad to a bézier. + var _13 = 1 / 3; + var _23 = 2 / 3; + + // We're going to create a new command so we don't change the original path. + cmd = { + type: 'C', + x: cmd.x, + y: cmd.y, + x1: _13 * x + _23 * cmd.x1, + y1: _13 * y + _23 * cmd.y1, + x2: _13 * cmd.x + _23 * cmd.x1, + y2: _13 * cmd.y + _23 * cmd.y1 + }; + } + + if (cmd.type === 'M') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rmoveto', type: 'OP', value: 21}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'L') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rlineto', type: 'OP', value: 5}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'C') { + var dx1 = Math.round(cmd.x1 - x); + var dy1 = Math.round(cmd.y1 - y); + var dx2 = Math.round(cmd.x2 - cmd.x1); + var dy2 = Math.round(cmd.y2 - cmd.y1); + dx = Math.round(cmd.x - cmd.x2); + dy = Math.round(cmd.y - cmd.y2); + ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); + ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); + ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); + ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rrcurveto', type: 'OP', value: 8}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } + + // Contours are closed automatically. + + } + + ops.push({name: 'endchar', type: 'OP', value: 14}); + return ops; +} + +function makeCharStringsIndex(glyphs) { + var t = new table.Table('CharStrings INDEX', [ + {name: 'charStrings', type: 'INDEX', value: []} + ]); + + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var ops = glyphToOps(glyph); + t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); + } + + return t; +} + +function makePrivateDict(attrs, strings) { + var t = new table.Table('Private DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); + return t; +} + +function makePrivateDictIndex(privateDict) { + var t = new table.Table('Private DICT INDEX', [ + {name: 'privateDicts', type: 'INDEX', value: []} + ]); + t.privateDicts = [{name: 'privateDict_0', type: 'TABLE', value: privateDict}]; + return t; +} + +function makeCFFTable(glyphs, options) { + var t = new table.Table('CFF ', [ + {name: 'header', type: 'TABLE'}, + {name: 'nameIndex', type: 'TABLE'}, + {name: 'topDictIndex', type: 'TABLE'}, + {name: 'stringIndex', type: 'TABLE'}, + {name: 'globalSubrIndex', type: 'TABLE'}, + {name: 'charsets', type: 'TABLE'}, + {name: 'charStringsIndex', type: 'TABLE'}, + {name: 'privateDictIndex', type: 'TABLE'} + ]); + + var fontScale = 1 / options.unitsPerEm; + // We use non-zero values for the offsets so that the DICT encodes them. + // This is important because the size of the Top DICT plays a role in offset calculation, + // and the size shouldn't change after we've written correct offsets. + var attrs = { + version: options.version, + fullName: options.fullName, + familyName: options.familyName, + weight: options.weightName, + fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], + charset: 999, + encoding: 0, + charStrings: 999, + private: [0, 999] + }; + + var privateAttrs = {}; + + var glyphNames = []; + var glyph; + + // Skip first glyph (.notdef) + for (var i = 1; i < glyphs.length; i += 1) { + glyph = glyphs.get(i); + glyphNames.push(glyph.name); + } + + var strings = []; + + t.header = makeHeader(); + t.nameIndex = makeNameIndex([options.postScriptName]); + var topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + t.globalSubrIndex = makeGlobalSubrIndex(); + t.charsets = makeCharsets(glyphNames, strings); + t.charStringsIndex = makeCharStringsIndex(glyphs); + var privateDict = makePrivateDict(privateAttrs, strings); + t.privateDictIndex = makePrivateDictIndex(privateDict); + + // Needs to come at the end, to encode all custom strings used in the font. + t.stringIndex = makeStringIndex(strings); + + var startOffset = t.header.sizeOf() + + t.nameIndex.sizeOf() + + t.topDictIndex.sizeOf() + + t.stringIndex.sizeOf() + + t.globalSubrIndex.sizeOf(); + attrs.charset = startOffset; + + // We use the CFF standard encoding; proper encoding will be handled in cmap. + attrs.encoding = 0; + attrs.charStrings = attrs.charset + t.charsets.sizeOf(); + attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); + + // Recreate the Top DICT INDEX with the correct offsets. + topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + + return t; +} + +exports.parse = parseCFFTable; +exports.make = makeCFFTable; + +},{"../encoding":4,"../glyphset":7,"../parse":9,"../path":10,"../table":11}],13:[function(_dereq_,module,exports){ +// The `cmap` table stores the mappings from characters to glyphs. +// https://www.microsoft.com/typography/OTSPEC/cmap.htm + +'use strict'; + +var check = _dereq_('../check'); +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the `cmap` table. This table stores the mappings from characters to glyphs. +// There are many available formats, but we only support the Windows format 4. +// This function returns a `CmapEncoding` object or null if no supported format could be found. +function parseCmapTable(data, start) { + var i; + var cmap = {}; + cmap.version = parse.getUShort(data, start); + check.argument(cmap.version === 0, 'cmap table version should be 0.'); + + // The cmap table can contain many sub-tables, each with their own format. + // We're only interested in a "platform 3" table. This is a Windows format. + cmap.numTables = parse.getUShort(data, start + 2); + var offset = -1; + for (i = 0; i < cmap.numTables; i += 1) { + var platformId = parse.getUShort(data, start + 4 + (i * 8)); + var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); + if (platformId === 3 && (encodingId === 1 || encodingId === 0)) { + offset = parse.getULong(data, start + 4 + (i * 8) + 4); + break; + } + } + + if (offset === -1) { + // There is no cmap table in the font that we support, so return null. + // This font will be marked as unsupported. + return null; + } + + var p = new parse.Parser(data, start + offset); + cmap.format = p.parseUShort(); + check.argument(cmap.format === 4, 'Only format 4 cmap tables are supported.'); + + // Length in bytes of the sub-tables. + cmap.length = p.parseUShort(); + cmap.language = p.parseUShort(); + + // segCount is stored x 2. + var segCount; + cmap.segCount = segCount = p.parseUShort() >> 1; + + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + + // The "unrolled" mapping from character codes to glyph indices. + cmap.glyphIndexMap = {}; + + var endCountParser = new parse.Parser(data, start + offset + 14); + var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); + var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); + var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); + var glyphIndexOffset = start + offset + 16 + segCount * 8; + for (i = 0; i < segCount - 1; i += 1) { + var glyphIndex; + var endCount = endCountParser.parseUShort(); + var startCount = startCountParser.parseUShort(); + var idDelta = idDeltaParser.parseShort(); + var idRangeOffset = idRangeOffsetParser.parseUShort(); + for (var c = startCount; c <= endCount; c += 1) { + if (idRangeOffset !== 0) { + // The idRangeOffset is relative to the current position in the idRangeOffset array. + // Take the current offset in the idRangeOffset array. + glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); + + // Add the value of the idRangeOffset, which will move us into the glyphIndex array. + glyphIndexOffset += idRangeOffset; + + // Then add the character index of the current segment, multiplied by 2 for USHORTs. + glyphIndexOffset += (c - startCount) * 2; + glyphIndex = parse.getUShort(data, glyphIndexOffset); + if (glyphIndex !== 0) { + glyphIndex = (glyphIndex + idDelta) & 0xFFFF; + } + } else { + glyphIndex = (c + idDelta) & 0xFFFF; + } + + cmap.glyphIndexMap[c] = glyphIndex; + } + } + + return cmap; +} + +function addSegment(t, code, glyphIndex) { + t.segments.push({ + end: code, + start: code, + delta: -(code - glyphIndex), + offset: 0 + }); +} + +function addTerminatorSegment(t) { + t.segments.push({ + end: 0xFFFF, + start: 0xFFFF, + delta: 1, + offset: 0 + }); +} + +function makeCmapTable(glyphs) { + var i; + var t = new table.Table('cmap', [ + {name: 'version', type: 'USHORT', value: 0}, + {name: 'numTables', type: 'USHORT', value: 1}, + {name: 'platformID', type: 'USHORT', value: 3}, + {name: 'encodingID', type: 'USHORT', value: 1}, + {name: 'offset', type: 'ULONG', value: 12}, + {name: 'format', type: 'USHORT', value: 4}, + {name: 'length', type: 'USHORT', value: 0}, + {name: 'language', type: 'USHORT', value: 0}, + {name: 'segCountX2', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + + t.segments = []; + for (i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + addSegment(t, glyph.unicodes[j], i); + } + + t.segments = t.segments.sort(function(a, b) { + return a.start - b.start; + }); + } + + addTerminatorSegment(t); + + var segCount; + segCount = t.segments.length; + t.segCountX2 = segCount * 2; + t.searchRange = Math.pow(2, Math.floor(Math.log(segCount) / Math.log(2))) * 2; + t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); + t.rangeShift = t.segCountX2 - t.searchRange; + + // Set up parallel segment arrays. + var endCounts = []; + var startCounts = []; + var idDeltas = []; + var idRangeOffsets = []; + var glyphIds = []; + + for (i = 0; i < segCount; i += 1) { + var segment = t.segments[i]; + endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); + startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); + idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); + idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); + if (segment.glyphId !== undefined) { + glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); + } + } + + t.fields = t.fields.concat(endCounts); + t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); + t.fields = t.fields.concat(startCounts); + t.fields = t.fields.concat(idDeltas); + t.fields = t.fields.concat(idRangeOffsets); + t.fields = t.fields.concat(glyphIds); + + t.length = 14 + // Subtable header + endCounts.length * 2 + + 2 + // reservedPad + startCounts.length * 2 + + idDeltas.length * 2 + + idRangeOffsets.length * 2 + + glyphIds.length * 2; + + return t; +} + +exports.parse = parseCmapTable; +exports.make = makeCmapTable; + +},{"../check":2,"../parse":9,"../table":11}],14:[function(_dereq_,module,exports){ +// The `glyf` table describes the glyphs in TrueType outline format. +// http://www.microsoft.com/typography/otspec/glyf.htm + +'use strict'; + +var check = _dereq_('../check'); +var glyphset = _dereq_('../glyphset'); +var parse = _dereq_('../parse'); +var path = _dereq_('../path'); + +// Parse the coordinate data for a glyph. +function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { + var v; + if ((flag & shortVectorBitMask) > 0) { + // The coordinate is 1 byte long. + v = p.parseByte(); + // The `same` bit is re-used for short values to signify the sign of the value. + if ((flag & sameBitMask) === 0) { + v = -v; + } + + v = previousValue + v; + } else { + // The coordinate is 2 bytes long. + // If the `same` bit is set, the coordinate is the same as the previous coordinate. + if ((flag & sameBitMask) > 0) { + v = previousValue; + } else { + // Parse the coordinate as a signed 16-bit delta value. + v = previousValue + p.parseShort(); + } + } + + return v; +} + +// Parse a TrueType glyph. +function parseGlyph(glyph, data, start) { + var p = new parse.Parser(data, start); + glyph.numberOfContours = p.parseShort(); + glyph.xMin = p.parseShort(); + glyph.yMin = p.parseShort(); + glyph.xMax = p.parseShort(); + glyph.yMax = p.parseShort(); + var flags; + var flag; + if (glyph.numberOfContours > 0) { + var i; + // This glyph is not a composite. + var endPointIndices = glyph.endPointIndices = []; + for (i = 0; i < glyph.numberOfContours; i += 1) { + endPointIndices.push(p.parseUShort()); + } + + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (i = 0; i < glyph.instructionLength; i += 1) { + glyph.instructions.push(p.parseByte()); + } + + var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; + flags = []; + for (i = 0; i < numberOfCoordinates; i += 1) { + flag = p.parseByte(); + flags.push(flag); + // If bit 3 is set, we repeat this flag n times, where n is the next byte. + if ((flag & 8) > 0) { + var repeatCount = p.parseByte(); + for (var j = 0; j < repeatCount; j += 1) { + flags.push(flag); + i += 1; + } + } + } + + check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); + + if (endPointIndices.length > 0) { + var points = []; + var point; + // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. + if (numberOfCoordinates > 0) { + for (i = 0; i < numberOfCoordinates; i += 1) { + flag = flags[i]; + point = {}; + point.onCurve = !!(flag & 1); + point.lastPointOfContour = endPointIndices.indexOf(i) >= 0; + points.push(point); + } + + var px = 0; + for (i = 0; i < numberOfCoordinates; i += 1) { + flag = flags[i]; + point = points[i]; + point.x = parseGlyphCoordinate(p, flag, px, 2, 16); + px = point.x; + } + + var py = 0; + for (i = 0; i < numberOfCoordinates; i += 1) { + flag = flags[i]; + point = points[i]; + point.y = parseGlyphCoordinate(p, flag, py, 4, 32); + py = point.y; + } + } + + glyph.points = points; + } else { + glyph.points = []; + } + } else if (glyph.numberOfContours === 0) { + glyph.points = []; + } else { + glyph.isComposite = true; + glyph.points = []; + glyph.components = []; + var moreComponents = true; + while (moreComponents) { + flags = p.parseUShort(); + var component = { + glyphIndex: p.parseUShort(), + xScale: 1, + scale01: 0, + scale10: 0, + yScale: 1, + dx: 0, + dy: 0 + }; + if ((flags & 1) > 0) { + // The arguments are words + component.dx = p.parseShort(); + component.dy = p.parseShort(); + } else { + // The arguments are bytes + component.dx = p.parseChar(); + component.dy = p.parseChar(); + } + + if ((flags & 8) > 0) { + // We have a scale + component.xScale = component.yScale = p.parseF2Dot14(); + } else if ((flags & 64) > 0) { + // We have an X / Y scale + component.xScale = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } else if ((flags & 128) > 0) { + // We have a 2x2 transformation + component.xScale = p.parseF2Dot14(); + component.scale01 = p.parseF2Dot14(); + component.scale10 = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } + + glyph.components.push(component); + moreComponents = !!(flags & 32); + } + } +} + +// Transform an array of points and return a new array. +function transformPoints(points, transform) { + var newPoints = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + var newPt = { + x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, + y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, + onCurve: pt.onCurve, + lastPointOfContour: pt.lastPointOfContour + }; + newPoints.push(newPt); + } + + return newPoints; +} + +function getContours(points) { + var contours = []; + var currentContour = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +} + +// Convert the TrueType glyph outline to a Path. +function getPath(points) { + var p = new path.Path(); + if (!points) { + return p; + } + + var contours = getContours(points); + for (var i = 0; i < contours.length; i += 1) { + var contour = contours[i]; + var firstPt = contour[0]; + var lastPt = contour[contour.length - 1]; + var curvePt; + var realFirstPoint; + if (firstPt.onCurve) { + curvePt = null; + // The first point will be consumed by the moveTo command, + // so skip it in the loop. + realFirstPoint = true; + } else { + if (lastPt.onCurve) { + // If the first point is off-curve and the last point is on-curve, + // start at the last point. + firstPt = lastPt; + } else { + // If both first and last points are off-curve, start at their middle. + firstPt = { x: (firstPt.x + lastPt.x) / 2, y: (firstPt.y + lastPt.y) / 2 }; + } + + curvePt = firstPt; + // The first point is synthesized, so don't skip the real first point. + realFirstPoint = false; + } + + p.moveTo(firstPt.x, firstPt.y); + + for (var j = realFirstPoint ? 1 : 0; j < contour.length; j += 1) { + var pt = contour[j]; + var prevPt = j === 0 ? firstPt : contour[j - 1]; + if (prevPt.onCurve && pt.onCurve) { + // This is a straight line. + p.lineTo(pt.x, pt.y); + } else if (prevPt.onCurve && !pt.onCurve) { + curvePt = pt; + } else if (!prevPt.onCurve && !pt.onCurve) { + var midPt = { x: (prevPt.x + pt.x) / 2, y: (prevPt.y + pt.y) / 2 }; + p.quadraticCurveTo(prevPt.x, prevPt.y, midPt.x, midPt.y); + curvePt = pt; + } else if (!prevPt.onCurve && pt.onCurve) { + // Previous point off-curve, this point on-curve. + p.quadraticCurveTo(curvePt.x, curvePt.y, pt.x, pt.y); + curvePt = null; + } else { + throw new Error('Invalid state.'); + } + } + + if (firstPt !== lastPt) { + // Connect the last and first points + if (curvePt) { + p.quadraticCurveTo(curvePt.x, curvePt.y, firstPt.x, firstPt.y); + } else { + p.lineTo(firstPt.x, firstPt.y); + } + } + } + + p.closePath(); + return p; +} + +function buildPath(glyphs, glyph) { + if (glyph.isComposite) { + for (var j = 0; j < glyph.components.length; j += 1) { + var component = glyph.components[j]; + var componentGlyph = glyphs.get(component.glyphIndex); + if (componentGlyph.points) { + var transformedPoints = transformPoints(componentGlyph.points, component); + glyph.points = glyph.points.concat(transformedPoints); + } + } + } + + return getPath(glyph.points); +} + +// Parse all the glyphs according to the offsets from the `loca` table. +function parseGlyfTable(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + var i; + + // The last element of the loca table is invalid. + for (i = 0; i < loca.length - 1; i += 1) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + } + + return glyphs; +} + +exports.parse = parseGlyfTable; + +},{"../check":2,"../glyphset":7,"../parse":9,"../path":10}],15:[function(_dereq_,module,exports){ +// The `GPOS` table contains kerning pairs, among other things. +// https://www.microsoft.com/typography/OTSPEC/gpos.htm + +'use strict'; + +var check = _dereq_('../check'); +var parse = _dereq_('../parse'); + +// Parse ScriptList and FeatureList tables of GPOS, GSUB, GDEF, BASE, JSTF tables. +// These lists are unused by now, this function is just the basis for a real parsing. +function parseTaggedListTable(data, start) { + var p = new parse.Parser(data, start); + var n = p.parseUShort(); + var list = []; + for (var i = 0; i < n; i++) { + list[p.parseTag()] = { offset: p.parseUShort() }; + } + + return list; +} + +// Parse a coverage table in a GSUB, GPOS or GDEF table. +// Format 1 is a simple list of glyph ids, +// Format 2 is a list of ranges. It is expanded in a list of glyphs, maybe not the best idea. +function parseCoverageTable(data, start) { + var p = new parse.Parser(data, start); + var format = p.parseUShort(); + var count = p.parseUShort(); + if (format === 1) { + return p.parseUShortList(count); + } + else if (format === 2) { + var coverage = []; + for (; count--;) { + var begin = p.parseUShort(); + var end = p.parseUShort(); + var index = p.parseUShort(); + for (var i = begin; i <= end; i++) { + coverage[index++] = i; + } + } + + return coverage; + } +} + +// Parse a Class Definition Table in a GSUB, GPOS or GDEF table. +// Returns a function that gets a class value from a glyph ID. +function parseClassDefTable(data, start) { + var p = new parse.Parser(data, start); + var format = p.parseUShort(); + if (format === 1) { + // Format 1 specifies a range of consecutive glyph indices, one class per glyph ID. + var startGlyph = p.parseUShort(); + var glyphCount = p.parseUShort(); + var classes = p.parseUShortList(glyphCount); + return function(glyphID) { + return classes[glyphID - startGlyph] || 0; + }; + } + else if (format === 2) { + // Format 2 defines multiple groups of glyph indices that belong to the same class. + var rangeCount = p.parseUShort(); + var startGlyphs = []; + var endGlyphs = []; + var classValues = []; + for (var i = 0; i < rangeCount; i++) { + startGlyphs[i] = p.parseUShort(); + endGlyphs[i] = p.parseUShort(); + classValues[i] = p.parseUShort(); + } + + return function(glyphID) { + var l = 0; + var r = startGlyphs.length - 1; + while (l < r) { + var c = (l + r + 1) >> 1; + if (glyphID < startGlyphs[c]) { + r = c - 1; + } else { + l = c; + } + } + + if (startGlyphs[l] <= glyphID && glyphID <= endGlyphs[l]) { + return classValues[l] || 0; + } + + return 0; + }; + } +} + +// Parse a pair adjustment positioning subtable, format 1 or format 2 +// The subtable is returned in the form of a lookup function. +function parsePairPosSubTable(data, start) { + var p = new parse.Parser(data, start); + // This part is common to format 1 and format 2 subtables + var format = p.parseUShort(); + var coverageOffset = p.parseUShort(); + var coverage = parseCoverageTable(data, start + coverageOffset); + // valueFormat 4: XAdvance only, 1: XPlacement only, 0: no ValueRecord for second glyph + // Only valueFormat1=4 and valueFormat2=0 is supported. + var valueFormat1 = p.parseUShort(); + var valueFormat2 = p.parseUShort(); + var value1; + var value2; + if (valueFormat1 !== 4 || valueFormat2 !== 0) return; + var sharedPairSets = {}; + if (format === 1) { + // Pair Positioning Adjustment: Format 1 + var pairSetCount = p.parseUShort(); + var pairSet = []; + // Array of offsets to PairSet tables-from beginning of PairPos subtable-ordered by Coverage Index + var pairSetOffsets = p.parseOffset16List(pairSetCount); + for (var firstGlyph = 0; firstGlyph < pairSetCount; firstGlyph++) { + var pairSetOffset = pairSetOffsets[firstGlyph]; + var sharedPairSet = sharedPairSets[pairSetOffset]; + if (!sharedPairSet) { + // Parse a pairset table in a pair adjustment subtable format 1 + sharedPairSet = {}; + p.relativeOffset = pairSetOffset; + var pairValueCount = p.parseUShort(); + for (; pairValueCount--;) { + var secondGlyph = p.parseUShort(); + if (valueFormat1) value1 = p.parseShort(); + if (valueFormat2) value2 = p.parseShort(); + // We only support valueFormat1 = 4 and valueFormat2 = 0, + // so value1 is the XAdvance and value2 is empty. + sharedPairSet[secondGlyph] = value1; + } + } + + pairSet[coverage[firstGlyph]] = sharedPairSet; + } + + return function(leftGlyph, rightGlyph) { + var pairs = pairSet[leftGlyph]; + if (pairs) return pairs[rightGlyph]; + }; + } + else if (format === 2) { + // Pair Positioning Adjustment: Format 2 + var classDef1Offset = p.parseUShort(); + var classDef2Offset = p.parseUShort(); + var class1Count = p.parseUShort(); + var class2Count = p.parseUShort(); + var getClass1 = parseClassDefTable(data, start + classDef1Offset); + var getClass2 = parseClassDefTable(data, start + classDef2Offset); + + // Parse kerning values by class pair. + var kerningMatrix = []; + for (var i = 0; i < class1Count; i++) { + var kerningRow = kerningMatrix[i] = []; + for (var j = 0; j < class2Count; j++) { + if (valueFormat1) value1 = p.parseShort(); + if (valueFormat2) value2 = p.parseShort(); + // We only support valueFormat1 = 4 and valueFormat2 = 0, + // so value1 is the XAdvance and value2 is empty. + kerningRow[j] = value1; + } + } + + // Convert coverage list to a hash + var covered = {}; + for (i = 0; i < coverage.length; i++) covered[coverage[i]] = 1; + + // Get the kerning value for a specific glyph pair. + return function(leftGlyph, rightGlyph) { + if (!covered[leftGlyph]) return; + var class1 = getClass1(leftGlyph); + var class2 = getClass2(rightGlyph); + var kerningRow = kerningMatrix[class1]; + + if (kerningRow) { + return kerningRow[class2]; + } + }; + } +} + +// Parse a LookupTable (present in of GPOS, GSUB, GDEF, BASE, JSTF tables). +function parseLookupTable(data, start) { + var p = new parse.Parser(data, start); + var lookupType = p.parseUShort(); + var lookupFlag = p.parseUShort(); + var useMarkFilteringSet = lookupFlag & 0x10; + var subTableCount = p.parseUShort(); + var subTableOffsets = p.parseOffset16List(subTableCount); + var table = { + lookupType: lookupType, + lookupFlag: lookupFlag, + markFilteringSet: useMarkFilteringSet ? p.parseUShort() : -1 + }; + // LookupType 2, Pair adjustment + if (lookupType === 2) { + var subtables = []; + for (var i = 0; i < subTableCount; i++) { + subtables.push(parsePairPosSubTable(data, start + subTableOffsets[i])); + } + // Return a function which finds the kerning values in the subtables. + table.getKerningValue = function(leftGlyph, rightGlyph) { + for (var i = subtables.length; i--;) { + var value = subtables[i](leftGlyph, rightGlyph); + if (value !== undefined) return value; + } + + return 0; + }; + } + + return table; +} + +// Parse the `GPOS` table which contains, among other things, kerning pairs. +// https://www.microsoft.com/typography/OTSPEC/gpos.htm +function parseGposTable(data, start, font) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseFixed(); + check.argument(tableVersion === 1, 'Unsupported GPOS table version.'); + + // ScriptList and FeatureList - ignored for now + parseTaggedListTable(data, start + p.parseUShort()); + // 'kern' is the feature we are looking for. + parseTaggedListTable(data, start + p.parseUShort()); + + // LookupList + var lookupListOffset = p.parseUShort(); + p.relativeOffset = lookupListOffset; + var lookupCount = p.parseUShort(); + var lookupTableOffsets = p.parseOffset16List(lookupCount); + var lookupListAbsoluteOffset = start + lookupListOffset; + for (var i = 0; i < lookupCount; i++) { + var table = parseLookupTable(data, lookupListAbsoluteOffset + lookupTableOffsets[i]); + if (table.lookupType === 2 && !font.getGposKerningValue) font.getGposKerningValue = table.getKerningValue; + } +} + +exports.parse = parseGposTable; + +},{"../check":2,"../parse":9}],16:[function(_dereq_,module,exports){ +// The `head` table contains global information about the font. +// https://www.microsoft.com/typography/OTSPEC/head.htm + +'use strict'; + +var check = _dereq_('../check'); +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the header `head` table +function parseHeadTable(data, start) { + var head = {}; + var p = new parse.Parser(data, start); + head.version = p.parseVersion(); + head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; + head.checkSumAdjustment = p.parseULong(); + head.magicNumber = p.parseULong(); + check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); + head.flags = p.parseUShort(); + head.unitsPerEm = p.parseUShort(); + head.created = p.parseLongDateTime(); + head.modified = p.parseLongDateTime(); + head.xMin = p.parseShort(); + head.yMin = p.parseShort(); + head.xMax = p.parseShort(); + head.yMax = p.parseShort(); + head.macStyle = p.parseUShort(); + head.lowestRecPPEM = p.parseUShort(); + head.fontDirectionHint = p.parseShort(); + head.indexToLocFormat = p.parseShort(); // 50 + head.glyphDataFormat = p.parseShort(); + return head; +} + +function makeHeadTable(options) { + return new table.Table('head', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, + {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, + {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, + {name: 'flags', type: 'USHORT', value: 0}, + {name: 'unitsPerEm', type: 'USHORT', value: 1000}, + {name: 'created', type: 'LONGDATETIME', value: 0}, + {name: 'modified', type: 'LONGDATETIME', value: 0}, + {name: 'xMin', type: 'SHORT', value: 0}, + {name: 'yMin', type: 'SHORT', value: 0}, + {name: 'xMax', type: 'SHORT', value: 0}, + {name: 'yMax', type: 'SHORT', value: 0}, + {name: 'macStyle', type: 'USHORT', value: 0}, + {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, + {name: 'fontDirectionHint', type: 'SHORT', value: 2}, + {name: 'indexToLocFormat', type: 'SHORT', value: 0}, + {name: 'glyphDataFormat', type: 'SHORT', value: 0} + ], options); +} + +exports.parse = parseHeadTable; +exports.make = makeHeadTable; + +},{"../check":2,"../parse":9,"../table":11}],17:[function(_dereq_,module,exports){ +// The `hhea` table contains information for horizontal layout. +// https://www.microsoft.com/typography/OTSPEC/hhea.htm + +'use strict'; + +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the horizontal header `hhea` table +function parseHheaTable(data, start) { + var hhea = {}; + var p = new parse.Parser(data, start); + hhea.version = p.parseVersion(); + hhea.ascender = p.parseShort(); + hhea.descender = p.parseShort(); + hhea.lineGap = p.parseShort(); + hhea.advanceWidthMax = p.parseUShort(); + hhea.minLeftSideBearing = p.parseShort(); + hhea.minRightSideBearing = p.parseShort(); + hhea.xMaxExtent = p.parseShort(); + hhea.caretSlopeRise = p.parseShort(); + hhea.caretSlopeRun = p.parseShort(); + hhea.caretOffset = p.parseShort(); + p.relativeOffset += 8; + hhea.metricDataFormat = p.parseShort(); + hhea.numberOfHMetrics = p.parseUShort(); + return hhea; +} + +function makeHheaTable(options) { + return new table.Table('hhea', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'ascender', type: 'FWORD', value: 0}, + {name: 'descender', type: 'FWORD', value: 0}, + {name: 'lineGap', type: 'FWORD', value: 0}, + {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, + {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, + {name: 'minRightSideBearing', type: 'FWORD', value: 0}, + {name: 'xMaxExtent', type: 'FWORD', value: 0}, + {name: 'caretSlopeRise', type: 'SHORT', value: 1}, + {name: 'caretSlopeRun', type: 'SHORT', value: 0}, + {name: 'caretOffset', type: 'SHORT', value: 0}, + {name: 'reserved1', type: 'SHORT', value: 0}, + {name: 'reserved2', type: 'SHORT', value: 0}, + {name: 'reserved3', type: 'SHORT', value: 0}, + {name: 'reserved4', type: 'SHORT', value: 0}, + {name: 'metricDataFormat', type: 'SHORT', value: 0}, + {name: 'numberOfHMetrics', type: 'USHORT', value: 0} + ], options); +} + +exports.parse = parseHheaTable; +exports.make = makeHheaTable; + +},{"../parse":9,"../table":11}],18:[function(_dereq_,module,exports){ +// The `hmtx` table contains the horizontal metrics for all glyphs. +// https://www.microsoft.com/typography/OTSPEC/hmtx.htm + +'use strict'; + +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. +// This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. +function parseHmtxTable(data, start, numMetrics, numGlyphs, glyphs) { + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + var glyph = glyphs.get(i); + glyph.advanceWidth = advanceWidth; + glyph.leftSideBearing = leftSideBearing; + } +} + +function makeHmtxTable(glyphs) { + var t = new table.Table('hmtx', []); + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var advanceWidth = glyph.advanceWidth || 0; + var leftSideBearing = glyph.leftSideBearing || 0; + t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); + t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); + } + + return t; +} + +exports.parse = parseHmtxTable; +exports.make = makeHmtxTable; + +},{"../parse":9,"../table":11}],19:[function(_dereq_,module,exports){ +// The `kern` table contains kerning pairs. +// Note that some fonts use the GPOS OpenType layout table to specify kerning. +// https://www.microsoft.com/typography/OTSPEC/kern.htm + +'use strict'; + +var check = _dereq_('../check'); +var parse = _dereq_('../parse'); + +// Parse the `kern` table which contains kerning pairs. +function parseKernTable(data, start) { + var pairs = {}; + var p = new parse.Parser(data, start); + var tableVersion = p.parseUShort(); + check.argument(tableVersion === 0, 'Unsupported kern table version.'); + // Skip nTables. + p.skip('uShort', 1); + var subTableVersion = p.parseUShort(); + check.argument(subTableVersion === 0, 'Unsupported kern sub-table version.'); + // Skip subTableLength, subTableCoverage + p.skip('uShort', 2); + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + + return pairs; +} + +exports.parse = parseKernTable; + +},{"../check":2,"../parse":9}],20:[function(_dereq_,module,exports){ +// The `loca` table stores the offsets to the locations of the glyphs in the font. +// https://www.microsoft.com/typography/OTSPEC/loca.htm + +'use strict'; + +var parse = _dereq_('../parse'); + +// Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, +// relative to the beginning of the glyphData table. +// The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) +// The loca table has two versions: a short version where offsets are stored as uShorts, and a long +// version where offsets are stored as uLongs. The `head` table specifies which version to use +// (under indexToLocFormat). +function parseLocaTable(data, start, numGlyphs, shortVersion) { + var p = new parse.Parser(data, start); + var parseFn = shortVersion ? p.parseUShort : p.parseULong; + // There is an extra entry after the last index element to compute the length of the last glyph. + // That's why we use numGlyphs + 1. + var glyphOffsets = []; + for (var i = 0; i < numGlyphs + 1; i += 1) { + var glyphOffset = parseFn.call(p); + if (shortVersion) { + // The short table version stores the actual offset divided by 2. + glyphOffset *= 2; + } + + glyphOffsets.push(glyphOffset); + } + + return glyphOffsets; +} + +exports.parse = parseLocaTable; + +},{"../parse":9}],21:[function(_dereq_,module,exports){ +// The `maxp` table establishes the memory requirements for the font. +// We need it just to get the number of glyphs in the font. +// https://www.microsoft.com/typography/OTSPEC/maxp.htm + +'use strict'; + +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the maximum profile `maxp` table. +function parseMaxpTable(data, start) { + var maxp = {}; + var p = new parse.Parser(data, start); + maxp.version = p.parseVersion(); + maxp.numGlyphs = p.parseUShort(); + if (maxp.version === 1.0) { + maxp.maxPoints = p.parseUShort(); + maxp.maxContours = p.parseUShort(); + maxp.maxCompositePoints = p.parseUShort(); + maxp.maxCompositeContours = p.parseUShort(); + maxp.maxZones = p.parseUShort(); + maxp.maxTwilightPoints = p.parseUShort(); + maxp.maxStorage = p.parseUShort(); + maxp.maxFunctionDefs = p.parseUShort(); + maxp.maxInstructionDefs = p.parseUShort(); + maxp.maxStackElements = p.parseUShort(); + maxp.maxSizeOfInstructions = p.parseUShort(); + maxp.maxComponentElements = p.parseUShort(); + maxp.maxComponentDepth = p.parseUShort(); + } + + return maxp; +} + +function makeMaxpTable(numGlyphs) { + return new table.Table('maxp', [ + {name: 'version', type: 'FIXED', value: 0x00005000}, + {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} + ]); +} + +exports.parse = parseMaxpTable; +exports.make = makeMaxpTable; + +},{"../parse":9,"../table":11}],22:[function(_dereq_,module,exports){ +// The `name` naming table. +// https://www.microsoft.com/typography/OTSPEC/name.htm + +'use strict'; + +var encode = _dereq_('../types').encode; +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// NameIDs for the name table. +var nameTableNames = [ + 'copyright', // 0 + 'fontFamily', // 1 + 'fontSubfamily', // 2 + 'uniqueID', // 3 + 'fullName', // 4 + 'version', // 5 + 'postScriptName', // 6 + 'trademark', // 7 + 'manufacturer', // 8 + 'designer', // 9 + 'description', // 10 + 'manufacturerURL', // 11 + 'designerURL', // 12 + 'licence', // 13 + 'licenceURL', // 14 + 'reserved', // 15 + 'preferredFamily', // 16 + 'preferredSubfamily', // 17 + 'compatibleFullName', // 18 + 'sampleText', // 19 + 'postScriptFindFontName', // 20 + 'wwsFamily', // 21 + 'wwsSubfamily' // 22 +]; + +// Parse the naming `name` table +// Only Windows Unicode English names are supported. +// Format 1 additional fields are not supported +function parseNameTable(data, start) { + var name = {}; + var p = new parse.Parser(data, start); + name.format = p.parseUShort(); + var count = p.parseUShort(); + var stringOffset = p.offset + p.parseUShort(); + var unknownCount = 0; + for (var i = 0; i < count; i++) { + var platformID = p.parseUShort(); + var encodingID = p.parseUShort(); + var languageID = p.parseUShort(); + var nameID = p.parseUShort(); + var property = nameTableNames[nameID]; + var byteLength = p.parseUShort(); + var offset = p.parseUShort(); + // platformID - encodingID - languageID standard combinations : + // 1 - 0 - 0 : Macintosh, Roman, English + // 3 - 1 - 0x409 : Windows, Unicode BMP (UCS-2), en-US + if (platformID === 3 && encodingID === 1 && languageID === 0x409) { + var codePoints = []; + var length = byteLength / 2; + for (var j = 0; j < length; j++, offset += 2) { + codePoints[j] = parse.getShort(data, stringOffset + offset); + } + + var str = String.fromCharCode.apply(null, codePoints); + if (property) { + name[property] = str; + } + else { + unknownCount++; + name['unknown' + unknownCount] = str; + } + } + + } + + if (name.format === 1) { + name.langTagCount = p.parseUShort(); + } + + return name; +} + +function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { + return new table.Table('NameRecord', [ + {name: 'platformID', type: 'USHORT', value: platformID}, + {name: 'encodingID', type: 'USHORT', value: encodingID}, + {name: 'languageID', type: 'USHORT', value: languageID}, + {name: 'nameID', type: 'USHORT', value: nameID}, + {name: 'length', type: 'USHORT', value: length}, + {name: 'offset', type: 'USHORT', value: offset} + ]); +} + +function addMacintoshNameRecord(t, recordID, s, offset) { + // Macintosh, Roman, English + var stringBytes = encode.STRING(s); + t.records.push(makeNameRecord(1, 0, 0, recordID, stringBytes.length, offset)); + t.strings.push(stringBytes); + offset += stringBytes.length; + return offset; +} + +function addWindowsNameRecord(t, recordID, s, offset) { + // Windows, Unicode BMP (UCS-2), US English + var utf16Bytes = encode.UTF16(s); + t.records.push(makeNameRecord(3, 1, 0x0409, recordID, utf16Bytes.length, offset)); + t.strings.push(utf16Bytes); + offset += utf16Bytes.length; + return offset; +} + +function makeNameTable(options) { + var t = new table.Table('name', [ + {name: 'format', type: 'USHORT', value: 0}, + {name: 'count', type: 'USHORT', value: 0}, + {name: 'stringOffset', type: 'USHORT', value: 0} + ]); + t.records = []; + t.strings = []; + var offset = 0; + var i; + var s; + // Add Macintosh records first + for (i = 0; i < nameTableNames.length; i += 1) { + if (options[nameTableNames[i]] !== undefined) { + s = options[nameTableNames[i]]; + offset = addMacintoshNameRecord(t, i, s, offset); + } + } + // Then add Windows records + for (i = 0; i < nameTableNames.length; i += 1) { + if (options[nameTableNames[i]] !== undefined) { + s = options[nameTableNames[i]]; + offset = addWindowsNameRecord(t, i, s, offset); + } + } + + t.count = t.records.length; + t.stringOffset = 6 + t.count * 12; + for (i = 0; i < t.records.length; i += 1) { + t.fields.push({name: 'record_' + i, type: 'TABLE', value: t.records[i]}); + } + + for (i = 0; i < t.strings.length; i += 1) { + t.fields.push({name: 'string_' + i, type: 'LITERAL', value: t.strings[i]}); + } + + return t; +} + +exports.parse = parseNameTable; +exports.make = makeNameTable; + +},{"../parse":9,"../table":11,"../types":26}],23:[function(_dereq_,module,exports){ +// The `OS/2` table contains metrics required in OpenType fonts. +// https://www.microsoft.com/typography/OTSPEC/os2.htm + +'use strict'; + +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +var unicodeRanges = [ + {begin: 0x0000, end: 0x007F}, // Basic Latin + {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement + {begin: 0x0100, end: 0x017F}, // Latin Extended-A + {begin: 0x0180, end: 0x024F}, // Latin Extended-B + {begin: 0x0250, end: 0x02AF}, // IPA Extensions + {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters + {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks + {begin: 0x0370, end: 0x03FF}, // Greek and Coptic + {begin: 0x2C80, end: 0x2CFF}, // Coptic + {begin: 0x0400, end: 0x04FF}, // Cyrillic + {begin: 0x0530, end: 0x058F}, // Armenian + {begin: 0x0590, end: 0x05FF}, // Hebrew + {begin: 0xA500, end: 0xA63F}, // Vai + {begin: 0x0600, end: 0x06FF}, // Arabic + {begin: 0x07C0, end: 0x07FF}, // NKo + {begin: 0x0900, end: 0x097F}, // Devanagari + {begin: 0x0980, end: 0x09FF}, // Bengali + {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi + {begin: 0x0A80, end: 0x0AFF}, // Gujarati + {begin: 0x0B00, end: 0x0B7F}, // Oriya + {begin: 0x0B80, end: 0x0BFF}, // Tamil + {begin: 0x0C00, end: 0x0C7F}, // Telugu + {begin: 0x0C80, end: 0x0CFF}, // Kannada + {begin: 0x0D00, end: 0x0D7F}, // Malayalam + {begin: 0x0E00, end: 0x0E7F}, // Thai + {begin: 0x0E80, end: 0x0EFF}, // Lao + {begin: 0x10A0, end: 0x10FF}, // Georgian + {begin: 0x1B00, end: 0x1B7F}, // Balinese + {begin: 0x1100, end: 0x11FF}, // Hangul Jamo + {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional + {begin: 0x1F00, end: 0x1FFF}, // Greek Extended + {begin: 0x2000, end: 0x206F}, // General Punctuation + {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts + {begin: 0x20A0, end: 0x20CF}, // Currency Symbol + {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols + {begin: 0x2100, end: 0x214F}, // Letterlike Symbols + {begin: 0x2150, end: 0x218F}, // Number Forms + {begin: 0x2190, end: 0x21FF}, // Arrows + {begin: 0x2200, end: 0x22FF}, // Mathematical Operators + {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical + {begin: 0x2400, end: 0x243F}, // Control Pictures + {begin: 0x2440, end: 0x245F}, // Optical Character Recognition + {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics + {begin: 0x2500, end: 0x257F}, // Box Drawing + {begin: 0x2580, end: 0x259F}, // Block Elements + {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes + {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols + {begin: 0x2700, end: 0x27BF}, // Dingbats + {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation + {begin: 0x3040, end: 0x309F}, // Hiragana + {begin: 0x30A0, end: 0x30FF}, // Katakana + {begin: 0x3100, end: 0x312F}, // Bopomofo + {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo + {begin: 0xA840, end: 0xA87F}, // Phags-pa + {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months + {begin: 0x3300, end: 0x33FF}, // CJK Compatibility + {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables + {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * + {begin: 0x10900, end: 0x1091F}, // Phoenicia + {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs + {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) + {begin: 0x31C0, end: 0x31EF}, // CJK Strokes + {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms + {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A + {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks + {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms + {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants + {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B + {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms + {begin: 0xFFF0, end: 0xFFFF}, // Specials + {begin: 0x0F00, end: 0x0FFF}, // Tibetan + {begin: 0x0700, end: 0x074F}, // Syriac + {begin: 0x0780, end: 0x07BF}, // Thaana + {begin: 0x0D80, end: 0x0DFF}, // Sinhala + {begin: 0x1000, end: 0x109F}, // Myanmar + {begin: 0x1200, end: 0x137F}, // Ethiopic + {begin: 0x13A0, end: 0x13FF}, // Cherokee + {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics + {begin: 0x1680, end: 0x169F}, // Ogham + {begin: 0x16A0, end: 0x16FF}, // Runic + {begin: 0x1780, end: 0x17FF}, // Khmer + {begin: 0x1800, end: 0x18AF}, // Mongolian + {begin: 0x2800, end: 0x28FF}, // Braille Patterns + {begin: 0xA000, end: 0xA48F}, // Yi Syllables + {begin: 0x1700, end: 0x171F}, // Tagalog + {begin: 0x10300, end: 0x1032F}, // Old Italic + {begin: 0x10330, end: 0x1034F}, // Gothic + {begin: 0x10400, end: 0x1044F}, // Deseret + {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols + {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols + {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) + {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors + {begin: 0xE0000, end: 0xE007F}, // Tags + {begin: 0x1900, end: 0x194F}, // Limbu + {begin: 0x1950, end: 0x197F}, // Tai Le + {begin: 0x1980, end: 0x19DF}, // New Tai Lue + {begin: 0x1A00, end: 0x1A1F}, // Buginese + {begin: 0x2C00, end: 0x2C5F}, // Glagolitic + {begin: 0x2D30, end: 0x2D7F}, // Tifinagh + {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols + {begin: 0xA800, end: 0xA82F}, // Syloti Nagri + {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary + {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers + {begin: 0x10380, end: 0x1039F}, // Ugaritic + {begin: 0x103A0, end: 0x103DF}, // Old Persian + {begin: 0x10450, end: 0x1047F}, // Shavian + {begin: 0x10480, end: 0x104AF}, // Osmanya + {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary + {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi + {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols + {begin: 0x12000, end: 0x123FF}, // Cuneiform + {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals + {begin: 0x1B80, end: 0x1BBF}, // Sundanese + {begin: 0x1C00, end: 0x1C4F}, // Lepcha + {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki + {begin: 0xA880, end: 0xA8DF}, // Saurashtra + {begin: 0xA900, end: 0xA92F}, // Kayah Li + {begin: 0xA930, end: 0xA95F}, // Rejang + {begin: 0xAA00, end: 0xAA5F}, // Cham + {begin: 0x10190, end: 0x101CF}, // Ancient Symbols + {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc + {begin: 0x102A0, end: 0x102DF}, // Carian + {begin: 0x1F030, end: 0x1F09F} // Domino Tiles +]; + +function getUnicodeRange(unicode) { + for (var i = 0; i < unicodeRanges.length; i += 1) { + var range = unicodeRanges[i]; + if (unicode >= range.begin && unicode < range.end) { + return i; + } + } + + return -1; +} + +// Parse the OS/2 and Windows metrics `OS/2` table +function parseOS2Table(data, start) { + var os2 = {}; + var p = new parse.Parser(data, start); + os2.version = p.parseUShort(); + os2.xAvgCharWidth = p.parseShort(); + os2.usWeightClass = p.parseUShort(); + os2.usWidthClass = p.parseUShort(); + os2.fsType = p.parseUShort(); + os2.ySubscriptXSize = p.parseShort(); + os2.ySubscriptYSize = p.parseShort(); + os2.ySubscriptXOffset = p.parseShort(); + os2.ySubscriptYOffset = p.parseShort(); + os2.ySuperscriptXSize = p.parseShort(); + os2.ySuperscriptYSize = p.parseShort(); + os2.ySuperscriptXOffset = p.parseShort(); + os2.ySuperscriptYOffset = p.parseShort(); + os2.yStrikeoutSize = p.parseShort(); + os2.yStrikeoutPosition = p.parseShort(); + os2.sFamilyClass = p.parseShort(); + os2.panose = []; + for (var i = 0; i < 10; i++) { + os2.panose[i] = p.parseByte(); + } + + os2.ulUnicodeRange1 = p.parseULong(); + os2.ulUnicodeRange2 = p.parseULong(); + os2.ulUnicodeRange3 = p.parseULong(); + os2.ulUnicodeRange4 = p.parseULong(); + os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); + os2.fsSelection = p.parseUShort(); + os2.usFirstCharIndex = p.parseUShort(); + os2.usLastCharIndex = p.parseUShort(); + os2.sTypoAscender = p.parseShort(); + os2.sTypoDescender = p.parseShort(); + os2.sTypoLineGap = p.parseShort(); + os2.usWinAscent = p.parseUShort(); + os2.usWinDescent = p.parseUShort(); + if (os2.version >= 1) { + os2.ulCodePageRange1 = p.parseULong(); + os2.ulCodePageRange2 = p.parseULong(); + } + + if (os2.version >= 2) { + os2.sxHeight = p.parseShort(); + os2.sCapHeight = p.parseShort(); + os2.usDefaultChar = p.parseUShort(); + os2.usBreakChar = p.parseUShort(); + os2.usMaxContent = p.parseUShort(); + } + + return os2; +} + +function makeOS2Table(options) { + return new table.Table('OS/2', [ + {name: 'version', type: 'USHORT', value: 0x0003}, + {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, + {name: 'usWeightClass', type: 'USHORT', value: 0}, + {name: 'usWidthClass', type: 'USHORT', value: 0}, + {name: 'fsType', type: 'USHORT', value: 0}, + {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, + {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, + {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, + {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, + {name: 'sFamilyClass', type: 'SHORT', value: 0}, + {name: 'bFamilyType', type: 'BYTE', value: 0}, + {name: 'bSerifStyle', type: 'BYTE', value: 0}, + {name: 'bWeight', type: 'BYTE', value: 0}, + {name: 'bProportion', type: 'BYTE', value: 0}, + {name: 'bContrast', type: 'BYTE', value: 0}, + {name: 'bStrokeVariation', type: 'BYTE', value: 0}, + {name: 'bArmStyle', type: 'BYTE', value: 0}, + {name: 'bLetterform', type: 'BYTE', value: 0}, + {name: 'bMidline', type: 'BYTE', value: 0}, + {name: 'bXHeight', type: 'BYTE', value: 0}, + {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, + {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, + {name: 'fsSelection', type: 'USHORT', value: 0}, + {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, + {name: 'usLastCharIndex', type: 'USHORT', value: 0}, + {name: 'sTypoAscender', type: 'SHORT', value: 0}, + {name: 'sTypoDescender', type: 'SHORT', value: 0}, + {name: 'sTypoLineGap', type: 'SHORT', value: 0}, + {name: 'usWinAscent', type: 'USHORT', value: 0}, + {name: 'usWinDescent', type: 'USHORT', value: 0}, + {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, + {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, + {name: 'sxHeight', type: 'SHORT', value: 0}, + {name: 'sCapHeight', type: 'SHORT', value: 0}, + {name: 'usDefaultChar', type: 'USHORT', value: 0}, + {name: 'usBreakChar', type: 'USHORT', value: 0}, + {name: 'usMaxContext', type: 'USHORT', value: 0} + ], options); +} + +exports.unicodeRanges = unicodeRanges; +exports.getUnicodeRange = getUnicodeRange; +exports.parse = parseOS2Table; +exports.make = makeOS2Table; + +},{"../parse":9,"../table":11}],24:[function(_dereq_,module,exports){ +// The `post` table stores additional PostScript information, such as glyph names. +// https://www.microsoft.com/typography/OTSPEC/post.htm + +'use strict'; + +var encoding = _dereq_('../encoding'); +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the PostScript `post` table +function parsePostTable(data, start) { + var post = {}; + var p = new parse.Parser(data, start); + var i; + post.version = p.parseVersion(); + post.italicAngle = p.parseFixed(); + post.underlinePosition = p.parseShort(); + post.underlineThickness = p.parseShort(); + post.isFixedPitch = p.parseULong(); + post.minMemType42 = p.parseULong(); + post.maxMemType42 = p.parseULong(); + post.minMemType1 = p.parseULong(); + post.maxMemType1 = p.parseULong(); + switch (post.version) { + case 1: + post.names = encoding.standardNames.slice(); + break; + case 2: + post.numberOfGlyphs = p.parseUShort(); + post.glyphNameIndex = new Array(post.numberOfGlyphs); + for (i = 0; i < post.numberOfGlyphs; i++) { + post.glyphNameIndex[i] = p.parseUShort(); + } + + post.names = []; + for (i = 0; i < post.numberOfGlyphs; i++) { + if (post.glyphNameIndex[i] >= encoding.standardNames.length) { + var nameLength = p.parseChar(); + post.names.push(p.parseString(nameLength)); + } + } + + break; + case 2.5: + post.numberOfGlyphs = p.parseUShort(); + post.offset = new Array(post.numberOfGlyphs); + for (i = 0; i < post.numberOfGlyphs; i++) { + post.offset[i] = p.parseChar(); + } + + break; + } + return post; +} + +function makePostTable() { + return new table.Table('post', [ + {name: 'version', type: 'FIXED', value: 0x00030000}, + {name: 'italicAngle', type: 'FIXED', value: 0}, + {name: 'underlinePosition', type: 'FWORD', value: 0}, + {name: 'underlineThickness', type: 'FWORD', value: 0}, + {name: 'isFixedPitch', type: 'ULONG', value: 0}, + {name: 'minMemType42', type: 'ULONG', value: 0}, + {name: 'maxMemType42', type: 'ULONG', value: 0}, + {name: 'minMemType1', type: 'ULONG', value: 0}, + {name: 'maxMemType1', type: 'ULONG', value: 0} + ]); +} + +exports.parse = parsePostTable; +exports.make = makePostTable; + +},{"../encoding":4,"../parse":9,"../table":11}],25:[function(_dereq_,module,exports){ +// The `sfnt` wrapper provides organization for the tables in the font. +// It is the top-level data structure in a font. +// https://www.microsoft.com/typography/OTSPEC/otff.htm +// Recommendations for creating OpenType Fonts: +// http://www.microsoft.com/typography/otspec140/recom.htm + +'use strict'; + +var check = _dereq_('../check'); +var table = _dereq_('../table'); + +var cmap = _dereq_('./cmap'); +var cff = _dereq_('./cff'); +var head = _dereq_('./head'); +var hhea = _dereq_('./hhea'); +var hmtx = _dereq_('./hmtx'); +var maxp = _dereq_('./maxp'); +var _name = _dereq_('./name'); +var os2 = _dereq_('./os2'); +var post = _dereq_('./post'); + +function log2(v) { + return Math.log(v) / Math.log(2) | 0; +} + +function computeCheckSum(bytes) { + while (bytes.length % 4 !== 0) { + bytes.push(0); + } + + var sum = 0; + for (var i = 0; i < bytes.length; i += 4) { + sum += (bytes[i] << 24) + + (bytes[i + 1] << 16) + + (bytes[i + 2] << 8) + + (bytes[i + 3]); + } + + sum %= Math.pow(2, 32); + return sum; +} + +function makeTableRecord(tag, checkSum, offset, length) { + return new table.Table('Table Record', [ + {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, + {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, + {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, + {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} + ]); +} + +function makeSfntTable(tables) { + var sfnt = new table.Table('sfnt', [ + {name: 'version', type: 'TAG', value: 'OTTO'}, + {name: 'numTables', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + sfnt.tables = tables; + sfnt.numTables = tables.length; + var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); + sfnt.searchRange = 16 * highestPowerOf2; + sfnt.entrySelector = log2(highestPowerOf2); + sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; + + var recordFields = []; + var tableFields = []; + + var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + + for (var i = 0; i < tables.length; i += 1) { + var t = tables[i]; + check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); + var tableLength = t.sizeOf(); + var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); + recordFields.push({name: tableRecord.tag + ' Table Record', type: 'TABLE', value: tableRecord}); + tableFields.push({name: t.tableName + ' table', type: 'TABLE', value: t}); + offset += tableLength; + check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + } + + // Table records need to be sorted alphabetically. + recordFields.sort(function(r1, r2) { + if (r1.value.tag > r2.value.tag) { + return 1; + } else { + return -1; + } + }); + + sfnt.fields = sfnt.fields.concat(recordFields); + sfnt.fields = sfnt.fields.concat(tableFields); + return sfnt; +} + +// Get the metrics for a character. If the string has more than one character +// this function returns metrics for the first available character. +// You can provide optional fallback metrics if no characters are available. +function metricsForChar(font, chars, notFoundMetrics) { + for (var i = 0; i < chars.length; i += 1) { + var glyphIndex = font.charToGlyphIndex(chars[i]); + if (glyphIndex > 0) { + var glyph = font.glyphs.get(glyphIndex); + return glyph.getMetrics(); + } + } + + return notFoundMetrics; +} + +function average(vs) { + var sum = 0; + for (var i = 0; i < vs.length; i += 1) { + sum += vs[i]; + } + + return sum / vs.length; +} + +// Convert the font object to a SFNT data structure. +// This structure contains all the necessary tables and metadata to create a binary OTF file. +function fontToSfntTable(font) { + var xMins = []; + var yMins = []; + var xMaxs = []; + var yMaxs = []; + var advanceWidths = []; + var leftSideBearings = []; + var rightSideBearings = []; + var firstCharIndex; + var lastCharIndex = 0; + var ulUnicodeRange1 = 0; + var ulUnicodeRange2 = 0; + var ulUnicodeRange3 = 0; + var ulUnicodeRange4 = 0; + + for (var i = 0; i < font.glyphs.length; i += 1) { + var glyph = font.glyphs.get(i); + var unicode = glyph.unicode | 0; + if (firstCharIndex > unicode || firstCharIndex === null) { + firstCharIndex = unicode; + } + + if (lastCharIndex < unicode) { + lastCharIndex = unicode; + } + + var position = os2.getUnicodeRange(unicode); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << position - 32; + } else if (position < 96) { + ulUnicodeRange3 |= 1 << position - 64; + } else if (position < 123) { + ulUnicodeRange4 |= 1 << position - 96; + } else { + throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); + } + // Skip non-important characters. + if (glyph.name === '.notdef') continue; + var metrics = glyph.getMetrics(); + xMins.push(metrics.xMin); + yMins.push(metrics.yMin); + xMaxs.push(metrics.xMax); + yMaxs.push(metrics.yMax); + leftSideBearings.push(metrics.leftSideBearing); + rightSideBearings.push(metrics.rightSideBearing); + advanceWidths.push(glyph.advanceWidth); + } + + var globals = { + xMin: Math.min.apply(null, xMins), + yMin: Math.min.apply(null, yMins), + xMax: Math.max.apply(null, xMaxs), + yMax: Math.max.apply(null, yMaxs), + advanceWidthMax: Math.max.apply(null, advanceWidths), + advanceWidthAvg: average(advanceWidths), + minLeftSideBearing: Math.min.apply(null, leftSideBearings), + maxLeftSideBearing: Math.max.apply(null, leftSideBearings), + minRightSideBearing: Math.min.apply(null, rightSideBearings) + }; + globals.ascender = font.ascender !== undefined ? font.ascender : globals.yMax; + globals.descender = font.descender !== undefined ? font.descender : globals.yMin; + + var headTable = head.make({ + unitsPerEm: font.unitsPerEm, + xMin: globals.xMin, + yMin: globals.yMin, + xMax: globals.xMax, + yMax: globals.yMax + }); + + var hheaTable = hhea.make({ + ascender: globals.ascender, + descender: globals.descender, + advanceWidthMax: globals.advanceWidthMax, + minLeftSideBearing: globals.minLeftSideBearing, + minRightSideBearing: globals.minRightSideBearing, + xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), + numberOfHMetrics: font.glyphs.length + }); + + var maxpTable = maxp.make(font.glyphs.length); + + var os2Table = os2.make({ + xAvgCharWidth: Math.round(globals.advanceWidthAvg), + usWeightClass: 500, // Medium FIXME Make this configurable + usWidthClass: 5, // Medium (normal) FIXME Make this configurable + usFirstCharIndex: firstCharIndex, + usLastCharIndex: lastCharIndex, + ulUnicodeRange1: ulUnicodeRange1, + ulUnicodeRange2: ulUnicodeRange2, + ulUnicodeRange3: ulUnicodeRange3, + ulUnicodeRange4: ulUnicodeRange4, + // See http://typophile.com/node/13081 for more info on vertical metrics. + // We get metrics for typical characters (such as "x" for xHeight). + // We provide some fallback characters if characters are unavailable: their + // ordering was chosen experimentally. + sTypoAscender: globals.ascender, + sTypoDescender: globals.descender, + sTypoLineGap: 0, + usWinAscent: globals.ascender, + usWinDescent: -globals.descender, + sxHeight: metricsForChar(font, 'xyvw', {yMax: 0}).yMax, + sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, + usBreakChar: font.hasChar(' ') ? 32 : 0 // Use space as the break character, if available. + }); + + var hmtxTable = hmtx.make(font.glyphs); + var cmapTable = cmap.make(font.glyphs); + + var fullName = font.familyName + ' ' + font.styleName; + var postScriptName = font.familyName.replace(/\s/g, '') + '-' + font.styleName; + var nameTable = _name.make({ + copyright: font.copyright, + fontFamily: font.familyName, + fontSubfamily: font.styleName, + uniqueID: font.manufacturer + ':' + fullName, + fullName: fullName, + version: font.version, + postScriptName: postScriptName, + trademark: font.trademark, + manufacturer: font.manufacturer, + designer: font.designer, + description: font.description, + manufacturerURL: font.manufacturerURL, + designerURL: font.designerURL, + license: font.license, + licenseURL: font.licenseURL, + preferredFamily: font.familyName, + preferredSubfamily: font.styleName + }); + var postTable = post.make(); + var cffTable = cff.make(font.glyphs, { + version: font.version, + fullName: fullName, + familyName: font.familyName, + weightName: font.styleName, + postScriptName: postScriptName, + unitsPerEm: font.unitsPerEm + }); + // Order the tables according to the the OpenType specification 1.4. + var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; + + var sfntTable = makeSfntTable(tables); + + // Compute the font's checkSum and store it in head.checkSumAdjustment. + var bytes = sfntTable.encode(); + var checkSum = computeCheckSum(bytes); + var tableFields = sfntTable.fields; + var checkSumAdjusted = false; + for (i = 0; i < tableFields.length; i += 1) { + if (tableFields[i].name === 'head table') { + tableFields[i].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; + checkSumAdjusted = true; + break; + } + } + + if (!checkSumAdjusted) { + throw new Error('Could not find head table with checkSum to adjust.'); + } + + return sfntTable; +} + +exports.computeCheckSum = computeCheckSum; +exports.make = makeSfntTable; +exports.fontToTable = fontToSfntTable; + +},{"../check":2,"../table":11,"./cff":12,"./cmap":13,"./head":16,"./hhea":17,"./hmtx":18,"./maxp":21,"./name":22,"./os2":23,"./post":24}],26:[function(_dereq_,module,exports){ +// Data types used in the OpenType font file. +// All OpenType fonts use Motorola-style byte ordering (Big Endian) + +/* global WeakMap */ + +'use strict'; + +var check = _dereq_('./check'); + +var LIMIT16 = 32768; // The limit at which a 16-bit number switches signs == 2^15 +var LIMIT32 = 2147483648; // The limit at which a 32-bit number switches signs == 2 ^ 31 + +var decode = {}; +var encode = {}; +var sizeOf = {}; + +// Return a function that always returns the same value. +function constant(v) { + return function() { + return v; + }; +} + +// OpenType data types ////////////////////////////////////////////////////// + +// Convert an 8-bit unsigned integer to a list of 1 byte. +encode.BYTE = function(v) { + check.argument(v >= 0 && v <= 255, 'Byte value should be between 0 and 255.'); + return [v]; +}; + +sizeOf.BYTE = constant(1); + +// Convert a 8-bit signed integer to a list of 1 byte. +encode.CHAR = function(v) { + return [v.charCodeAt(0)]; +}; + +sizeOf.BYTE = constant(1); + +// Convert an ASCII string to a list of bytes. +encode.CHARARRAY = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + b.push(v.charCodeAt(i)); + } + + return b; +}; + +sizeOf.CHARARRAY = function(v) { + return v.length; +}; + +// Convert a 16-bit unsigned integer to a list of 2 bytes. +encode.USHORT = function(v) { + return [(v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.USHORT = constant(2); + +// Convert a 16-bit signed integer to a list of 2 bytes. +encode.SHORT = function(v) { + // Two's complement + if (v >= LIMIT16) { + v = -(2 * LIMIT16 - v); + } + + return [(v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.SHORT = constant(2); + +// Convert a 24-bit unsigned integer to a list of 3 bytes. +encode.UINT24 = function(v) { + return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.UINT24 = constant(3); + +// Convert a 32-bit unsigned integer to a list of 4 bytes. +encode.ULONG = function(v) { + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.ULONG = constant(4); + +// Convert a 32-bit unsigned integer to a list of 4 bytes. +encode.LONG = function(v) { + // Two's complement + if (v >= LIMIT32) { + v = -(2 * LIMIT32 - v); + } + + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.LONG = constant(4); + +encode.FIXED = encode.ULONG; +sizeOf.FIXED = sizeOf.ULONG; + +encode.FWORD = encode.SHORT; +sizeOf.FWORD = sizeOf.SHORT; + +encode.UFWORD = encode.USHORT; +sizeOf.UFWORD = sizeOf.USHORT; + +// FIXME Implement LONGDATETIME +encode.LONGDATETIME = function() { + return [0, 0, 0, 0, 0, 0, 0, 0]; +}; + +sizeOf.LONGDATETIME = constant(8); + +// Convert a 4-char tag to a list of 4 bytes. +encode.TAG = function(v) { + check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); + return [v.charCodeAt(0), + v.charCodeAt(1), + v.charCodeAt(2), + v.charCodeAt(3)]; +}; + +sizeOf.TAG = constant(4); + +// CFF data types /////////////////////////////////////////////////////////// + +encode.Card8 = encode.BYTE; +sizeOf.Card8 = sizeOf.BYTE; + +encode.Card16 = encode.USHORT; +sizeOf.Card16 = sizeOf.USHORT; + +encode.OffSize = encode.BYTE; +sizeOf.OffSize = sizeOf.BYTE; + +encode.SID = encode.USHORT; +sizeOf.SID = sizeOf.USHORT; + +// Convert a numeric operand or charstring number to a variable-size list of bytes. +encode.NUMBER = function(v) { + if (v >= -107 && v <= 107) { + return [v + 139]; + } else if (v >= 108 && v <= 1131) { + v = v - 108; + return [(v >> 8) + 247, v & 0xFF]; + } else if (v >= -1131 && v <= -108) { + v = -v - 108; + return [(v >> 8) + 251, v & 0xFF]; + } else if (v >= -32768 && v <= 32767) { + return encode.NUMBER16(v); + } else { + return encode.NUMBER32(v); + } +}; + +sizeOf.NUMBER = function(v) { + return encode.NUMBER(v).length; +}; + +// Convert a signed number between -32768 and +32767 to a three-byte value. +// This ensures we always use three bytes, but is not the most compact format. +encode.NUMBER16 = function(v) { + return [28, (v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.NUMBER16 = constant(2); + +// Convert a signed number between -(2^31) and +(2^31-1) to a four-byte value. +// This is useful if you want to be sure you always use four bytes, +// at the expense of wasting a few bytes for smaller numbers. +encode.NUMBER32 = function(v) { + return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.NUMBER32 = constant(4); + +encode.REAL = function(v) { + var value = v.toString(); + + // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) + // This code converts it back to a number without the epsilon. + var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); + if (m) { + var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); + value = (Math.round(v * epsilon) / epsilon).toString(); + } + + var nibbles = ''; + var i; + var ii; + for (i = 0, ii = value.length; i < ii; i += 1) { + var c = value[i]; + if (c === 'e') { + nibbles += value[++i] === '-' ? 'c' : 'b'; + } else if (c === '.') { + nibbles += 'a'; + } else if (c === '-') { + nibbles += 'e'; + } else { + nibbles += c; + } + } + + nibbles += (nibbles.length & 1) ? 'f' : 'ff'; + var out = [30]; + for (i = 0, ii = nibbles.length; i < ii; i += 2) { + out.push(parseInt(nibbles.substr(i, 2), 16)); + } + + return out; +}; + +sizeOf.REAL = function(v) { + return encode.REAL(v).length; +}; + +encode.NAME = encode.CHARARRAY; +sizeOf.NAME = sizeOf.CHARARRAY; + +encode.STRING = encode.CHARARRAY; +sizeOf.STRING = sizeOf.CHARARRAY; + +// Convert a ASCII string to a list of UTF16 bytes. +encode.UTF16 = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + b.push(0); + b.push(v.charCodeAt(i)); + } + + return b; +}; + +sizeOf.UTF16 = function(v) { + return v.length * 2; +}; + +// Convert a list of values to a CFF INDEX structure. +// The values should be objects containing name / type / value. +encode.INDEX = function(l) { + var i; + //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, + // dataSize, i, v; + // Because we have to know which data type to use to encode the offsets, + // we have to go through the values twice: once to encode the data and + // calculate the offets, then again to encode the offsets using the fitting data type. + var offset = 1; // First offset is always 1. + var offsets = [offset]; + var data = []; + var dataSize = 0; + for (i = 0; i < l.length; i += 1) { + var v = encode.OBJECT(l[i]); + Array.prototype.push.apply(data, v); + dataSize += v.length; + offset += v.length; + offsets.push(offset); + } + + if (data.length === 0) { + return [0, 0]; + } + + var encodedOffsets = []; + var offSize = (1 + Math.floor(Math.log(dataSize) / Math.log(2)) / 8) | 0; + var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; + for (i = 0; i < offsets.length; i += 1) { + var encodedOffset = offsetEncoder(offsets[i]); + Array.prototype.push.apply(encodedOffsets, encodedOffset); + } + + return Array.prototype.concat(encode.Card16(l.length), + encode.OffSize(offSize), + encodedOffsets, + data); +}; + +sizeOf.INDEX = function(v) { + return encode.INDEX(v).length; +}; + +// Convert an object to a CFF DICT structure. +// The keys should be numeric. +// The values should be objects containing name / type / value. +encode.DICT = function(m) { + var d = []; + var keys = Object.keys(m); + var length = keys.length; + + for (var i = 0; i < length; i += 1) { + // Object.keys() return string keys, but our keys are always numeric. + var k = parseInt(keys[i], 0); + var v = m[k]; + // Value comes before the key. + d = d.concat(encode.OPERAND(v.value, v.type)); + d = d.concat(encode.OPERATOR(k)); + } + + return d; +}; + +sizeOf.DICT = function(m) { + return encode.DICT(m).length; +}; + +encode.OPERATOR = function(v) { + if (v < 1200) { + return [v]; + } else { + return [12, v - 1200]; + } +}; + +encode.OPERAND = function(v, type) { + var d = []; + if (Array.isArray(type)) { + for (var i = 0; i < type.length; i += 1) { + check.argument(v.length === type.length, 'Not enough arguments given for type' + type); + d = d.concat(encode.OPERAND(v[i], type[i])); + } + } else { + if (type === 'SID') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'offset') { + // We make it easy for ourselves and always encode offsets as + // 4 bytes. This makes offset calculation for the top dict easier. + d = d.concat(encode.NUMBER32(v)); + } else if (type === 'number') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'real') { + d = d.concat(encode.REAL(v)); + } else { + throw new Error('Unknown operand type ' + type); + // FIXME Add support for booleans + } + } + + return d; +}; + +encode.OP = encode.BYTE; +sizeOf.OP = sizeOf.BYTE; + +// memoize charstring encoding using WeakMap if available +var wmm = typeof WeakMap === 'function' && new WeakMap(); +// Convert a list of CharString operations to bytes. +encode.CHARSTRING = function(ops) { + if (wmm && wmm.has(ops)) { + return wmm.get(ops); + } + + var d = []; + var length = ops.length; + + for (var i = 0; i < length; i += 1) { + var op = ops[i]; + d = d.concat(encode[op.type](op.value)); + } + + if (wmm) { + wmm.set(ops, d); + } + + return d; +}; + +sizeOf.CHARSTRING = function(ops) { + return encode.CHARSTRING(ops).length; +}; + +// Utility functions //////////////////////////////////////////////////////// + +// Convert an object containing name / type / value to bytes. +encode.OBJECT = function(v) { + var encodingFunction = encode[v.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); + return encodingFunction(v.value); +}; + +// Convert a table object to bytes. +// A table contains a list of fields containing the metadata (name, type and default value). +// The table itself has the field values set as attributes. +encode.TABLE = function(table) { + var d = []; + var length = table.fields.length; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var encodingFunction = encode[field.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + var bytes = encodingFunction(value); + d = d.concat(bytes); + } + + return d; +}; + +// Merge in a list of bytes. +encode.LITERAL = function(v) { + return v; +}; + +sizeOf.LITERAL = function(v) { + return v.length; +}; + +exports.decode = decode; +exports.encode = encode; +exports.sizeOf = sizeOf; + +},{"./check":2}],27:[function(_dereq_,module,exports){ +/*! + * Reqwest! A general purpose XHR connection manager + * license MIT (c) Dustin Diaz 2014 + * https://github.com/ded/reqwest + */ + +!function (name, context, definition) { + if (typeof module != 'undefined' && module.exports) module.exports = definition() + else if (typeof define == 'function' && define.amd) define(definition) + else context[name] = definition() +}('reqwest', this, function () { + + var win = window + , doc = document + , httpsRe = /^http/ + , protocolRe = /(^\w+):\/\// + , twoHundo = /^(20\d|1223)$/ //http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request + , byTag = 'getElementsByTagName' + , readyState = 'readyState' + , contentType = 'Content-Type' + , requestedWith = 'X-Requested-With' + , head = doc[byTag]('head')[0] + , uniqid = 0 + , callbackPrefix = 'reqwest_' + (+new Date()) + , lastValue // data stored by the most recent JSONP callback + , xmlHttpRequest = 'XMLHttpRequest' + , xDomainRequest = 'XDomainRequest' + , noop = function () {} + + , isArray = typeof Array.isArray == 'function' + ? Array.isArray + : function (a) { + return a instanceof Array + } + + , defaultHeaders = { + 'contentType': 'application/x-www-form-urlencoded' + , 'requestedWith': xmlHttpRequest + , 'accept': { + '*': 'text/javascript, text/html, application/xml, text/xml, */*' + , 'xml': 'application/xml, text/xml' + , 'html': 'text/html' + , 'text': 'text/plain' + , 'json': 'application/json, text/javascript' + , 'js': 'application/javascript, text/javascript' + } + } + + , xhr = function(o) { + // is it x-domain + if (o['crossOrigin'] === true) { + var xhr = win[xmlHttpRequest] ? new XMLHttpRequest() : null + if (xhr && 'withCredentials' in xhr) { + return xhr + } else if (win[xDomainRequest]) { + return new XDomainRequest() + } else { + throw new Error('Browser does not support cross-origin requests') + } + } else if (win[xmlHttpRequest]) { + return new XMLHttpRequest() + } else { + return new ActiveXObject('Microsoft.XMLHTTP') + } + } + , globalSetupOptions = { + dataFilter: function (data) { + return data + } + } + + function succeed(r) { + var protocol = protocolRe.exec(r.url); + protocol = (protocol && protocol[1]) || window.location.protocol; + return httpsRe.test(protocol) ? twoHundo.test(r.request.status) : !!r.request.response; + } + + function handleReadyState(r, success, error) { + return function () { + // use _aborted to mitigate against IE err c00c023f + // (can't read props on aborted request objects) + if (r._aborted) return error(r.request) + if (r._timedOut) return error(r.request, 'Request is aborted: timeout') + if (r.request && r.request[readyState] == 4) { + r.request.onreadystatechange = noop + if (succeed(r)) success(r.request) + else + error(r.request) + } + } + } + + function setHeaders(http, o) { + var headers = o['headers'] || {} + , h + + headers['Accept'] = headers['Accept'] + || defaultHeaders['accept'][o['type']] + || defaultHeaders['accept']['*'] + + var isAFormData = typeof FormData === 'function' && (o['data'] instanceof FormData); + // breaks cross-origin requests with legacy browsers + if (!o['crossOrigin'] && !headers[requestedWith]) headers[requestedWith] = defaultHeaders['requestedWith'] + if (!headers[contentType] && !isAFormData) headers[contentType] = o['contentType'] || defaultHeaders['contentType'] + for (h in headers) + headers.hasOwnProperty(h) && 'setRequestHeader' in http && http.setRequestHeader(h, headers[h]) + } + + function setCredentials(http, o) { + if (typeof o['withCredentials'] !== 'undefined' && typeof http.withCredentials !== 'undefined') { + http.withCredentials = !!o['withCredentials'] + } + } + + function generalCallback(data) { + lastValue = data + } + + function urlappend (url, s) { + return url + (/\?/.test(url) ? '&' : '?') + s + } + + function handleJsonp(o, fn, err, url) { + var reqId = uniqid++ + , cbkey = o['jsonpCallback'] || 'callback' // the 'callback' key + , cbval = o['jsonpCallbackName'] || reqwest.getcallbackPrefix(reqId) + , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)') + , match = url.match(cbreg) + , script = doc.createElement('script') + , loaded = 0 + , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1 + + if (match) { + if (match[3] === '?') { + url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name + } else { + cbval = match[3] // provided callback func name + } + } else { + url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em + } + + win[cbval] = generalCallback + + script.type = 'text/javascript' + script.src = url + script.async = true + if (typeof script.onreadystatechange !== 'undefined' && !isIE10) { + // need this for IE due to out-of-order onreadystatechange(), binding script + // execution to an event listener gives us control over when the script + // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html + script.htmlFor = script.id = '_reqwest_' + reqId + } + + script.onload = script.onreadystatechange = function () { + if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) { + return false + } + script.onload = script.onreadystatechange = null + script.onclick && script.onclick() + // Call the user callback with the last value stored and clean up values and scripts. + fn(lastValue) + lastValue = undefined + head.removeChild(script) + loaded = 1 + } + + // Add the script to the DOM head + head.appendChild(script) + + // Enable JSONP timeout + return { + abort: function () { + script.onload = script.onreadystatechange = null + err({}, 'Request is aborted: timeout', {}) + lastValue = undefined + head.removeChild(script) + loaded = 1 + } + } + } + + function getRequest(fn, err) { + var o = this.o + , method = (o['method'] || 'GET').toUpperCase() + , url = typeof o === 'string' ? o : o['url'] + // convert non-string objects to query-string form unless o['processData'] is false + , data = (o['processData'] !== false && o['data'] && typeof o['data'] !== 'string') + ? reqwest.toQueryString(o['data']) + : (o['data'] || null) + , http + , sendWait = false + + // if we're working on a GET request and we have data then we should append + // query string to end of URL and not post data + if ((o['type'] == 'jsonp' || method == 'GET') && data) { + url = urlappend(url, data) + data = null + } + + if (o['type'] == 'jsonp') return handleJsonp(o, fn, err, url) + + // get the xhr from the factory if passed + // if the factory returns null, fall-back to ours + http = (o.xhr && o.xhr(o)) || xhr(o) + + http.open(method, url, o['async'] === false ? false : true) + setHeaders(http, o) + setCredentials(http, o) + if (win[xDomainRequest] && http instanceof win[xDomainRequest]) { + http.onload = fn + http.onerror = err + // NOTE: see + // http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/30ef3add-767c-4436-b8a9-f1ca19b4812e + http.onprogress = function() {} + sendWait = true + } else { + http.onreadystatechange = handleReadyState(this, fn, err) + } + o['before'] && o['before'](http) + if (sendWait) { + setTimeout(function () { + http.send(data) + }, 200) + } else { + http.send(data) + } + return http + } + + function Reqwest(o, fn) { + this.o = o + this.fn = fn + + init.apply(this, arguments) + } + + function setType(header) { + // json, javascript, text/plain, text/html, xml + if (header.match('json')) return 'json' + if (header.match('javascript')) return 'js' + if (header.match('text')) return 'html' + if (header.match('xml')) return 'xml' + } + + function init(o, fn) { + + this.url = typeof o == 'string' ? o : o['url'] + this.timeout = null + + // whether request has been fulfilled for purpose + // of tracking the Promises + this._fulfilled = false + // success handlers + this._successHandler = function(){} + this._fulfillmentHandlers = [] + // error handlers + this._errorHandlers = [] + // complete (both success and fail) handlers + this._completeHandlers = [] + this._erred = false + this._responseArgs = {} + + var self = this + + fn = fn || function () {} + + if (o['timeout']) { + this.timeout = setTimeout(function () { + timedOut() + }, o['timeout']) + } + + if (o['success']) { + this._successHandler = function () { + o['success'].apply(o, arguments) + } + } + + if (o['error']) { + this._errorHandlers.push(function () { + o['error'].apply(o, arguments) + }) + } + + if (o['complete']) { + this._completeHandlers.push(function () { + o['complete'].apply(o, arguments) + }) + } + + function complete (resp) { + o['timeout'] && clearTimeout(self.timeout) + self.timeout = null + while (self._completeHandlers.length > 0) { + self._completeHandlers.shift()(resp) + } + } + + function success (resp) { + var type = o['type'] || resp && setType(resp.getResponseHeader('Content-Type')) // resp can be undefined in IE + resp = (type !== 'jsonp') ? self.request : resp + // use global data filter on response text + var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type) + , r = filteredResponse + try { + resp.responseText = r + } catch (e) { + // can't assign this in IE<=8, just ignore + } + if (r) { + switch (type) { + case 'json': + try { + resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')') + } catch (err) { + return error(resp, 'Could not parse JSON in response', err) + } + break + case 'js': + resp = eval(r) + break + case 'html': + resp = r + break + case 'xml': + resp = resp.responseXML + && resp.responseXML.parseError // IE trololo + && resp.responseXML.parseError.errorCode + && resp.responseXML.parseError.reason + ? null + : resp.responseXML + break + } + } + + self._responseArgs.resp = resp + self._fulfilled = true + fn(resp) + self._successHandler(resp) + while (self._fulfillmentHandlers.length > 0) { + resp = self._fulfillmentHandlers.shift()(resp) + } + + complete(resp) + } + + function timedOut() { + self._timedOut = true + self.request.abort() + } + + function error(resp, msg, t) { + resp = self.request + self._responseArgs.resp = resp + self._responseArgs.msg = msg + self._responseArgs.t = t + self._erred = true + while (self._errorHandlers.length > 0) { + self._errorHandlers.shift()(resp, msg, t) + } + complete(resp) + } + + this.request = getRequest.call(this, success, error) + } + + Reqwest.prototype = { + abort: function () { + this._aborted = true + this.request.abort() + } + + , retry: function () { + init.call(this, this.o, this.fn) + } + + /** + * Small deviation from the Promises A CommonJs specification + * http://wiki.commonjs.org/wiki/Promises/A + */ + + /** + * `then` will execute upon successful requests + */ + , then: function (success, fail) { + success = success || function () {} + fail = fail || function () {} + if (this._fulfilled) { + this._responseArgs.resp = success(this._responseArgs.resp) + } else if (this._erred) { + fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) + } else { + this._fulfillmentHandlers.push(success) + this._errorHandlers.push(fail) + } + return this + } + + /** + * `always` will execute whether the request succeeds or fails + */ + , always: function (fn) { + if (this._fulfilled || this._erred) { + fn(this._responseArgs.resp) + } else { + this._completeHandlers.push(fn) + } + return this + } + + /** + * `fail` will execute when the request fails + */ + , fail: function (fn) { + if (this._erred) { + fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) + } else { + this._errorHandlers.push(fn) + } + return this + } + , 'catch': function (fn) { + return this.fail(fn) + } + } + + function reqwest(o, fn) { + return new Reqwest(o, fn) + } + + // normalize newline variants according to spec -> CRLF + function normalize(s) { + return s ? s.replace(/\r?\n/g, '\r\n') : '' + } + + function serial(el, cb) { + var n = el.name + , t = el.tagName.toLowerCase() + , optCb = function (o) { + // IE gives value="" even where there is no value attribute + // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273 + if (o && !o['disabled']) + cb(n, normalize(o['attributes']['value'] && o['attributes']['value']['specified'] ? o['value'] : o['text'])) + } + , ch, ra, val, i + + // don't serialize elements that are disabled or without a name + if (el.disabled || !n) return + + switch (t) { + case 'input': + if (!/reset|button|image|file/i.test(el.type)) { + ch = /checkbox/i.test(el.type) + ra = /radio/i.test(el.type) + val = el.value + // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here + ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val)) + } + break + case 'textarea': + cb(n, normalize(el.value)) + break + case 'select': + if (el.type.toLowerCase() === 'select-one') { + optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null) + } else { + for (i = 0; el.length && i < el.length; i++) { + el.options[i].selected && optCb(el.options[i]) + } + } + break + } + } + + // collect up all form elements found from the passed argument elements all + // the way down to child elements; pass a '<form>' or form fields. + // called with 'this'=callback to use for serial() on each element + function eachFormElement() { + var cb = this + , e, i + , serializeSubtags = function (e, tags) { + var i, j, fa + for (i = 0; i < tags.length; i++) { + fa = e[byTag](tags[i]) + for (j = 0; j < fa.length; j++) serial(fa[j], cb) + } + } + + for (i = 0; i < arguments.length; i++) { + e = arguments[i] + if (/input|select|textarea/i.test(e.tagName)) serial(e, cb) + serializeSubtags(e, [ 'input', 'select', 'textarea' ]) + } + } + + // standard query string style serialization + function serializeQueryString() { + return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments)) + } + + // { 'name': 'value', ... } style serialization + function serializeHash() { + var hash = {} + eachFormElement.apply(function (name, value) { + if (name in hash) { + hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]]) + hash[name].push(value) + } else hash[name] = value + }, arguments) + return hash + } + + // [ { name: 'name', value: 'value' }, ... ] style serialization + reqwest.serializeArray = function () { + var arr = [] + eachFormElement.apply(function (name, value) { + arr.push({name: name, value: value}) + }, arguments) + return arr + } + + reqwest.serialize = function () { + if (arguments.length === 0) return '' + var opt, fn + , args = Array.prototype.slice.call(arguments, 0) + + opt = args.pop() + opt && opt.nodeType && args.push(opt) && (opt = null) + opt && (opt = opt.type) + + if (opt == 'map') fn = serializeHash + else if (opt == 'array') fn = reqwest.serializeArray + else fn = serializeQueryString + + return fn.apply(null, args) + } + + reqwest.toQueryString = function (o, trad) { + var prefix, i + , traditional = trad || false + , s = [] + , enc = encodeURIComponent + , add = function (key, value) { + // If value is a function, invoke it and return its value + value = ('function' === typeof value) ? value() : (value == null ? '' : value) + s[s.length] = enc(key) + '=' + enc(value) + } + // If an array was passed in, assume that it is an array of form elements. + if (isArray(o)) { + for (i = 0; o && i < o.length; i++) add(o[i]['name'], o[i]['value']) + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for (prefix in o) { + if (o.hasOwnProperty(prefix)) buildParams(prefix, o[prefix], traditional, add) + } + } + + // spaces should be + according to spec + return s.join('&').replace(/%20/g, '+') + } + + function buildParams(prefix, obj, traditional, add) { + var name, i, v + , rbracket = /\[\]$/ + + if (isArray(obj)) { + // Serialize array item. + for (i = 0; obj && i < obj.length; i++) { + v = obj[i] + if (traditional || rbracket.test(prefix)) { + // Treat each array item as a scalar. + add(prefix, v) + } else { + buildParams(prefix + '[' + (typeof v === 'object' ? i : '') + ']', v, traditional, add) + } + } + } else if (obj && obj.toString() === '[object Object]') { + // Serialize object item. + for (name in obj) { + buildParams(prefix + '[' + name + ']', obj[name], traditional, add) + } + + } else { + // Serialize scalar item. + add(prefix, obj) + } + } + + reqwest.getcallbackPrefix = function () { + return callbackPrefix + } + + // jQuery and Zepto compatibility, differences can be remapped here so you can call + // .ajax.compat(options, callback) + reqwest.compat = function (o, fn) { + if (o) { + o['type'] && (o['method'] = o['type']) && delete o['type'] + o['dataType'] && (o['type'] = o['dataType']) + o['jsonpCallback'] && (o['jsonpCallbackName'] = o['jsonpCallback']) && delete o['jsonpCallback'] + o['jsonp'] && (o['jsonpCallback'] = o['jsonp']) + } + return new Reqwest(o, fn) + } + + reqwest.ajaxSetup = function (options) { + options = options || {} + for (var k in options) { + globalSetupOptions[k] = options[k] + } + } + + return reqwest +}); + +},{}],28:[function(_dereq_,module,exports){ +/** + * @module Shape + * @submodule 3D Primitives + * @for p5 + * @requires core + * @requires p5.Geometry3D + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +_dereq_('./p5.Geometry3D'); + +/** + * Draw a plane with given a width and height + * @method plane + * @param {Number} width width of the plane + * @param {Number} height height of the plane + * @return {p5} the p5 object + * @example + * <div> + * <code> + * //draw a plane with width 200 and height 200 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * plane(200, 200); + * } + * </code> + * </div> + */ +p5.prototype.plane = function(width, height){ + + width = width || 50; + height = height || 50; + + //details for plane are highly optional + var detailX = typeof arguments[2] === Number ? arguments[2] : 1; + var detailY = typeof arguments[3] === Number ? arguments[3] : 1; + + var gId = 'plane|'+width+'|'+height+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createPlane = function(u, v){ + var x = 2 * width * u - width; + var y = 2 * height * v - height; + var z = 0; + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createPlane, detailX, detailY); + + var obj = geometry3d.generateObj(); + + this._renderer.initBuffer(gId, [obj]); + + } + + this._renderer.drawBuffer(gId); + +}; + +/** + * Draw a sphere with given raduis + * @method sphere + * @param {Number} radius radius of circle + * @param {Number} [detail] number of segments, + * the more segments the smoother geometry + * default is 24. Avoid detail number above + * 150, it may crash the browser. + * @example + * <div> + * <code> + * // draw a sphere with radius 200 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.sphere = function(radius, detail){ + + radius = radius || 50; + + var detailX = detail || 24; + var detailY = detail || 16; + + var gId = 'sphere|'+radius+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createSphere = function(u, v){ + var theta = 2 * Math.PI * u; + var phi = Math.PI * v - Math.PI / 2; + var x = radius * Math.cos(phi) * Math.sin(theta); + var y = radius * Math.sin(phi); + var z = radius * Math.cos(phi) * Math.cos(theta); + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createSphere, detailX, detailY); + + var obj = geometry3d.generateObj(true, true); + + this._renderer.initBuffer(gId, [obj]); + } + + this._renderer.drawBuffer(gId); + + return this; +}; + +/** + * Draw an ellipsoid with given radius + * @method ellipsoid + * @param {Number} radiusx xradius of circle + * @param {Number} radiusy yradius of circle + * @param {Number} radiusz zradius of circle + * @param {Number} [detail] Number of segments. + * The more segments, the smoother the + * geometry (default is 24). Avoid detail + * number above 150. It may crash the + * browser. + * @return {p5} the p5 object + * @example + * <div> + * <code> + * // draw an ellipsoid with radius 200, 300 and 400 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * ellipsoid(200,300,400); + * } + * </code> + * </div> + */ +p5.prototype.ellipsoid = function(radiusx, radiusy, radiusz, detail){ + + radiusx = radiusx || 50; + radiusy = radiusy || 50; + radiusz = radiusz || 50; + + var detailX = detail || 24; + var detailY = detail || 24; + + var gId = 'ellipsoid|'+radiusx+'|'+radiusy+ + '|'+radiusz+'|'+detailX+'|'+detailY; + + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createEllipsoid = function(u, v){ + var theta = 2 * Math.PI * u; + var phi = Math.PI * v - Math.PI / 2; + var x = radiusx * Math.cos(phi) * Math.sin(theta); + var y = radiusy * Math.sin(phi); + var z = radiusz * Math.cos(phi) * Math.cos(theta); + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createEllipsoid, detailX, detailY); + + var obj = geometry3d.generateObj(true, true); + + this._renderer.initBuffer(gId, [obj]); + } + + this._renderer.drawBuffer(gId); + + return this; +}; + +/** + * Draw a cylinder with given radius and height + * @method cylinder + * @param {Number} radius radius of the surface + * @param {Number} height height of the cylinder + * @param {Number} [detail] number of segments, + * the more segments the smoother geometry + * default is 24. Avoid detail number above + * 150. It may crash the browser. + * @return {p5} the p5 object + * @example + * <div> + * <code> + * //draw a spinning cylinder with radius 200 and height 200 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * rotateX(frameCount * 0.01); + * rotateZ(frameCount * 0.01); + * cylinder(200, 200); + * } + * </code> + * </div> + */ +p5.prototype.cylinder = function(radius, height, detail){ + + radius = radius || 50; + height = height || 50; + + var detailX = detail || 24; + var detailY = detail || 16; + + var gId = 'cylinder|'+radius+'|'+height+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createCylinder = function(u, v){ + var theta = 2 * Math.PI * u; + var x = radius * Math.sin(theta); + var y = 2 * height * v - height; + var z = radius * Math.cos(theta); + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createCylinder, detailX, detailY); + var obj = geometry3d.generateObj(true); + + var createTop = function(u, v){ + var theta = 2 * Math.PI * u; + var x = radius * Math.sin(-theta); + var y = height; + var z = radius * Math.cos(theta); + if(v === 0){ + return new p5.Vector(0, height, 0); + } + else{ + return new p5.Vector(x, y, z); + } + }; + + var geometry3d1 = new p5.Geometry3D(); + geometry3d1.parametricGeometry( + createTop, detailX, 1); + var obj1 = geometry3d1.generateObj(); + + var createBottom = function(u, v){ + var theta = 2 * Math.PI * u; + var x = radius * Math.sin(theta); + var y = -height; + var z = radius * Math.cos(theta); + if(v === 0){ + return new p5.Vector(0, -height, 0); + }else{ + return new p5.Vector(x, y, z); + } + }; + + var geometry3d2 = new p5.Geometry3D(); + geometry3d2.parametricGeometry( + createBottom, detailX, 1); + var obj2 = geometry3d2.generateObj(); + + + this._renderer.initBuffer(gId, [obj, obj1, obj2]); + } + + this._renderer.drawBuffer(gId); + + return this; +}; + + +/** + * Draw a cone with given radius and height + * @method cone + * @param {Number} radius radius of the bottom surface + * @param {Number} height height of the cone + * @param {Number} [detail] number of segments, + * the more segments the smoother geometry + * default is 24. Avoid detail number above + * 150. It may crash the browser. + * @example + * <div> + * <code> + * //draw a spinning cone with radius 200 and height 200 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * rotateX(frameCount * 0.01); + * rotateZ(frameCount * 0.01); + * cone(200, 200); + * } + * </code> + * </div> + */ +p5.prototype.cone = function(radius, height, detail){ + + radius = radius || 50; + height = height || 50; + + var detailX = detail || 24; + var detailY = detail || 16; + + var gId = 'cone|'+radius+'|'+height+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createCone = function(u, v){ + var theta = 2 * Math.PI * u; + var x = radius * (1 - v) * Math.sin(theta); + var y = 2 * height * v - height; + var z = radius * (1 - v) * Math.cos(theta); + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createCone, detailX, detailY); + var obj = geometry3d.generateObj(true); + + var geometry3d1 = new p5.Geometry3D(); + var createBottom = function(u, v){ + var theta = 2 * Math.PI * u; + var x = radius * (1 - v) * Math.sin(-theta); + var y = -height; + var z = radius * (1 - v) * Math.cos(theta); + return new p5.Vector(x, y, z); + }; + + geometry3d1.parametricGeometry( + createBottom, detailX, 1); + var obj1 = geometry3d1.generateObj(); + + this._renderer.initBuffer(gId, [obj, obj1]); + } + + this._renderer.drawBuffer(gId); + + return this; +}; + + +/** + * Draw a torus with given radius and tube radius + * @method torus + * @param {Number} radius radius of the whole ring + * @param {Number} tubeRadius radius of the tube + * @param {Number} [detail] number of segments, + * the more segments the smoother geometry + * default is 24. Avoid detail number above + * 150. It may crash the browser. + * @example + * <div> + * <code> + * //draw a spinning torus with radius 200 and tube radius 60 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * torus(200, 60); + * } + * </code> + * </div> + */ +p5.prototype.torus = function(radius, tubeRadius, detail){ + + radius = radius || 50; + tubeRadius = tubeRadius || 10; + + var detailX = detail || 24; + var detailY = detail || 16; + + var gId = 'torus|'+radius+'|'+tubeRadius+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createTorus = function(u, v){ + var theta = 2 * Math.PI * u; + var phi = 2 * Math.PI * v; + var x = (radius + tubeRadius * Math.cos(phi)) * Math.cos(theta); + var y = (radius + tubeRadius * Math.cos(phi)) * Math.sin(theta); + var z = tubeRadius * Math.sin(phi); + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createTorus, detailX, detailY); + + var obj = geometry3d.generateObj(true); + + this._renderer.initBuffer(gId, [obj]); + } + + this._renderer.drawBuffer(gId); + + return this; +}; + +/** + * Draw a box with given width, height and depth + * @method box + * @param {Number} width width of the box + * @param {Number} height height of the box + * @param {Number} depth depth of the box + * @return {p5} the p5 object + * @example + * <div> + * <code> + * //draw a spinning box with width, height and depth 200 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * box(200, 200, 200); + * } + * </code> + * </div> + */ +p5.prototype.box = function(width, height, depth){ + + width = width || 50; + height = height || width; + depth = depth || width; + + //details for box are highly optional + var detailX = typeof arguments[3] === Number ? arguments[3] : 1; + var detailY = typeof arguments[4] === Number ? arguments[4] : 1; + + var gId = 'cube|'+width+'|'+height+'|'+depth+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createPlane1 = function(u, v){ + var x = 2 * width * u - width; + var y = 2 * height * v - height; + var z = depth; + return new p5.Vector(x, y, z); + }; + var createPlane2 = function(u, v){ + var x = 2 * width * ( 1 - u ) - width; + var y = 2 * height * v - height; + var z = -depth; + return new p5.Vector(x, y, z); + }; + var createPlane3 = function(u, v){ + var x = 2 * width * ( 1 - u ) - width; + var y = height; + var z = 2 * depth * v - depth; + return new p5.Vector(x, y, z); + }; + var createPlane4 = function(u, v){ + var x = 2 * width * u - width; + var y = -height; + var z = 2 * depth * v - depth; + return new p5.Vector(x, y, z); + }; + var createPlane5 = function(u, v){ + var x = width; + var y = 2 * height * u - height; + var z = 2 * depth * v - depth; + return new p5.Vector(x, y, z); + }; + var createPlane6 = function(u, v){ + var x = -width; + var y = 2 * height * ( 1 - u ) - height; + var z = 2 * depth * v - depth; + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry( + createPlane1, detailX, detailY, geometry3d.vertices.length); + geometry3d.parametricGeometry( + createPlane2, detailX, detailY, geometry3d.vertices.length); + geometry3d.parametricGeometry( + createPlane3, detailX, detailY, geometry3d.vertices.length); + geometry3d.parametricGeometry( + createPlane4, detailX, detailY, geometry3d.vertices.length); + geometry3d.parametricGeometry( + createPlane5, detailX, detailY, geometry3d.vertices.length); + geometry3d.parametricGeometry( + createPlane6, detailX, detailY, geometry3d.vertices.length); + + var obj = geometry3d.generateObj(); + + this._renderer.initBuffer(gId, [obj]); + } + + this._renderer.drawBuffer(gId); + + return this; + +}; + +module.exports = p5; +},{"../core/core":48,"./p5.Geometry3D":34}],29:[function(_dereq_,module,exports){ +/** + * @module Lights, Camera + * @submodule Camera + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Sets camera position + * @method camera + * @param {Number} x camera postion value on x axis + * @param {Number} y camera postion value on y axis + * @param {Number} z camera postion value on z axis + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * //move the camera away from the plane by a sin wave + * camera(0, 0, sin(frameCount * 0.01) * 100); + * plane(120, 120); + * } + * </code> + * </div> + */ +p5.prototype.camera = function(x, y, z){ + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'camera', + args, + ['Number', 'Number', 'Number'] + ); + //what it manipulates is the model view matrix + this._renderer.translate(-x, -y, -z); +}; + +/** + * Sets perspective camera + * @method perspective + * @param {Number} fovy camera frustum vertical field of view, + * from bottom to top of view, in degrees + * @param {Number} aspect camera frustum aspect ratio + * @param {Number} near frustum near plane length + * @param {Number} far frustum far plane length + * @return {p5} the p5 object + * @example + * <div> + * <code> + * //drag mouse to toggle the world! + * //you will see there's a vanish point + * function setup(){ + * createCanvas(100, 100, WEBGL); + * perspective(60 / 180 * PI, width/height, 0.1, 100); + * } + * function draw(){ + * background(200); + * orbitControl(); + * for(var i = -1; i < 2; i++){ + * for(var j = -2; j < 3; j++){ + * push(); + * translate(i*160, 0, j*160); + * box(40, 40, 40); + * pop(); + * } + * } + * } + * </code> + * </div> + */ +p5.prototype.perspective = function(fovy,aspect,near,far) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'perspective', + args, + ['Number', 'Number', 'Number', 'Number'] + ); + this._renderer.uPMatrix = p5.Matrix.identity(); + this._renderer.uPMatrix.perspective(fovy,aspect,near,far); + this._renderer._setCamera = true; +}; + +/** + * Setup ortho camera + * @method ortho + * @param {Number} left camera frustum left plane + * @param {Number} right camera frustum right plane + * @param {Number} bottom camera frustum bottom plane + * @param {Number} top camera frustum top plane + * @param {Number} near camera frustum near plane + * @param {Number} far camera frustum far plane + * @return {p5} the p5 object + * @example + * <div> + * <code> + * //drag mouse to toggle the world! + * //there's no vanish point + * function setup(){ + * createCanvas(100, 100, WEBGL); + * ortho(-width/2, width/2, height/2, -height/2, 0.1, 100); + * } + * function draw(){ + * background(200); + * orbitControl(); + * for(var i = -1; i < 2; i++){ + * for(var j = -2; j < 3; j++){ + * push(); + * translate(i*160, 0, j*160); + * box(40, 40, 40); + * pop(); + * } + * } + * } + * </code> + * </div> + */ +p5.prototype.ortho = function(left,right,bottom,top,near,far) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'ortho', + args, + ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'] + ); + left /= this.width; + right /= this.width; + top /= this.height; + bottom /= this.height; + this._renderer.uPMatrix = p5.Matrix.identity(); + this._renderer.uPMatrix.ortho(left,right,bottom,top,near,far); + this._renderer._setCamera = true; +}; + +module.exports = p5; + +},{"../core/core":48}],30:[function(_dereq_,module,exports){ +//@TODO: documentation of immediate mode + +'use strict'; + +var p5 = _dereq_('../core/core'); + +////////////////////////////////////////////// +// _primitives2D in 3D space +////////////////////////////////////////////// + +p5.Renderer3D.prototype._primitives2D = function(arr){ + this._setDefaultCamera(); + var gl = this.GL; + var shaderProgram = this._getColorVertexShader(); + + //create vertice buffer + var vertexPositionBuffer = this.verticeBuffer; + gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer); + + gl.bufferData( + gl.ARRAY_BUFFER, new Float32Array(arr), gl.STATIC_DRAW); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, + 3, gl.FLOAT, false, 0, 0); + + //create vertexcolor buffer + var vertexColorBuffer = this.colorBuffer; + gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer); + var color = this._getCurColor(); + var colors = []; + for(var i = 0; i < arr.length / 3; i++){ + colors = colors.concat(color); + } + + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); + gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, + 4, gl.FLOAT, false, 0, 0); + + //matrix + var mId = 'vertexColorVert|vertexColorFrag'; + this.setMatrixUniforms(mId); +}; + +p5.Renderer3D.prototype.point = function(x, y, z){ + var gl = this.GL; + this._primitives2D([x, y, z]); + gl.drawArrays(gl.POINTS, 0, 1); + return this; +}; + +p5.Renderer3D.prototype.line = function(x1, y1, z1, x2, y2, z2){ + var gl = this.GL; + this._primitives2D([x1, y1, z1, x2, y2, z2]); + gl.drawArrays(gl.LINES, 0, 2); + return this; +}; + +p5.Renderer3D.prototype.triangle = function +(x1, y1, z1, x2, y2, z2, x3, y3, z3){ + var gl = this.GL; + this._primitives2D([x1, y1, z1, x2, y2, z2, x3, y3, z3]); + this._strokeCheck(); + gl.drawArrays(gl.TRIANGLES, 0, 3); + return this; +}; + +//@TODO: how to define the order of 4 points +p5.Renderer3D.prototype.quad = function +(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4){ + var gl = this.GL; + this._primitives2D( + [x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4]); + this._strokeCheck(); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + return this; +}; + +p5.Renderer3D.prototype.beginShape = function(mode){ + this.shapeMode = mode; + this.verticeStack = []; + return this; +}; + +p5.Renderer3D.prototype.vertex = function(x, y, z){ + this.verticeStack.push(x, y, z); + return this; +}; + +p5.Renderer3D.prototype.endShape = function(){ + var gl = this.GL; + this._primitives2D(this.verticeStack); + this.verticeStack = []; + + switch(this.shapeMode){ + case 'POINTS': + gl.drawArrays(gl.POINTS, 0, 1); + break; + case 'LINES': + gl.drawArrays(gl.LINES, 0, 2); + break; + case 'TRIANGLES': + this._strokeCheck(); + gl.drawArrays(gl.TRIANGLES, 0, 3); + break; + case 'TRIANGLE_STRIP': + this._strokeCheck(); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + break; + default: + this._strokeCheck(); + gl.drawArrays(gl.TRIANGLES, 0, 3); + break; + } + return this; +}; + +//@TODO: figure out how to actually do stroke on shapes in 3D +p5.Renderer3D.prototype._strokeCheck = function(){ + if(this.drawMode === 'stroke'){ + throw new Error( + 'stroke for shapes in 3D not yet implemented, use fill for now :(' + ); + } +}; + +//@TODO +p5.Renderer3D.prototype.strokeWeight = function() { + throw new Error('strokeWeight for 3d not yet implemented'); +}; + +////////////////////////////////////////////// +// COLOR +////////////////////////////////////////////// + +p5.Renderer3D.prototype.fill = function(r, g, b, a) { + var color = this._pInst.color.apply(this, arguments); + var colorNormalized = color._array; + this.curColor = colorNormalized; + this.drawMode = 'fill'; + return this; +}; + +p5.Renderer3D.prototype.stroke = function(r, g, b, a) { + var color = this._pInst.color.apply(this, arguments); + var colorNormalized = color._array; + this.curColor = colorNormalized; + this.drawMode = 'stroke'; + return this; +}; + +p5.Renderer3D.prototype._getColorVertexShader = function(){ + var gl = this.GL; + var mId = 'vertexColorVert|vertexColorFrag'; + var shaderProgram; + + if(!this.materialInHash(mId)){ + shaderProgram = + this.initShaders('vertexColorVert', 'vertexColorFrag', true); + this.mHash[mId] = shaderProgram; + shaderProgram.vertexColorAttribute = + gl.getAttribLocation(shaderProgram, 'aVertexColor'); + gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute); + }else{ + shaderProgram = this.mHash[mId]; + } + return shaderProgram; +}; + +module.exports = p5.Renderer3D; + +},{"../core/core":48}],31:[function(_dereq_,module,exports){ +'use strict'; + +var p5 = _dereq_('../core/core'); + +//@TODO: implement full orbit controls including +//pan, zoom, quaternion rotation, etc. +p5.prototype.orbitControl = function(){ + if(this.mouseIsPressed){ + this.rotateY((this.mouseX - this.width / 2) / (this.width / 2)); + this.rotateX((this.mouseY - this.height / 2) / (this.width / 2)); + } + return this; +}; + +module.exports = p5; +},{"../core/core":48}],32:[function(_dereq_,module,exports){ +/** + * @module Lights, Camera + * @submodule Lights + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Creates an ambient light with a color + * @method ambientLight + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * background(0); + * ambientLight(150); + * ambientMaterial(250); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.ambientLight = function(v1, v2, v3, a){ + var gl = this._renderer.GL; + var shaderProgram = this._renderer._getShader( + 'lightVert', 'lightTextureFrag'); + + gl.useProgram(shaderProgram); + shaderProgram.uAmbientColor = gl.getUniformLocation( + shaderProgram, + 'uAmbientColor[' + this._renderer.ambientLightCount + ']'); + + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, arguments); + var colors = color._array; + + gl.uniform3f( shaderProgram.uAmbientColor, + colors[0], colors[1], colors[2]); + + //in case there's no material color for the geometry + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + gl.uniform4f( shaderProgram.uMaterialColor, 1, 1, 1, 1); + + this._renderer.ambientLightCount ++; + shaderProgram.uAmbientLightCount = + gl.getUniformLocation(shaderProgram, 'uAmbientLightCount'); + gl.uniform1i(shaderProgram.uAmbientLightCount, + this._renderer.ambientLightCount); + + return this; +}; + +/** + * Creates a directional light with a color and a direction + * @method directionalLight + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity + * @param {Number|p5.Vector} x x axis direction or a p5.Vector + * @param {Number} [y] optional: y axis direction + * @param {Number} [z] optional: z axis direction + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * background(0); + * //move your mouse to change light direction + * var dirX = (mouseX / width - 0.5) *2; + * var dirY = (mouseY / height - 0.5) *(-2); + * directionalLight(250, 250, 250, dirX, dirY, 0.25); + * ambientMaterial(250); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.directionalLight = function(v1, v2, v3, a, x, y, z) { + // TODO(jgessner): Find an example using this and profile it. + // var args = new Array(arguments.length); + // for (var i = 0; i < args.length; ++i) { + // args[i] = arguments[i]; + // } + // this._validateParameters( + // 'directionalLight', + // args, + // [ + // //rgbaxyz + // ['Number', 'Number', 'Number', 'Number', 'Number', 'Number', 'Number'], + // //rgbxyz + // ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'], + // //caxyz + // ['Number', 'Number', 'Number', 'Number', 'Number'], + // //cxyz + // ['Number', 'Number', 'Number', 'Number'], + // ['String', 'Number', 'Number', 'Number'], + // ['Array', 'Number', 'Number', 'Number'], + // ['Object', 'Number', 'Number', 'Number'], + // //rgbavector + // ['Number', 'Number', 'Number', 'Number', 'Object'], + // //rgbvector + // ['Number', 'Number', 'Number', 'Object'], + // //cavector + // ['Number', 'Number', 'Object'], + // //cvector + // ['Number', 'Object'], + // ['String', 'Object'], + // ['Array', 'Object'], + // ['Object', 'Object'] + // ] + // ); + + var gl = this._renderer.GL; + var shaderProgram = this._renderer._getShader( + 'lightVert', 'lightTextureFrag'); + + gl.useProgram(shaderProgram); + shaderProgram.uDirectionalColor = gl.getUniformLocation( + shaderProgram, + 'uDirectionalColor[' + this._renderer.directionalLightCount + ']'); + + //@TODO: check parameters number + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, [v1, v2, v3]); + var colors = color._array; + + gl.uniform3f( shaderProgram.uDirectionalColor, + colors[0], colors[1], colors[2]); + + var _x, _y, _z; + + if(typeof arguments[arguments.length-1] === 'number'){ + _x = arguments[arguments.length-3]; + _y = arguments[arguments.length-2]; + _z = arguments[arguments.length-1]; + + }else{ + try{ + _x = arguments[arguments.length-1].x; + _y = arguments[arguments.length-1].y; + _z = arguments[arguments.length-1].z; + } + catch(error){ + throw error; + } + } + + shaderProgram.uLightingDirection = gl.getUniformLocation( + shaderProgram, + 'uLightingDirection[' + this._renderer.directionalLightCount + ']'); + gl.uniform3f( shaderProgram.uLightingDirection, _x, _y, _z); + + //in case there's no material color for the geometry + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + gl.uniform4f( shaderProgram.uMaterialColor, 1, 1, 1, 1); + + this._renderer.directionalLightCount ++; + shaderProgram.uDirectionalLightCount = + gl.getUniformLocation(shaderProgram, 'uDirectionalLightCount'); + gl.uniform1i(shaderProgram.uDirectionalLightCount, + this._renderer.directionalLightCount); + + return this; +}; + +/** + * Creates a point light with a color and a light position + * @method pointLight + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity + * @param {Number|p5.Vector} x x axis position or a p5.Vector + * @param {Number} [y] optional: y axis position + * @param {Number} [z] optional: z axis position + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * background(0); + * //move your mouse to change light position + * var locY = (mouseY / height - 0.5) *(-2); + * var locX = (mouseX / width - 0.5) *2; + * //to set the light position, + * //think of the world's coordinate as: + * // -1,1 -------- 1,1 + * // | | + * // | | + * // | | + * // -1,-1---------1,-1 + * pointLight(250, 250, 250, locX, locY, 0); + * ambientMaterial(250); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.pointLight = function(v1, v2, v3, a, x, y, z) { + // TODO(jgessner): Find an example using this and profile it. + // var args = new Array(arguments.length); + // for (var i = 0; i < args.length; ++i) { + // args[i] = arguments[i]; + // } + // this._validateParameters( + // 'pointLight', + // arguments, + // [ + // //rgbaxyz + // ['Number', 'Number', 'Number', 'Number', 'Number', 'Number', 'Number'], + // //rgbxyz + // ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'], + // //caxyz + // ['Number', 'Number', 'Number', 'Number', 'Number'], + // //cxyz + // ['Number', 'Number', 'Number', 'Number'], + // ['String', 'Number', 'Number', 'Number'], + // ['Array', 'Number', 'Number', 'Number'], + // ['Object', 'Number', 'Number', 'Number'], + // //rgbavector + // ['Number', 'Number', 'Number', 'Number', 'Object'], + // //rgbvector + // ['Number', 'Number', 'Number', 'Object'], + // //cavector + // ['Number', 'Number', 'Object'], + // //cvector + // ['Number', 'Object'], + // ['String', 'Object'], + // ['Array', 'Object'], + // ['Object', 'Object'] + // ] + // ); + + var gl = this._renderer.GL; + var shaderProgram = this._renderer._getShader( + 'lightVert', 'lightTextureFrag'); + + gl.useProgram(shaderProgram); + shaderProgram.uPointLightColor = gl.getUniformLocation( + shaderProgram, + 'uPointLightColor[' + this._renderer.pointLightCount + ']'); + + //@TODO: check parameters number + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, [v1, v2, v3]); + var colors = color._array; + + gl.uniform3f( shaderProgram.uPointLightColor, + colors[0], colors[1], colors[2]); + + var _x, _y, _z; + + if(typeof arguments[arguments.length-1] === 'number'){ + _x = arguments[arguments.length-3]; + _y = arguments[arguments.length-2]; + _z = arguments[arguments.length-1]; + + }else{ + try{ + _x = arguments[arguments.length-1].x; + _y = arguments[arguments.length-1].y; + _z = arguments[arguments.length-1].z; + } + catch(error){ + throw error; + } + } + + shaderProgram.uPointLightLocation = gl.getUniformLocation( + shaderProgram, + 'uPointLightLocation[' + this._renderer.pointLightCount + ']'); + gl.uniform3f( shaderProgram.uPointLightLocation, _x, _y, _z); + + //in case there's no material color for the geometry + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + gl.uniform4f( shaderProgram.uMaterialColor, 1, 1, 1, 1); + + this._renderer.pointLightCount ++; + shaderProgram.uPointLightCount = + gl.getUniformLocation(shaderProgram, 'uPointLightCount'); + gl.uniform1i(shaderProgram.uPointLightCount, + this._renderer.pointLightCount); + + return this; +}; + +module.exports = p5; + +},{"../core/core":48}],33:[function(_dereq_,module,exports){ +/** + * @module Lights, Camera + * @submodule Material + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +//require('./p5.Texture'); + +/** + * Normal material for geometry + * @method normalMaterial + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(0); + * normalMaterial(); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.normalMaterial = function(){ + this._renderer._getShader('normalVert', 'normalFrag'); + return this; +}; + +/** + * Texture for geometry + * @method texture + * @return {p5} the p5 object + * @example + * <div> + * <code> + * var img; + * function setup(){ + * createCanvas(100, 100, WEBGL); + * img = loadImage("assets/laDefense.jpg"); + * } + * + * function draw(){ + * background(0); + * rotateZ(frameCount * 0.01); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * //pass image as texture + * texture(img); + * box(200, 200, 200); + * } + * </code> + * </div> + */ +p5.prototype.texture = function(image){ + var gl = this._renderer.GL; + var shaderProgram = this._renderer._getShader('lightVert', + 'lightTextureFrag'); + gl.useProgram(shaderProgram); + if (image instanceof p5.Image) { + //check if image is already used as texture + if(!image.isTexture){ + //createTexture and set isTexture to true + var tex = gl.createTexture(); + image.createTexture(tex); + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); + image._setProperty('isTexture', true); + } + //otherwise we're good to bind texture without creating + //a new one on the gl + else { + //TODO + } + image.loadPixels(); + var data = new Uint8Array(image.pixels); + gl.texImage2D(gl.TEXTURE_2D, 0, + gl.RGBA, image.width, image.height, + 0, gl.RGBA, gl.UNSIGNED_BYTE, data); + } + //if param is a video + else if (image instanceof p5.MediaElement){ + if(!image.loadedmetadata) {return;} + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, + gl.UNSIGNED_BYTE, image.elt); + } + else { + //@TODO handle following cases: + //- 2D canvas (p5 inst) + } + if (_isPowerOf2(image.width) && _isPowerOf2(image.height)) { + gl.generateMipmap(gl.TEXTURE_2D); + } else { + //@TODO this is problematic + //image.width = _nextHighestPOT(image.width); + //image.height = _nextHighestPOT(image.height); + gl.texParameteri(gl.TEXTURE_2D, + gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, + gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, + gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, + gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + } + //this is where we'd activate multi textures + //eg. gl.activeTexture(gl.TEXTURE0 + (unit || 0)); + //but for now we just have a single texture. + //@TODO need to extend this functionality + //gl.activeTexture(gl.TEXTURE0 + 0); + //gl.bindTexture(gl.TEXTURE_2D, tex); + gl.uniform1i(gl.getUniformLocation(shaderProgram, 'uSampler'), 0); + gl.uniform1i(gl.getUniformLocation(shaderProgram, 'isTexture'), true); + return this; +}; + +/** + * Helper functions; Checks whether val is a pot + * more info on power of 2 here: + * https://www.opengl.org/wiki/NPOT_Texture + * @param {Number} value + * @return {Boolean} + */ +function _isPowerOf2 (value){ + return (value & (value - 1)) === 0; +} + +/** + * returns the next highest power of 2 value + * @param {Number} value [description] + * @return {Number} [description] + */ +// function _nextHighestPOT (value){ +// --value; +// for (var i = 1; i < 32; i <<= 1) { +// value = value | value >> i; +// } +// return value + 1; +// } + +/** + * Basic material for geometry with a given color + * @method basicMaterial + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(0); + * basicMaterial(250, 0, 0); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * rotateZ(frameCount * 0.01); + * box(200, 200, 200); + * } + * </code> + * </div> + */ +p5.prototype.basicMaterial = function(v1, v2, v3, a){ + var gl = this._renderer.GL; + + var shaderProgram = this._renderer._getShader('normalVert', 'basicFrag'); + + gl.useProgram(shaderProgram); + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, arguments); + var colors = color._array; + + gl.uniform4f( shaderProgram.uMaterialColor, + colors[0], colors[1], colors[2], colors[3]); + + return this; + +}; + +/** + * Ambient material for geometry with a given color + * @method ambientMaterial + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity +* @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * background(0); + * ambientLight(100); + * pointLight(250, 250, 250, 100, 100, 0); + * ambientMaterial(250); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.ambientMaterial = function(v1, v2, v3, a) { + var gl = this._renderer.GL; + var shaderProgram = + this._renderer._getShader('lightVert', 'lightTextureFrag'); + + gl.useProgram(shaderProgram); + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, arguments); + var colors = color._array; + + gl.uniform4f(shaderProgram.uMaterialColor, + colors[0], colors[1], colors[2], colors[3]); + + shaderProgram.uSpecular = gl.getUniformLocation( + shaderProgram, 'uSpecular' ); + gl.uniform1i(shaderProgram.uSpecular, false); + + gl.uniform1i(gl.getUniformLocation(shaderProgram, 'isTexture'), false); + + return this; +}; + +/** + * Specular material for geometry with a given color + * @method specularMaterial + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * background(0); + * ambientLight(100); + * pointLight(250, 250, 250, 100, 100, 0); + * specularMaterial(250); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.specularMaterial = function(v1, v2, v3, a) { + var gl = this._renderer.GL; + var shaderProgram = + this._renderer._getShader('lightVert', 'lightTextureFrag'); + gl.uniform1i(gl.getUniformLocation(shaderProgram, 'isTexture'), false); + gl.useProgram(shaderProgram); + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, arguments); + var colors = color._array; + + gl.uniform4f(shaderProgram.uMaterialColor, + colors[0], colors[1], colors[2], colors[3]); + + shaderProgram.uSpecular = gl.getUniformLocation( + shaderProgram, 'uSpecular' ); + gl.uniform1i(shaderProgram.uSpecular, true); + + return this; +}; + +module.exports = p5; + +},{"../core/core":48}],34:[function(_dereq_,module,exports){ +//some of the functions are adjusted from Three.js(http://threejs.org) + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * p5 Geometry3D class + */ +p5.Geometry3D = function(){ + //an array holding every vertice + //each vertex is a p5.Vector + this.vertices = []; + //an array holding each normals for each vertice + //each normal is a p5.Vector + this.vertexNormals = []; + //an array holding each three indecies of vertices that form a face + //[[0, 1, 2], [1, 2, 3], ...] + this.faces = []; + //an array holding every noraml for each face + //each faceNormal is a p5.Vector + //[[p5.Vector, p5.Vector, p5.Vector],[p5.Vector, p5.Vector, p5.Vector],...] + this.faceNormals = []; + //an array of array holding uvs (group according to faces) + //[[[0, 0], [1, 0], [1, 0]],...] + this.uvs = []; +}; + +/** + * generate geometriy with parametric method + * @param {Function} func callback function for how to generate geometry + * @param {Number} detailX number of vertices on horizontal surface + * @param {Number} detailY number of vertices on horizontal surface + * @param {Number} offset offset of vertices index + */ +p5.Geometry3D.prototype.parametricGeometry = function +//@TODO: put func as the last parameters +(func, detailX, detailY, offset){ + + var i, j, p; + var u, v; + offset = offset || 0; + this.detailX = detailX; + this.detailY = detailY; + + var sliceCount = detailX + 1; + for (i = 0; i <= detailY; i++){ + v = i / detailY; + for (j = 0; j <= detailX; j++){ + u = j / detailX; + p = func(u, v); + this.vertices.push(p); + } + } + + var a, b, c, d; + var uva, uvb, uvc, uvd; + + for (i = 0; i < detailY; i++){ + for (j = 0; j < detailX; j++){ + a = i * sliceCount + j + offset; + b = i * sliceCount + j + 1 + offset; + c = (i + 1)* sliceCount + j + 1 + offset; + d = (i + 1)* sliceCount + j + offset; + + uva = [j/detailX, i/detailY]; + uvb = [(j + 1)/ detailX, i/detailY]; + uvc = [(j + 1)/ detailX, (i + 1)/detailY]; + uvd = [j/detailX, (i + 1)/detailY]; + + this.faces.push([a, b, d]); + this.uvs.push([uva, uvb, uvd]); + + this.faces.push([b, c, d]); + this.uvs.push([uvb, uvc, uvd]); + } + } +}; + +/** + * compute faceNormals for a geometry + */ +p5.Geometry3D.prototype.computeFaceNormals = function(){ + + var cb = new p5.Vector(); + var ab = new p5.Vector(); + + for (var f = 0; f < this.faces.length; f++){ + var face = this.faces[f]; + var vA = this.vertices[face[0]]; + var vB = this.vertices[face[1]]; + var vC = this.vertices[face[2]]; + + p5.Vector.sub(vC, vB, cb); + p5.Vector.sub(vA, vB, ab); + + var normal = p5.Vector.cross(ab, cb); + normal.normalize(); + normal.mult(-1); + this.faceNormals[f] = normal; + } + +}; + +/** + * compute vertexNormals for a geometry + */ +p5.Geometry3D.prototype.computeVertexNormals = function (){ + + var v, f, face, faceNormal, vertices; + var vertexNormals = []; + + vertices = new Array(this.vertices.length); + for (v = 0; v < this.vertices.length; v++) { + vertices[v] = new p5.Vector(); + } + + for (f = 0; f < this.faces.length; f++) { + face = this.faces[f]; + faceNormal = this.faceNormals[f]; + + vertices[face[0]].add(faceNormal); + vertices[face[1]].add(faceNormal); + vertices[face[2]].add(faceNormal); + } + + for (v = 0; v < this.vertices.length; v++) { + vertices[v].normalize(); + } + + for (f = 0; f < this.faces.length; f++) { + face = this.faces[f]; + vertexNormals[f] = []; + vertexNormals[f][0]= vertices[face[0]].copy(); + vertexNormals[f][1]= vertices[face[1]].copy(); + vertexNormals[f][2]= vertices[face[2]].copy(); + } + + for (f = 0; f < this.faces.length; f++){ + face = this.faces[f]; + faceNormal = this.faceNormals[f]; + this.vertexNormals[face[0]] = vertexNormals[f][0]; + this.vertexNormals[face[1]] = vertexNormals[f][1]; + this.vertexNormals[face[2]] = vertexNormals[f][2]; + } + +}; + +p5.Geometry3D.prototype.averageNormals = function() { + + for(var i = 0; i <= this.detailY; i++){ + var offset = this.detailX + 1; + var temp = p5.Vector + .add(this.vertexNormals[i*offset], + this.vertexNormals[i*offset + this.detailX]); + temp = p5.Vector.div(temp, 2); + this.vertexNormals[i*offset] = temp; + this.vertexNormals[i*offset + this.detailX] = temp; + } +}; + +p5.Geometry3D.prototype.averagePoleNormals = function() { + + //average the north pole + var sum = new p5.Vector(0, 0, 0); + for(var i = 0; i < this.detailX; i++){ + sum.add(this.vertexNormals[i]); + } + sum = p5.Vector.div(sum, this.detailX); + + for(i = 0; i < this.detailX; i++){ + this.vertexNormals[i] = sum; + } + + //average the south pole + sum = new p5.Vector(0, 0, 0); + for(i = this.vertices.length - 1; + i > this.vertices.length - 1 - this.detailX; i--){ + sum.add(this.vertexNormals[i]); + } + sum = p5.Vector.div(sum, this.detailX); + + for(i = this.vertices.length - 1; + i > this.vertices.length - 1 - this.detailX; i--){ + this.vertexNormals[i] = sum; + } +}; + +/** + * [generateUV description] + * @param {Array} faces [description] + * @param {Array} uvs [description] + */ +p5.Geometry3D.prototype.generateUV = function(faces, uvs){ + + faces = flatten(faces); + uvs = flatten(uvs); + var arr = []; + faces.forEach(function(item, index){ + arr[item] = uvs[index]; + }); + return flatten(arr); +}; + + +/** + * generate an object containing information needed to create buffer + */ +p5.Geometry3D.prototype.generateObj = function(average, sphere){ + + this.computeFaceNormals(); + this.computeVertexNormals(); + + if(average){ + this.averageNormals(); + } + + if(sphere){ + this.averagePoleNormals(); + } + + var obj = { + vertices: turnVectorArrayIntoNumberArray(this.vertices), + vertexNormals: turnVectorArrayIntoNumberArray(this.vertexNormals), + uvs: this.generateUV(this.faces, this.uvs), + faces: flatten(this.faces), + len: this.faces.length * 3 + }; + return obj; +}; + +/** + * turn a two dimensional array into one dimensional array + * @param {Array} arr 2-dimensional array + * @return {Array} 1-dimensional array + * [[1, 2, 3],[4, 5, 6]] -> [1, 2, 3, 4, 5, 6] + */ +function flatten(arr){ + return arr.reduce(function(a, b){ + return a.concat(b); + }); +} + +/** + * turn an array of Vector into a one dimensional array of numbers + * @param {Array} arr an array of p5.Vector + * @return {Array]} a one dimensional array of numbers + * [p5.Vector(1, 2, 3), p5.Vector(4, 5, 6)] -> + * [1, 2, 3, 4, 5, 6] + */ +function turnVectorArrayIntoNumberArray(arr){ + return flatten(arr.map(function(item){ + return [item.x, item.y, item.z]; + })); +} + +module.exports = p5.Geometry3D; +},{"../core/core":48}],35:[function(_dereq_,module,exports){ +/** +* @requires constants +* @todo see methods below needing further implementation. +* future consideration: implement SIMD optimizations +* when browser compatibility becomes available +* https://developer.mozilla.org/en-US/docs/Web/JavaScript/ +* Reference/Global_Objects/SIMD +*/ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var polarGeometry = _dereq_('../math/polargeometry'); +var constants = _dereq_('../core/constants'); +var GLMAT_ARRAY_TYPE = ( + typeof Float32Array !== 'undefined') ? + Float32Array : Array; + +/** + * A class to describe a 4x4 matrix + * for model and view matrix manipulation in the p5js webgl renderer. + * class p5.Matrix + * @constructor + * @param {Array} [mat4] array literal of our 4x4 matrix + */ +p5.Matrix = function() { + // This is how it comes in with createMatrix() + if(arguments[0] instanceof p5) { + // save reference to p5 if passed in + this.p5 = arguments[0]; + this.mat4 = arguments[1] || new GLMAT_ARRAY_TYPE([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]); + // This is what we'll get with new p5.Matrix() + // a mat4 identity matrix + } else { + this.mat4 = arguments[0] || new GLMAT_ARRAY_TYPE([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]); + } + return this; +}; + +/** + * Sets the x, y, and z component of the vector using two or three separate + * variables, the data from a p5.Matrix, or the values from a float array. + * + * @param {p5.Matrix|Array} [inMatrix] the input p5.Matrix or + * an Array of length 16 + */ +p5.Matrix.prototype.set = function (inMatrix) { + if (inMatrix instanceof p5.Matrix) { + this.mat4 = inMatrix.mat4; + return this; + } + else if (inMatrix instanceof GLMAT_ARRAY_TYPE) { + this.mat4 = inMatrix; + return this; + } + return this; +}; + +/** + * Gets a copy of the vector, returns a p5.Matrix object. + * + * @return {p5.Matrix} the copy of the p5.Matrix object + */ +p5.Matrix.prototype.get = function () { + return new p5.Matrix(this.mat4); +}; + +/** + * return a copy of a matrix + * @return {p5.Matrix} the result matrix + */ +p5.Matrix.prototype.copy = function(){ + var copied = new p5.Matrix(); + copied.mat4[0] = this.mat4[0]; + copied.mat4[1] = this.mat4[1]; + copied.mat4[2] = this.mat4[2]; + copied.mat4[3] = this.mat4[3]; + copied.mat4[4] = this.mat4[4]; + copied.mat4[5] = this.mat4[5]; + copied.mat4[6] = this.mat4[6]; + copied.mat4[7] = this.mat4[7]; + copied.mat4[8] = this.mat4[8]; + copied.mat4[9] = this.mat4[9]; + copied.mat4[10] = this.mat4[10]; + copied.mat4[11] = this.mat4[11]; + copied.mat4[12] = this.mat4[12]; + copied.mat4[13] = this.mat4[13]; + copied.mat4[14] = this.mat4[14]; + copied.mat4[15] = this.mat4[15]; + return copied; +}; + +/** + * return an identity matrix + * @return {p5.Matrix} the result matrix + */ +p5.Matrix.identity = function(){ + return new p5.Matrix(); +}; + +/** + * transpose according to a given matrix + * @param {p5.Matrix | Typed Array} a the matrix to be based on to transpose + * @return {p5.Matrix} this + */ +p5.Matrix.prototype.transpose = function(a){ + var a01, a02, a03, a12, a13, a23; + if(a instanceof p5.Matrix){ + a01 = a.mat4[1]; + a02 = a.mat4[2]; + a03 = a.mat4[3]; + a12 = a.mat4[6]; + a13 = a.mat4[7]; + a23 = a.mat4[11]; + + this.mat4[0] = a.mat4[0]; + this.mat4[1] = a.mat4[4]; + this.mat4[2] = a.mat4[8]; + this.mat4[3] = a.mat4[12]; + this.mat4[4] = a01; + this.mat4[5] = a.mat4[5]; + this.mat4[6] = a.mat4[9]; + this.mat4[7] = a.mat4[13]; + this.mat4[8] = a02; + this.mat4[9] = a12; + this.mat4[10] = a.mat4[10]; + this.mat4[11] = a.mat4[14]; + this.mat4[12] = a03; + this.mat4[13] = a13; + this.mat4[14] = a23; + this.mat4[15] = a.mat4[15]; + + }else if(a instanceof GLMAT_ARRAY_TYPE){ + a01 = a[1]; + a02 = a[2]; + a03 = a[3]; + a12 = a[6]; + a13 = a[7]; + a23 = a[11]; + + this.mat4[0] = a[0]; + this.mat4[1] = a[4]; + this.mat4[2] = a[8]; + this.mat4[3] = a[12]; + this.mat4[4] = a01; + this.mat4[5] = a[5]; + this.mat4[6] = a[9]; + this.mat4[7] = a[13]; + this.mat4[8] = a02; + this.mat4[9] = a12; + this.mat4[10] = a[10]; + this.mat4[11] = a[14]; + this.mat4[12] = a03; + this.mat4[13] = a13; + this.mat4[14] = a23; + this.mat4[15] = a[15]; + } + return this; +}; + +/** + * invert matrix according to a give matrix + * @param {p5.Matrix or Typed Array} a the matrix to be based on to invert + * @return {p5.Matrix} this + */ +p5.Matrix.prototype.invert = function(a){ + var a00, a01, a02, a03, a10, a11, a12, a13, + a20, a21, a22, a23, a30, a31, a32, a33; + if(a instanceof p5.Matrix){ + a00 = a.mat4[0]; + a01 = a.mat4[1]; + a02 = a.mat4[2]; + a03 = a.mat4[3]; + a10 = a.mat4[4]; + a11 = a.mat4[5]; + a12 = a.mat4[6]; + a13 = a.mat4[7]; + a20 = a.mat4[8]; + a21 = a.mat4[9]; + a22 = a.mat4[10]; + a23 = a.mat4[11]; + a30 = a.mat4[12]; + a31 = a.mat4[13]; + a32 = a.mat4[14]; + a33 = a.mat4[15]; + }else if(a instanceof GLMAT_ARRAY_TYPE){ + a00 = a[0]; + a01 = a[1]; + a02 = a[2]; + a03 = a[3]; + a10 = a[4]; + a11 = a[5]; + a12 = a[6]; + a13 = a[7]; + a20 = a[8]; + a21 = a[9]; + a22 = a[10]; + a23 = a[11]; + a30 = a[12]; + a31 = a[13]; + a32 = a[14]; + a33 = a[15]; + } + var b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + // Calculate the determinant + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - + b04 * b07 + b05 * b06; + + if (!det) { + return null; + } + det = 1.0 / det; + + this.mat4[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + this.mat4[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + this.mat4[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + this.mat4[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; + this.mat4[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + this.mat4[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + this.mat4[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + this.mat4[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; + this.mat4[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + this.mat4[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + this.mat4[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + this.mat4[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; + this.mat4[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; + this.mat4[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; + this.mat4[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; + this.mat4[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; + + return this; +}; + +/** + * inspired by Toji's mat4 determinant + * @return {Number} Determinant of our 4x4 matrix + */ +p5.Matrix.prototype.determinant = function(){ + var d00 = (this.mat4[0] * this.mat4[5]) - (this.mat4[1] * this.mat4[4]), + d01 = (this.mat4[0] * this.mat4[6]) - (this.mat4[2] * this.mat4[4]), + d02 = (this.mat4[0] * this.mat4[7]) - (this.mat4[3] * this.mat4[4]), + d03 = (this.mat4[1] * this.mat4[6]) - (this.mat4[2] * this.mat4[5]), + d04 = (this.mat4[1] * this.mat4[7]) - (this.mat4[3] * this.mat4[5]), + d05 = (this.mat4[2] * this.mat4[7]) - (this.mat4[3] * this.mat4[6]), + d06 = (this.mat4[8] * this.mat4[13]) - (this.mat4[9] * this.mat4[12]), + d07 = (this.mat4[8] * this.mat4[14]) - (this.mat4[10] * this.mat4[12]), + d08 = (this.mat4[8] * this.mat4[15]) - (this.mat4[11] * this.mat4[12]), + d09 = (this.mat4[9] * this.mat4[14]) - (this.mat4[10] * this.mat4[13]), + d10 = (this.mat4[9] * this.mat4[15]) - (this.mat4[11] * this.mat4[13]), + d11 = (this.mat4[10] * this.mat4[15]) - (this.mat4[11] * this.mat4[14]); + + // Calculate the determinant + return d00 * d11 - d01 * d10 + d02 * d09 + + d03 * d08 - d04 * d07 + d05 * d06; +}; + +/** + * multiply two mat4s + * @param {p5.Matrix | Array} multMatrix The matrix we want to multiply by + * @return {p5.Matrix} this + */ +p5.Matrix.prototype.mult = function(multMatrix){ + var _dest = new GLMAT_ARRAY_TYPE(16); + var _src = new GLMAT_ARRAY_TYPE(16); + + if(multMatrix instanceof p5.Matrix) { + _src = multMatrix.mat4; + } + else if(multMatrix instanceof GLMAT_ARRAY_TYPE){ + _src = multMatrix; + } + + // each row is used for the multiplier + var b0 = this.mat4[0], b1 = this.mat4[1], + b2 = this.mat4[2], b3 = this.mat4[3]; + _dest[0] = b0*_src[0] + b1*_src[4] + b2*_src[8] + b3*_src[12]; + _dest[1] = b0*_src[1] + b1*_src[5] + b2*_src[9] + b3*_src[13]; + _dest[2] = b0*_src[2] + b1*_src[6] + b2*_src[10] + b3*_src[14]; + _dest[3] = b0*_src[3] + b1*_src[7] + b2*_src[11] + b3*_src[15]; + + b0 = this.mat4[4]; + b1 = this.mat4[5]; + b2 = this.mat4[6]; + b3 = this.mat4[7]; + _dest[4] = b0*_src[0] + b1*_src[4] + b2*_src[8] + b3*_src[12]; + _dest[5] = b0*_src[1] + b1*_src[5] + b2*_src[9] + b3*_src[13]; + _dest[6] = b0*_src[2] + b1*_src[6] + b2*_src[10] + b3*_src[14]; + _dest[7] = b0*_src[3] + b1*_src[7] + b2*_src[11] + b3*_src[15]; + + b0 = this.mat4[8]; + b1 = this.mat4[9]; + b2 = this.mat4[10]; + b3 = this.mat4[11]; + _dest[8] = b0*_src[0] + b1*_src[4] + b2*_src[8] + b3*_src[12]; + _dest[9] = b0*_src[1] + b1*_src[5] + b2*_src[9] + b3*_src[13]; + _dest[10] = b0*_src[2] + b1*_src[6] + b2*_src[10] + b3*_src[14]; + _dest[11] = b0*_src[3] + b1*_src[7] + b2*_src[11] + b3*_src[15]; + + b0 = this.mat4[12]; + b1 = this.mat4[13]; + b2 = this.mat4[14]; + b3 = this.mat4[15]; + _dest[12] = b0*_src[0] + b1*_src[4] + b2*_src[8] + b3*_src[12]; + _dest[13] = b0*_src[1] + b1*_src[5] + b2*_src[9] + b3*_src[13]; + _dest[14] = b0*_src[2] + b1*_src[6] + b2*_src[10] + b3*_src[14]; + _dest[15] = b0*_src[3] + b1*_src[7] + b2*_src[11] + b3*_src[15]; + + this.mat4 = _dest; + + return this; +}; + +/** + * scales a p5.Matrix by scalars or a vector + * @param {p5.Vector | Array } + * vector to scale by + * @return {p5.Matrix} this + */ +p5.Matrix.prototype.scale = function() { + var x,y,z; + var args = new Array(arguments.length); + for(var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + //if our 1st arg is a type p5.Vector + if (args[0] instanceof p5.Vector){ + x = args[0].x; + y = args[0].y; + z = args[0].z; + } + //otherwise if it's an array + else if (args[0] instanceof Array){ + x = args[0][0]; + y = args[0][1]; + z = args[0][2]; + } + var _dest = new GLMAT_ARRAY_TYPE(16); + _dest[0] = this.mat4[0] * x; + _dest[1] = this.mat4[1] * x; + _dest[2] = this.mat4[2] * x; + _dest[3] = this.mat4[3] * x; + _dest[4] = this.mat4[4] * y; + _dest[5] = this.mat4[5] * y; + _dest[6] = this.mat4[6] * y; + _dest[7] = this.mat4[7] * y; + _dest[8] = this.mat4[8] * z; + _dest[9] = this.mat4[9] * z; + _dest[10] = this.mat4[10] * z; + _dest[11] = this.mat4[11] * z; + _dest[12] = this.mat4[12]; + _dest[13] = this.mat4[13]; + _dest[14] = this.mat4[14]; + _dest[15] = this.mat4[15]; + + this.mat4 = _dest; + return this; +}; + +/** + * rotate our Matrix around an axis by the given angle. + * @param {Number} a The angle of rotation in radians + * @param {p5.Vector | Array} axis the axis(es) to rotate around + * @return {p5.Matrix} this + * inspired by Toji's gl-matrix lib, mat4 rotation + */ +p5.Matrix.prototype.rotate = function(a, axis){ + var x, y, z, _a, len; + + if (this.p5) { + if (this.p5._angleMode === constants.DEGREES) { + _a = polarGeometry.degreesToRadians(a); + } + } + else { + _a = a; + } + if (axis instanceof p5.Vector) { + x = axis.x; + y = axis.y; + z = axis.z; + } + else if (axis instanceof Array) { + x = axis[0]; + y = axis[1]; + z = axis[2]; + } + + len = Math.sqrt(x * x + y * y + z * z); + x *= (1/len); + y *= (1/len); + z *= (1/len); + + var a00 = this.mat4[0]; + var a01 = this.mat4[1]; + var a02 = this.mat4[2]; + var a03 = this.mat4[3]; + var a10 = this.mat4[4]; + var a11 = this.mat4[5]; + var a12 = this.mat4[6]; + var a13 = this.mat4[7]; + var a20 = this.mat4[8]; + var a21 = this.mat4[9]; + var a22 = this.mat4[10]; + var a23 = this.mat4[11]; + + //sin,cos, and tan of respective angle + var sA = Math.sin(_a); + var cA = Math.cos(_a); + var tA = 1 - cA; + // Construct the elements of the rotation matrix + var b00 = x * x * tA + cA; + var b01 = y * x * tA + z * sA; + var b02 = z * x * tA - y * sA; + var b10 = x * y * tA - z * sA; + var b11 = y * y * tA + cA; + var b12 = z * y * tA + x * sA; + var b20 = x * z * tA + y * sA; + var b21 = y * z * tA - x * sA; + var b22 = z * z * tA + cA; + + // rotation-specific matrix multiplication + this.mat4[0] = a00 * b00 + a10 * b01 + a20 * b02; + this.mat4[1] = a01 * b00 + a11 * b01 + a21 * b02; + this.mat4[2] = a02 * b00 + a12 * b01 + a22 * b02; + this.mat4[3] = a03 * b00 + a13 * b01 + a23 * b02; + this.mat4[4] = a00 * b10 + a10 * b11 + a20 * b12; + this.mat4[5] = a01 * b10 + a11 * b11 + a21 * b12; + this.mat4[6] = a02 * b10 + a12 * b11 + a22 * b12; + this.mat4[7] = a03 * b10 + a13 * b11 + a23 * b12; + this.mat4[8] = a00 * b20 + a10 * b21 + a20 * b22; + this.mat4[9] = a01 * b20 + a11 * b21 + a21 * b22; + this.mat4[10] = a02 * b20 + a12 * b21 + a22 * b22; + this.mat4[11] = a03 * b20 + a13 * b21 + a23 * b22; + + return this; +}; + +/** + * @todo finish implementing this method! + * translates + * @param {Array} v vector to translate by + * @return {p5.Matrix} this + */ +p5.Matrix.prototype.translate = function(v){ + var x = v[0], + y = v[1], + z = v[2] || 0; + this.mat4[12] = + this.mat4[0] * x +this.mat4[4] * y +this.mat4[8] * z +this.mat4[12]; + this.mat4[13] = + this.mat4[1] * x +this.mat4[5] * y +this.mat4[9] * z +this.mat4[13]; + this.mat4[14] = + this.mat4[2] * x +this.mat4[6] * y +this.mat4[10] * z +this.mat4[14]; + this.mat4[15] = + this.mat4[3] * x +this.mat4[7] * y +this.mat4[11] * z +this.mat4[15]; +}; + +p5.Matrix.prototype.rotateX = function(a){ + this.rotate(a, [1,0,0]); +}; +p5.Matrix.prototype.rotateY = function(a){ + this.rotate(a, [0,1,0]); +}; +p5.Matrix.prototype.rotateZ = function(a){ + this.rotate(a, [0,0,1]); +}; + +/** + * sets the perspective matrix + * @param {Number} fovy [description] + * @param {Number} aspect [description] + * @param {Number} near near clipping plane + * @param {Number} far far clipping plane + * @return {void} + */ +p5.Matrix.prototype.perspective = function(fovy,aspect,near,far){ + + var f = 1.0 / Math.tan(fovy / 2), + nf = 1 / (near - far); + + this.mat4[0] = f / aspect; + this.mat4[1] = 0; + this.mat4[2] = 0; + this.mat4[3] = 0; + this.mat4[4] = 0; + this.mat4[5] = f; + this.mat4[6] = 0; + this.mat4[7] = 0; + this.mat4[8] = 0; + this.mat4[9] = 0; + this.mat4[10] = (far + near) * nf; + this.mat4[11] = -1; + this.mat4[12] = 0; + this.mat4[13] = 0; + this.mat4[14] = (2 * far * near) * nf; + this.mat4[15] = 0; + + return this; + +}; + +/** + * sets the ortho matrix + * @param {Number} left [description] + * @param {Number} right [description] + * @param {Number} bottom [description] + * @param {Number} top [description] + * @param {Number} near near clipping plane + * @param {Number} far far clipping plane + * @return {void} + */ +p5.Matrix.prototype.ortho = function(left,right,bottom,top,near,far){ + + var lr = 1 / (left - right), + bt = 1 / (bottom - top), + nf = 1 / (near - far); + this.mat4[0] = -2 * lr; + this.mat4[1] = 0; + this.mat4[2] = 0; + this.mat4[3] = 0; + this.mat4[4] = 0; + this.mat4[5] = -2 * bt; + this.mat4[6] = 0; + this.mat4[7] = 0; + this.mat4[8] = 0; + this.mat4[9] = 0; + this.mat4[10] = 2 * nf; + this.mat4[11] = 0; + this.mat4[12] = (left + right) * lr; + this.mat4[13] = (top + bottom) * bt; + this.mat4[14] = (far + near) * nf; + this.mat4[15] = 1; + + return this; +}; + +/** + * PRIVATE + */ +// matrix methods adapted from: +// https://developer.mozilla.org/en-US/docs/Web/WebGL/ +// gluPerspective +// +// function _makePerspective(fovy, aspect, znear, zfar){ +// var ymax = znear * Math.tan(fovy * Math.PI / 360.0); +// var ymin = -ymax; +// var xmin = ymin * aspect; +// var xmax = ymax * aspect; +// return _makeFrustum(xmin, xmax, ymin, ymax, znear, zfar); +// } + +//// +//// glFrustum +//// +//function _makeFrustum(left, right, bottom, top, znear, zfar){ +// var X = 2*znear/(right-left); +// var Y = 2*znear/(top-bottom); +// var A = (right+left)/(right-left); +// var B = (top+bottom)/(top-bottom); +// var C = -(zfar+znear)/(zfar-znear); +// var D = -2*zfar*znear/(zfar-znear); +// var frustrumMatrix =[ +// X, 0, A, 0, +// 0, Y, B, 0, +// 0, 0, C, D, +// 0, 0, -1, 0 +//]; +//return frustrumMatrix; +// } + +// function _setMVPMatrices(){ +////an identity matrix +////@TODO use the p5.Matrix class to abstract away our MV matrices and +///other math +//var _mvMatrix = +//[ +// 1.0,0.0,0.0,0.0, +// 0.0,1.0,0.0,0.0, +// 0.0,0.0,1.0,0.0, +// 0.0,0.0,0.0,1.0 +//]; + +module.exports = p5.Matrix; +},{"../core/constants":47,"../core/core":48,"../math/polargeometry":77}],36:[function(_dereq_,module,exports){ +'use strict'; + +var p5 = _dereq_('../core/core'); +var shader = _dereq_('./shader'); +_dereq_('../core/p5.Renderer'); +_dereq_('./p5.Matrix'); +var uMVMatrixStack = []; +var RESOLUTION = 1000; + +//@TODO should probably implement an override for these attributes +var attributes = { + alpha: true, + depth: true, + stencil: true, + antialias: false, + premultipliedAlpha: false, + preserveDrawingBuffer: false +}; + +/** + * 3D graphics class. Can also be used as an off-screen graphics buffer. + * A p5.Renderer3D object can be constructed + * + */ +p5.Renderer3D = function(elt, pInst, isMainCanvas) { + p5.Renderer.call(this, elt, pInst, isMainCanvas); + + try { + this.drawingContext = this.canvas.getContext('webgl', attributes) || + this.canvas.getContext('experimental-webgl', attributes); + if (this.drawingContext === null) { + throw new Error('Error creating webgl context'); + } else { + console.log('p5.Renderer3D: enabled webgl context'); + } + } catch (er) { + throw new Error(er); + } + + this.isP3D = true; //lets us know we're in 3d mode + this.GL = this.drawingContext; + var gl = this.GL; + gl.clearColor(1.0, 1.0, 1.0, 1.0); //background is initialized white + gl.clearDepth(1); + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LEQUAL); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + this._init(); + return this; +}; + +p5.Renderer3D.prototype = Object.create(p5.Renderer.prototype); + +p5.Renderer3D.prototype._applyDefaults = function() { + return this; +}; + +////////////////////////////////////////////// +// Setting +////////////////////////////////////////////// + +p5.Renderer3D.prototype._init = function(first_argument) { + var gl = this.GL; + //for our default matrices + this.initMatrix(); + this.initHash(); + //for immedidate mode + this.verticeStack = []; + this.verticeBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + //for camera + this._setCamera = false; + //for counting lights + this.ambientLightCount = 0; + this.directionalLightCount = 0; + this.pointLightCount = 0; +}; + +p5.Renderer3D.prototype._update = function() { + this.resetMatrix(); + this.translate(0, 0, -800); + this.ambientLightCount = 0; + this.directionalLightCount = 0; + this.pointLightCount = 0; + this.verticeStack = []; +}; + +/** + * [resize description] + * @param {[type]} w [description] + * @param {[tyoe]} h [description] + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.resize = function(w,h) { + var gl = this.GL; + p5.Renderer.prototype.resize.call(this, w, h); + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); +}; + +/** + * [background description] + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.background = function() { + var gl = this.GL; + var _col = this._pInst.color.apply(this._pInst, arguments); + // gl.clearColor(0.0,0.0,0.0,1.0); + var _r = (_col.levels[0]) / 255; + var _g = (_col.levels[1]) / 255; + var _b = (_col.levels[2]) / 255; + var _a = (_col.levels[3]) / 255; + gl.clearColor(_r, _g, _b, _a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); +}; + +//@TODO implement this +// p5.Renderer3D.prototype.clear = function() { +//@TODO +// }; + +////////////////////////////////////////////// +// SHADER +////////////////////////////////////////////// + +/** + * [initShaders description] + * @param {[type]} vertId [description] + * @param {[type]} fragId [description] + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.initShaders = function(vertId, fragId, immediateMode) { + var gl = this.GL; + //set up our default shaders by: + // 1. create the shader, + // 2. load the shader source, + // 3. compile the shader + var _vertShader = gl.createShader(gl.VERTEX_SHADER); + //load in our default vertex shader + gl.shaderSource(_vertShader, shader[vertId]); + gl.compileShader(_vertShader); + // if our vertex shader failed compilation? + if (!gl.getShaderParameter(_vertShader, gl.COMPILE_STATUS)) { + alert('Yikes! An error occurred compiling the shaders:' + + gl.getShaderInfoLog(_vertShader)); + return null; + } + + var _fragShader = gl.createShader(gl.FRAGMENT_SHADER); + //load in our material frag shader + gl.shaderSource(_fragShader, shader[fragId]); + gl.compileShader(_fragShader); + // if our frag shader failed compilation? + if (!gl.getShaderParameter(_fragShader, gl.COMPILE_STATUS)) { + alert('Darn! An error occurred compiling the shaders:' + + gl.getShaderInfoLog(_fragShader)); + return null; + } + + var shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, _vertShader); + gl.attachShader(shaderProgram, _fragShader); + gl.linkProgram(shaderProgram); + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + alert('Snap! Error linking shader program'); + } + //END SHADERS SETUP + + this._getLocation(shaderProgram, immediateMode); + + return shaderProgram; +}; + +p5.Renderer3D.prototype._getLocation = function(shaderProgram, immediateMode) { + var gl = this.GL; + gl.useProgram(shaderProgram); + shaderProgram.uResolution = + gl.getUniformLocation(shaderProgram, 'uResolution'); + gl.uniform1f(shaderProgram.uResolution, RESOLUTION); + + //vertex position Attribute + shaderProgram.vertexPositionAttribute = + gl.getAttribLocation(shaderProgram, 'aPosition'); + gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); + + //projection Matrix uniform + shaderProgram.uPMatrixUniform = + gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'); + //model view Matrix uniform + shaderProgram.uMVMatrixUniform = + gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'); + + //@TODO: figure out a better way instead of if statement + if(immediateMode === undefined){ + //vertex normal Attribute + shaderProgram.vertexNormalAttribute = + gl.getAttribLocation(shaderProgram, 'aNormal'); + gl.enableVertexAttribArray(shaderProgram.vertexNormalAttribute); + + //normal Matrix uniform + shaderProgram.uNMatrixUniform = + gl.getUniformLocation(shaderProgram, 'uNormalMatrix'); + + //texture coordinate Attribute + shaderProgram.textureCoordAttribute = + gl.getAttribLocation(shaderProgram, 'aTexCoord'); + gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute); + + shaderProgram.samplerUniform = + gl.getUniformLocation(shaderProgram, 'uSampler'); + } +}; + +p5.Renderer3D.prototype.setMatrixUniforms = function(shaderKey) { + var gl = this.GL; + var shaderProgram = this.mHash[shaderKey]; + + gl.useProgram(shaderProgram); + + gl.uniformMatrix4fv( + shaderProgram.uPMatrixUniform, + false, this.uPMatrix.mat4); + + gl.uniformMatrix4fv( + shaderProgram.uMVMatrixUniform, + false, this.uMVMatrix.mat4); + + this.uNMatrix = new p5.Matrix(); + this.uNMatrix.invert(this.uMVMatrix); + this.uNMatrix.transpose(this.uNMatrix); + + gl.uniformMatrix4fv( + shaderProgram.uNMatrixUniform, + false, this.uNMatrix.mat4); +}; +////////////////////////////////////////////// +// GET CURRENT | for shader and color +////////////////////////////////////////////// +p5.Renderer3D.prototype._getShader = function(vertId, fragId, immediateMode) { + var mId = vertId+ '|' + fragId; + //create it and put it into hashTable + if(!this.materialInHash(mId)){ + var shaderProgram = this.initShaders(vertId, fragId, immediateMode); + this.mHash[mId] = shaderProgram; + } + this.curShaderId = mId; + + return this.mHash[this.curShaderId]; +}; + +p5.Renderer3D.prototype._getCurShaderId = function(){ + //if it's not defined yet + if(this.curShaderId === undefined){ + //default shader: normalMaterial() + var mId = 'normalVert|normalFrag'; + var shaderProgram = this.initShaders('normalVert', 'normalFrag'); + this.mHash[mId] = shaderProgram; + this.curShaderId = mId; + } + + return this.curShaderId; +}; + +p5.Renderer3D.prototype._getCurColor = function() { + //default color: gray + if(this.curColor === undefined) { + this.curColor = [0.5, 0.5, 0.5, 1.0]; + } + return this.curColor; +}; + +////////////////////////////////////////////// +// HASH | for material and geometry +////////////////////////////////////////////// + +p5.Renderer3D.prototype.initHash = function(){ + this.gHash = {}; + this.mHash = {}; +}; + +p5.Renderer3D.prototype.geometryInHash = function(gId){ + return this.gHash[gId] !== undefined; +}; + +p5.Renderer3D.prototype.materialInHash = function(mId){ + return this.mHash[mId] !== undefined; +}; + +////////////////////////////////////////////// +// MATRIX +////////////////////////////////////////////// + +p5.Renderer3D.prototype.initMatrix = function(){ + this.uMVMatrix = new p5.Matrix(); + this.uPMatrix = new p5.Matrix(); + this.uNMatrix = new p5.Matrix(); +}; + +p5.Renderer3D.prototype.resetMatrix = function() { + this.uMVMatrix = p5.Matrix.identity(); + //this.uPMatrix = p5.Matrix.identity(); +}; + +//detect if user didn't set the camera +//then call this function below +p5.Renderer3D.prototype._setDefaultCamera = function(){ + if(!this._setCamera){ + var _w = this.width; + var _h = this.height; + this.uPMatrix = p5.Matrix.identity(); + this.uPMatrix.perspective(60 / 180 * Math.PI, _w / _h, 0.1, 100); + this._setCamera = true; + } +}; + +/** + * [translate description] + * @param {[type]} x [description] + * @param {[type]} y [description] + * @param {[type]} z [description] + * @return {[type]} [description] + * @todo implement handle for components or vector as args + */ +p5.Renderer3D.prototype.translate = function(x, y, z) { + //@TODO: figure out how to fit the resolution + x = x / RESOLUTION; + y = -y / RESOLUTION; + z = z / RESOLUTION; + this.uMVMatrix.translate([x,y,z]); + return this; +}; + +/** + * Scales the Model View Matrix by a vector + * @param {Number | p5.Vector | Array} x [description] + * @param {Number} [y] y-axis scalar + * @param {Number} [z] z-axis scalar + * @return {this} [description] + */ +p5.Renderer3D.prototype.scale = function(x,y,z) { + this.uMVMatrix.scale([x,y,z]); + return this; +}; + +/** + * [rotate description] + * @param {Number} rad angle in radians + * @param {p5.Vector | Array} axis axis to rotate around + * @return {p5.Renderer3D} [description] + */ +p5.Renderer3D.prototype.rotate = function(rad, axis){ + this.uMVMatrix.rotate(rad, axis); + return this; +}; + +/** + * [rotateX description] + * @param {Number} rad radians to rotate + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.rotateX = function(rad) { + this.uMVMatrix.rotateX(rad); + return this; +}; + +/** + * [rotateY description] + * @param {Number} rad rad radians to rotate + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.rotateY = function(rad) { + this.uMVMatrix.rotateY(rad); + return this; +}; + +/** + * [rotateZ description] + * @param {Number} rad rad radians to rotate + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.rotateZ = function(rad) { + this.uMVMatrix.rotateZ(rad); + return this; +}; + +/** + * pushes a copy of the model view matrix onto the + * MV Matrix stack. + * NOTE to self: could probably make this more readable + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.push = function() { + uMVMatrixStack.push(this.uMVMatrix.copy()); +}; + +/** + * [pop description] + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.pop = function() { + if (uMVMatrixStack.length === 0) { + throw new Error('Invalid popMatrix!'); + } + this.uMVMatrix = uMVMatrixStack.pop(); +}; + +module.exports = p5.Renderer3D; + +},{"../core/core":48,"../core/p5.Renderer":54,"./p5.Matrix":35,"./shader":38}],37:[function(_dereq_,module,exports){ +//retained mode is used by rendering 3d_primitives + +'use strict'; + +var p5 = _dereq_('../core/core'); +var hashCount = 0; + +/** + * createBuffer + * @param {String} gId key of the geometry object + * @param {Array} arr array holding bject containing geometry information + */ +p5.Renderer3D.prototype.createBuffer = function(gId, arr) { + + hashCount ++; + if(hashCount > 1000){ + var key = Object.keys(this.gHash)[0]; + delete this.gHash[key]; + hashCount --; + } + + var gl = this.GL; + this.gHash[gId] = {}; + this.gHash[gId].len = []; + this.gHash[gId].vertexBuffer = []; + this.gHash[gId].normalBuffer = []; + this.gHash[gId].uvBuffer = []; + this.gHash[gId].indexBuffer =[]; + + arr.forEach(function(obj){ + this.gHash[gId].len.push(obj.len); + this.gHash[gId].vertexBuffer.push(gl.createBuffer()); + this.gHash[gId].normalBuffer.push(gl.createBuffer()); + this.gHash[gId].uvBuffer.push(gl.createBuffer()); + this.gHash[gId].indexBuffer.push(gl.createBuffer()); + }.bind(this)); +}; + +/** + * initBuffer description + * @param {String} gId key of the geometry object + * @param {Array} arr array holding bject containing geometry information + */ +p5.Renderer3D.prototype.initBuffer = function(gId, arr) { + this._setDefaultCamera(); + var gl = this.GL; + this.createBuffer(gId, arr); + + var shaderProgram = this.mHash[this._getCurShaderId()]; + + arr.forEach(function(obj, i){ + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].vertexBuffer[i]); + gl.bufferData( + gl.ARRAY_BUFFER, new Float32Array(obj.vertices), gl.STATIC_DRAW); + gl.vertexAttribPointer( + shaderProgram.vertexPositionAttribute, + 3, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].normalBuffer[i]); + gl.bufferData( + gl.ARRAY_BUFFER, new Float32Array(obj.vertexNormals), gl.STATIC_DRAW); + gl.vertexAttribPointer( + shaderProgram.vertexNormalAttribute, + 3, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].uvBuffer[i]); + gl.bufferData( + gl.ARRAY_BUFFER, new Float32Array(obj.uvs), gl.STATIC_DRAW); + gl.vertexAttribPointer( + shaderProgram.textureCoordAttribute, + 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.gHash[gId].indexBuffer[i]); + gl.bufferData + (gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(obj.faces), gl.STATIC_DRAW); + }.bind(this)); +}; + +/** + * drawBuffer + * @param {String} gId key of the geometery object + */ +p5.Renderer3D.prototype.drawBuffer = function(gId) { + this._setDefaultCamera(); + var gl = this.GL; + var shaderKey = this._getCurShaderId(); + var shaderProgram = this.mHash[shaderKey]; + + this.gHash[gId].len.forEach(function(d, i){ + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].vertexBuffer[i]); + gl.vertexAttribPointer( + shaderProgram.vertexPositionAttribute, + 3, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].normalBuffer[i]); + gl.vertexAttribPointer( + shaderProgram.vertexNormalAttribute, + 3, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].uvBuffer[i]); + gl.vertexAttribPointer( + shaderProgram.textureCoordAttribute, + 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.gHash[gId].indexBuffer[i]); + + this.setMatrixUniforms(shaderKey); + + gl.drawElements( + gl.TRIANGLES, this.gHash[gId].len[i], + gl.UNSIGNED_SHORT, 0); + }.bind(this)); +}; + +module.exports = p5.Renderer3D; +},{"../core/core":48}],38:[function(_dereq_,module,exports){ + + +module.exports = { + vertexColorVert: + "attribute vec3 aPosition;\nattribute vec4 aVertexColor;\n\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\nuniform float uResolution;\n\nvarying vec4 vColor;\n\nvoid main(void) {\n vec4 positionVec4 = vec4(aPosition / uResolution * vec3(1.0, -1.0, 1.0), 1.0);\n gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;\n vColor = aVertexColor;\n}", + vertexColorFrag: + "precision mediump float;\nvarying vec4 vColor;\nvoid main(void) {\n gl_FragColor = vColor;\n}", + normalVert: + "attribute vec3 aPosition;\nattribute vec3 aNormal;\nattribute vec2 aTexCoord;\n\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\nuniform mat4 uNormalMatrix;\nuniform float uResolution;\n\nvarying vec3 vVertexNormal;\nvarying highp vec2 vVertTexCoord;\n\nvoid main(void) {\n vec4 positionVec4 = vec4(aPosition / uResolution, 1.0);\n gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;\n vVertexNormal = vec3( uNormalMatrix * vec4( aNormal, 1.0 ) );\n vVertTexCoord = aTexCoord;\n}", + normalFrag: + "precision mediump float;\nvarying vec3 vVertexNormal;\nvoid main(void) {\n gl_FragColor = vec4(vVertexNormal, 1.0);\n}", + basicFrag: + "precision mediump float;\nvarying vec3 vVertexNormal;\nuniform vec4 uMaterialColor;\nvoid main(void) {\n gl_FragColor = uMaterialColor;\n}", + lightVert: + "attribute vec3 aPosition;\nattribute vec3 aNormal;\nattribute vec2 aTexCoord;\n\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\nuniform mat4 uNormalMatrix;\nuniform float uResolution;\nuniform int uAmbientLightCount;\nuniform int uDirectionalLightCount;\nuniform int uPointLightCount;\n\nuniform vec3 uAmbientColor[8];\nuniform vec3 uLightingDirection[8];\nuniform vec3 uDirectionalColor[8];\nuniform vec3 uPointLightLocation[8];\nuniform vec3 uPointLightColor[8];\nuniform bool uSpecular;\n\nvarying vec3 vVertexNormal;\nvarying vec2 vVertTexCoord;\nvarying vec3 vLightWeighting;\n\nvec3 ambientLightFactor = vec3(0.0, 0.0, 0.0);\nvec3 directionalLightFactor = vec3(0.0, 0.0, 0.0);\nvec3 pointLightFactor = vec3(0.0, 0.0, 0.0);\nvec3 pointLightFactor2 = vec3(0.0, 0.0, 0.0);\n\nvoid main(void){\n\n vec4 positionVec4 = vec4(aPosition / uResolution, 1.0);\n gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;\n\n vec3 vertexNormal = vec3( uNormalMatrix * vec4( aNormal, 1.0 ) );\n vVertexNormal = vertexNormal;\n vVertTexCoord = aTexCoord;\n\n vec4 mvPosition = uModelViewMatrix * vec4(aPosition / uResolution, 1.0);\n vec3 eyeDirection = normalize(-mvPosition.xyz);\n\n float shininess = 32.0;\n float specularFactor = 2.0;\n float diffuseFactor = 0.3;\n\n for(int i = 0; i < 8; i++){\n if(uAmbientLightCount == i) break;\n ambientLightFactor += uAmbientColor[i];\n }\n\n for(int j = 0; j < 8; j++){\n if(uDirectionalLightCount == j) break;\n vec3 dir = uLightingDirection[j];\n float directionalLightWeighting = max(dot(vertexNormal, dir), 0.0);\n directionalLightFactor += uDirectionalColor[j] * directionalLightWeighting;\n }\n\n for(int k = 0; k < 8; k++){\n if(uPointLightCount == k) break;\n vec3 loc = uPointLightLocation[k];\n //loc = loc / uResolution;\n vec3 lightDirection = normalize(loc - mvPosition.xyz);\n\n float directionalLightWeighting = max(dot(vertexNormal, lightDirection), 0.0);\n pointLightFactor += uPointLightColor[k] * directionalLightWeighting;\n\n //factor2 for specular\n vec3 reflectionDirection = reflect(-lightDirection, vertexNormal);\n float specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), shininess);\n\n pointLightFactor2 += uPointLightColor[k] * (specularFactor * specularLightWeighting\n + directionalLightWeighting * diffuseFactor);\n }\n \n if(!uSpecular){\n vLightWeighting = ambientLightFactor + directionalLightFactor + pointLightFactor;\n }else{\n vLightWeighting = ambientLightFactor + directionalLightFactor + pointLightFactor2;\n }\n\n}", + lightTextureFrag: + "precision mediump float;\n\nuniform vec4 uMaterialColor;\nuniform sampler2D uSampler;\nuniform bool isTexture;\n\nvarying vec3 vLightWeighting;\nvarying highp vec2 vVertTexCoord;\n\nvoid main(void) {\n if(!isTexture){\n gl_FragColor = vec4(vec3(uMaterialColor.rgb * vLightWeighting), uMaterialColor.a);\n }else{\n vec4 textureColor = texture2D(uSampler, vVertTexCoord);\n if(vLightWeighting == vec3(0., 0., 0.)){\n gl_FragColor = textureColor;\n }else{\n gl_FragColor = vec4(vec3(textureColor.rgb * vLightWeighting), textureColor.a); \n }\n }\n}" +}; +},{}],39:[function(_dereq_,module,exports){ + +'use strict'; + +var p5 = _dereq_('./core/core'); +_dereq_('./color/p5.Color'); +_dereq_('./core/p5.Element'); +_dereq_('./typography/p5.Font'); +_dereq_('./core/p5.Graphics'); +_dereq_('./core/p5.Renderer2D'); + +_dereq_('./image/p5.Image'); +_dereq_('./math/p5.Vector'); +_dereq_('./io/p5.TableRow'); +_dereq_('./io/p5.Table'); + +_dereq_('./color/creating_reading'); +_dereq_('./color/setting'); +_dereq_('./core/constants'); +_dereq_('./utilities/conversion'); +_dereq_('./utilities/array_functions'); +_dereq_('./utilities/string_functions'); +_dereq_('./core/environment'); +_dereq_('./image/image'); +_dereq_('./image/loading_displaying'); +_dereq_('./image/pixels'); +_dereq_('./io/files'); +_dereq_('./events/keyboard'); +_dereq_('./events/acceleration'); //john +_dereq_('./events/mouse'); +_dereq_('./utilities/time_date'); +_dereq_('./events/touch'); +_dereq_('./math/math'); +_dereq_('./math/calculation'); +_dereq_('./math/random'); +_dereq_('./math/noise'); +_dereq_('./math/trigonometry'); +_dereq_('./core/rendering'); +_dereq_('./core/2d_primitives'); + +_dereq_('./core/attributes'); +_dereq_('./core/curves'); +_dereq_('./core/vertex'); +_dereq_('./core/structure'); +_dereq_('./core/transform'); +_dereq_('./typography/attributes'); +_dereq_('./typography/loading_displaying'); + +_dereq_('./3d/p5.Renderer3D'); +_dereq_('./3d/p5.Geometry3D'); +_dereq_('./3d/retainedMode3D'); +_dereq_('./3d/immediateMode3D'); +_dereq_('./3d/3d_primitives'); +_dereq_('./3d/p5.Matrix'); +_dereq_('./3d/material'); +_dereq_('./3d/light'); +_dereq_('./3d/shader'); +_dereq_('./3d/camera'); +_dereq_('./3d/interaction'); + +/** + * _globalInit + * + * TODO: ??? + * if sketch is on window + * assume "global" mode + * and instantiate p5 automatically + * otherwise do nothing + * + * @return {Undefined} + */ +var _globalInit = function() { + if (!window.PHANTOMJS && !window.mocha) { + // If there is a setup or draw function on the window + // then instantiate p5 in "global" mode + if((window.setup && typeof window.setup === 'function') || + (window.draw && typeof window.draw === 'function')) { + new p5(); + } + } +}; + +// TODO: ??? +if (document.readyState === 'complete') { + _globalInit(); +} else { + window.addEventListener('load', _globalInit , false); +} + +module.exports = p5; +},{"./3d/3d_primitives":28,"./3d/camera":29,"./3d/immediateMode3D":30,"./3d/interaction":31,"./3d/light":32,"./3d/material":33,"./3d/p5.Geometry3D":34,"./3d/p5.Matrix":35,"./3d/p5.Renderer3D":36,"./3d/retainedMode3D":37,"./3d/shader":38,"./color/creating_reading":41,"./color/p5.Color":42,"./color/setting":43,"./core/2d_primitives":44,"./core/attributes":45,"./core/constants":47,"./core/core":48,"./core/curves":49,"./core/environment":50,"./core/p5.Element":52,"./core/p5.Graphics":53,"./core/p5.Renderer2D":55,"./core/rendering":56,"./core/structure":58,"./core/transform":59,"./core/vertex":60,"./events/acceleration":61,"./events/keyboard":62,"./events/mouse":63,"./events/touch":64,"./image/image":66,"./image/loading_displaying":67,"./image/p5.Image":68,"./image/pixels":69,"./io/files":70,"./io/p5.Table":71,"./io/p5.TableRow":72,"./math/calculation":73,"./math/math":74,"./math/noise":75,"./math/p5.Vector":76,"./math/random":78,"./math/trigonometry":79,"./typography/attributes":80,"./typography/loading_displaying":81,"./typography/p5.Font":82,"./utilities/array_functions":83,"./utilities/conversion":84,"./utilities/string_functions":85,"./utilities/time_date":86}],40:[function(_dereq_,module,exports){ +/** + * module Conversion + * submodule Color Conversion + * @for p5 + * @requires core + */ + +'use strict'; + +/** + * Conversions adapted from <http://www.easyrgb.com/math.html>. + * + * In these functions, hue is always in the range [0,1); all other components + * are in the range [0,1]. 'Brightness' and 'value' are used interchangeably. + */ + +var p5 = _dereq_('../core/core'); +p5.ColorConversion = {}; + +/** + * Convert an HSBA array to HSLA. + */ +p5.ColorConversion._hsbaToHSLA = function(hsba) { + var hue = hsba[0]; + var sat = hsba[1]; + var val = hsba[2]; + + // Calculate lightness. + var li = (2 - sat) * val / 2; + + // Convert saturation. + if (li !== 0) { + if (li === 1) { + sat = 0; + } else if (li < 0.5) { + sat = sat / (2 - sat); + } else { + sat = sat * val / (2 - li * 2); + } + } + + // Hue and alpha stay the same. + return [hue, sat, li, hsba[3]]; +}; + +/** + * Convert an HSBA array to RGBA. + */ +p5.ColorConversion._hsbaToRGBA = function(hsba) { + var hue = hsba[0] * 6; // We will split hue into 6 sectors. + var sat = hsba[1]; + var val = hsba[2]; + + var RGBA = []; + + if (sat === 0) { + RGBA = [val, val, val, hsba[3]]; // Return early if grayscale. + } else { + var sector = Math.floor(hue); + var tint1 = val * (1 - sat); + var tint2 = val * (1 - sat * (hue - sector)); + var tint3 = val * (1 - sat * (1 + sector - hue)); + var red, green, blue; + if (sector === 0) { // Red to yellow. + red = val; + green = tint3; + blue = tint1; + } else if (sector === 1) { // Yellow to green. + red = tint2; + green = val; + blue = tint1; + } else if (sector === 2) { // Green to cyan. + red = tint1; + green = val; + blue = tint3; + } else if (sector === 3) { // Cyan to blue. + red = tint1; + green = tint2; + blue = val; + } else if (sector === 4) { // Blue to magenta. + red = tint3; + green = tint1; + blue = val; + } else { // Magenta to red. + red = val; + green = tint1; + blue = tint2; + } + RGBA = [red, green, blue, hsba[3]]; + } + + return RGBA; +}; + +/** + * Convert an HSLA array to HSBA. + */ +p5.ColorConversion._hslaToHSBA = function(hsla) { + var hue = hsla[0]; + var sat = hsla[1]; + var li = hsla[2]; + + // Calculate brightness. + var val; + if (li < 0.5) { + val = (1 + sat) * li; + } else { + val = li + sat - li * sat; + } + + // Convert saturation. + sat = 2 * (val - li) / val; + + // Hue and alpha stay the same. + return [hue, sat, val, hsla[3]]; +}; + +/** + * Convert an HSLA array to RGBA. + * + * We need to change basis from HSLA to something that can be more easily be + * projected onto RGBA. We will choose hue and brightness as our first two + * components, and pick a convenient third one ('zest') so that we don't need + * to calculate formal HSBA saturation. + */ +p5.ColorConversion._hslaToRGBA = function(hsla){ + var hue = hsla[0] * 6; // We will split hue into 6 sectors. + var sat = hsla[1]; + var li = hsla[2]; + + var RGBA = []; + + if (sat === 0) { + RGBA = [li, li, li, hsla[3]]; // Return early if grayscale. + } else { + + // Calculate brightness. + var val; + if (li < 0.5) { + val = (1 + sat) * li; + } else { + val = li + sat - li * sat; + } + + // Define zest. + var zest = 2 * li - val; + + // Implement projection (project onto green by default). + var hzvToRGB = function(hue, zest, val) { + if (hue < 0) { // Hue must wrap to allow projection onto red and blue. + hue += 6; + } else if (hue >= 6) { + hue -= 6; + } + if (hue < 1) { // Red to yellow (increasing green). + return (zest + (val - zest) * hue); + } else if (hue < 3) { // Yellow to cyan (greatest green). + return val; + } else if (hue < 4) { // Cyan to blue (decreasing green). + return (zest + (val - zest) * (4 - hue)); + } else { // Blue to red (least green). + return zest; + } + }; + + // Perform projections, offsetting hue as necessary. + RGBA = [hzvToRGB(hue + 2, zest, val), + hzvToRGB(hue , zest, val), + hzvToRGB(hue - 2, zest, val), + hsla[3]]; + } + + return RGBA; +}; + +/** + * Convert an RGBA array to HSBA. + */ +p5.ColorConversion._rgbaToHSBA = function(rgba) { + var red = rgba[0]; + var green = rgba[1]; + var blue = rgba[2]; + + var val = Math.max(red, green, blue); + var chroma = val - Math.min(red, green, blue); + + var hue, sat; + if (chroma === 0) { // Return early if grayscale. + hue = 0; + sat = 0; + } + else { + sat = chroma / val; + if (red === val) { // Magenta to yellow. + hue = (green - blue) / chroma; + } else if (green === val) { // Yellow to cyan. + hue = 2 + (blue - red) / chroma; + } else if (blue === val) { // Cyan to magenta. + hue = 4 + (red - green) / chroma; + } + if (hue < 0) { // Confine hue to the interval [0, 1). + hue += 6; + } else if (hue >= 6) { + hue -= 6; + } + } + + return [hue / 6, sat, val, rgba[3]]; +}; + +/** + * Convert an RGBA array to HSLA. + */ +p5.ColorConversion._rgbaToHSLA = function(rgba) { + var red = rgba[0]; + var green = rgba[1]; + var blue = rgba[2]; + + var val = Math.max(red, green, blue); + var min = Math.min(red, green, blue); + var li = val + min; // We will halve this later. + var chroma = val - min; + + var hue, sat; + if (chroma === 0) { // Return early if grayscale. + hue = 0; + sat = 0; + } else { + if (li < 1) { + sat = chroma / li; + } else { + sat = chroma / (2 - chroma); + } + if (red === val) { // Magenta to yellow. + hue = (green - blue) / chroma; + } else if (green === val) { // Yellow to cyan. + hue = 2 + (blue - red) / chroma; + } else if (blue === val) { // Cyan to magenta. + hue = 4 + (red - green) / chroma; + } + if (hue < 0) { // Confine hue to the interval [0, 1). + hue += 6; + } else if (hue >= 6) { + hue -= 6; + } + } + + return [hue / 6, sat, li / 2, rgba[3]]; +}; + +module.exports = p5.ColorConversion; + +},{"../core/core":48}],41:[function(_dereq_,module,exports){ +/** + * @module Color + * @submodule Creating & Reading + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); +_dereq_('./p5.Color'); + +/** + * Extracts the alpha value from a color or pixel array. + * + * @method alpha + * @param {Object} obj p5.Color object or pixel array + * @example + * <div> + * <code> + * noStroke(); + * c = color(0, 126, 255, 102); + * fill(c); + * rect(15, 15, 35, 70); + * value = alpha(c); // Sets 'value' to 102 + * fill(value); + * rect(50, 15, 35, 70); + * </code> + * </div> + */ +p5.prototype.alpha = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getAlpha(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Extracts the blue value from a color or pixel array. + * + * @method blue + * @param {Object} obj p5.Color object or pixel array + * @example + * <div> + * <code> + * c = color(175, 100, 220); // Define color 'c' + * fill(c); // Use color variable 'c' as fill color + * rect(15, 20, 35, 60); // Draw left rectangle + * + * blueValue = blue(c); // Get blue in 'c' + * println(blueValue); // Prints "220.0" + * fill(0, 0, blueValue); // Use 'blueValue' in new fill + * rect(50, 20, 35, 60); // Draw right rectangle + * </code> + * </div> + */ +p5.prototype.blue = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getBlue(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Extracts the HSB brightness value from a color or pixel array. + * + * @method brightness + * @param {Object} color p5.Color object or pixel array + * @example + * <div> + * <code> + * noStroke(); + * colorMode(HSB, 255); + * c = color(0, 126, 255); + * fill(c); + * rect(15, 20, 35, 60); + * value = brightness(c); // Sets 'value' to 255 + * fill(value); + * rect(50, 20, 35, 60); + * </code> + * </div> + */ +p5.prototype.brightness = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getBrightness(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * 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. + * <br><br> + * Note that if only one value is provided to color(), it will be interpreted + * as a grayscale value. Add a second value, and it will be used for alpha + * transparency. When three values are specified, they are interpreted as + * either RGB or HSB values. Adding a fourth value applies alpha + * transparency. If a single string parameter is provided it will be + * interpreted as a CSS-compatible color string. + * + * Colors are stored as Numbers or Arrays. + * + * @method color + * @param {Number|String} v1 gray value or red or hue value relative to + * the current color range, or a color string + * @param {Number} [v2] gray value or green or saturation value + * relative to the current color range (or + * alpha value if first param is gray value) + * @param {Number} [v3] gray value or blue or brightness value + * relative to the current color range + * @param {Number} [alpha] alpha value relative to current color range + * @return {Array} resulting color + * + * @example + * <div> + * <code> + * var c = color(255, 204, 0); // Define color 'c' + * fill(c); // Use color variable 'c' as fill color + * noStroke(); // Don't draw a stroke around shapes + * rect(30, 20, 55, 55); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * var c = color(255, 204, 0); // Define color 'c' + * fill(c); // Use color variable 'c' as fill color + * noStroke(); // Don't draw a stroke around shapes + * ellipse(25, 25, 80, 80); // Draw left circle + * + * // Using only one value with color() + * // generates a grayscale value. + * var c = color(65); // Update 'c' with grayscale value + * fill(c); // Use updated 'c' as fill color + * ellipse(75, 75, 80, 80); // Draw right circle + * </code> + * </div> + * + * <div> + * <code> + * // Named SVG & CSS colors may be used, + * var c = color('magenta'); + * fill(c); // Use 'c' as fill color + * noStroke(); // Don't draw a stroke around shapes + * rect(20, 20, 60, 60); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * // as can hex color codes: + * noStroke(); // Don't draw a stroke around shapes + * var c = color('#0f0'); + * fill(c); // Use 'c' as fill color + * rect(0, 10, 45, 80); // Draw rectangle + * + * c = color('#00ff00'); + * fill(c); // Use updated 'c' as fill color + * rect(55, 10, 45, 80); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * // RGB and RGBA color strings are also supported: + * // these all set to the same color (solid blue) + * var c; + * noStroke(); // Don't draw a stroke around shapes + * c = color('rgb(0,0,255)'); + * fill(c); // Use 'c' as fill color + * rect(10, 10, 35, 35); // Draw rectangle + * + * c = color('rgb(0%, 0%, 100%)'); + * fill(c); // Use updated 'c' as fill color + * rect(55, 10, 35, 35); // Draw rectangle + * + * c = color('rgba(0, 0, 255, 1)'); + * fill(c); // Use updated 'c' as fill color + * rect(10, 55, 35, 35); // Draw rectangle + * + * c = color('rgba(0%, 0%, 100%, 1)'); + * fill(c); // Use updated 'c' as fill color + * rect(55, 55, 35, 35); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * // HSL color is also supported and can be specified + * // by value + * var c; + * noStroke(); // Don't draw a stroke around shapes + * c = color('hsl(160, 100%, 50%)'); + * fill(c); // Use 'c' as fill color + * rect(0, 10, 45, 80); // Draw rectangle + * + * c = color('hsla(160, 100%, 50%, 0.5)'); + * fill(c); // Use updated 'c' as fill color + * rect(55, 10, 45, 80); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * // HSB color is also supported and can be specified + * // by value + * var c; + * noStroke(); // Don't draw a stroke around shapes + * c = color('hsb(160, 100%, 50%)'); + * fill(c); // Use 'c' as fill color + * rect(0, 10, 45, 80); // Draw rectangle + * + * c = color('hsba(160, 100%, 50%, 0.5)'); + * fill(c); // Use updated 'c' as fill color + * rect(55, 10, 45, 80); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * var c; // Declare color 'c' + * noStroke(); // Don't draw a stroke around shapes + * + * // If no colorMode is specified, then the + * // default of RGB with scale of 0-255 is used. + * c = color(50, 55, 100); // Create a color for 'c' + * fill(c); // Use color variable 'c' as fill color + * rect(0, 10, 45, 80); // Draw left rect + * + * colorMode(HSB, 100); // Use HSB with scale of 0-100 + * c = color(50, 55, 100); // Update 'c' with new color + * fill(c); // Use updated 'c' as fill color + * rect(55, 10, 45, 80); // Draw right rect + * </code> + * </div> + */ +p5.prototype.color = function() { + if (arguments[0] instanceof p5.Color) { + return arguments[0]; // Do nothing if argument is already a color object. + } else if (arguments[0] instanceof Array) { + if (this instanceof p5.Renderer) { + return new p5.Color(this, arguments[0]); + } else { + return new p5.Color(this._renderer, arguments[0]); + } + } else { + if (this instanceof p5.Renderer) { + return new p5.Color(this, arguments); + } else { + return new p5.Color(this._renderer, arguments); + } + } +}; + +/** + * Extracts the green value from a color or pixel array. + * + * @method green + * @param {Object} color p5.Color object or pixel array + * @example + * <div> + * <code> + * c = color(20, 75, 200); // Define color 'c' + * fill(c); // Use color variable 'c' as fill color + * rect(15, 20, 35, 60); // Draw left rectangle + * + * greenValue = green(c); // Get green in 'c' + * println(greenValue); // Print "75.0" + * fill(0, greenValue, 0); // Use 'greenValue' in new fill + * rect(50, 20, 35, 60); // Draw right rectangle + * </code> + * </div> + */ +p5.prototype.green = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getGreen(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Extracts the hue value from a color or pixel array. + * + * Hue exists in both HSB and HSL. This function will return the + * HSB-normalized hue when supplied with an HSB color object (or when supplied + * with a pixel array while the color mode is HSB), but will default to the + * HSL-normalized hue otherwise. (The values will only be different if the + * maximum hue setting for each system is different.) + * + * @method hue + * @param {Object} color p5.Color object or pixel array + * @example + * <div> + * <code> + * noStroke(); + * colorMode(HSB, 255); + * c = color(0, 126, 255); + * fill(c); + * rect(15, 20, 35, 60); + * value = hue(c); // Sets 'value' to "0" + * fill(value); + * rect(50, 20, 35, 60); + * </code> + * </div> + */ +p5.prototype.hue = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getHue(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Blends two colors to find a third color somewhere between them. The amt + * parameter is the amount to interpolate between the two values where 0.0 + * equal to the first color, 0.1 is very near the first color, 0.5 is halfway + * in between, etc. An amount below 0 will be treated as 0. Likewise, amounts + * above 1 will be capped at 1. This is different from the behavior of lerp(), + * but necessary because otherwise numbers outside the range will produce + * strange and unexpected colors. + * <br><br> + * The way that colours are interpolated depends on the current color mode. + * + * @method lerpColor + * @param {Array/Number} c1 interpolate from this color + * @param {Array/Number} c2 interpolate to this color + * @param {Number} amt number between 0 and 1 + * @return {Array/Number} interpolated color + * @example + * <div> + * <code> + * colorMode(RGB); + * stroke(255); + * background(51); + * from = color(218, 165, 32); + * to = color(72, 61, 139); + * colorMode(RGB); // Try changing to HSB. + * interA = lerpColor(from, to, .33); + * interB = lerpColor(from, to, .66); + * fill(from); + * rect(10, 20, 20, 60); + * fill(interA); + * rect(30, 20, 20, 60); + * fill(interB); + * rect(50, 20, 20, 60); + * fill(to); + * rect(70, 20, 20, 60); + * </code> + * </div> + */ +p5.prototype.lerpColor = function(c1, c2, amt) { + var mode = this._renderer._colorMode; + var maxes = this._renderer._colorMaxes; + var l0, l1, l2, l3; + var fromArray, toArray; + + if (mode === constants.RGB) { + fromArray = c1.levels.map(function(level) { + return level / 255; + }); + toArray = c2.levels.map(function(level) { + return level / 255; + }); + } else if (mode === constants.HSB) { + c1._getBrightness(); // Cache hsba so it definitely exists. + c2._getBrightness(); + fromArray = c1.hsba; + toArray = c2.hsba; + } else if (mode === constants.HSL) { + c1._getLightness(); // Cache hsla so it definitely exists. + c2._getLightness(); + fromArray = c1.hsla; + toArray = c2.hsla; + } else { + throw new Error (mode + 'cannot be used for interpolation.'); + } + + // Prevent extrapolation. + amt = Math.max(Math.min(amt, 1), 0); + + // Perform interpolation. + l0 = this.lerp(fromArray[0], toArray[0], amt); + l1 = this.lerp(fromArray[1], toArray[1], amt); + l2 = this.lerp(fromArray[2], toArray[2], amt); + l3 = this.lerp(fromArray[3], toArray[3], amt); + + // Scale components. + l0 *= maxes[mode][0]; + l1 *= maxes[mode][1]; + l2 *= maxes[mode][2]; + l3 *= maxes[mode][3]; + + return this.color(l0, l1, l2, l3); +}; + +/** + * Extracts the HSL lightness value from a color or pixel array. + * + * @method lightness + * @param {Object} color p5.Color object or pixel array + * @example + * <div> + * <code> + * noStroke(); + * colorMode(HSL); + * c = color(156, 100, 50, 1); + * fill(c); + * rect(15, 20, 35, 60); + * value = lightness(c); // Sets 'value' to 50 + * fill(value); + * rect(50, 20, 35, 60); + * </code> + * </div> + */ +p5.prototype.lightness = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getLightness(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Extracts the red value from a color or pixel array. + * + * @method red + * @param {Object} obj p5.Color object or pixel array + * @example + * <div> + * <code> + * c = color(255, 204, 0); // Define color 'c' + * fill(c); // Use color variable 'c' as fill color + * rect(15, 20, 35, 60); // Draw left rectangle + * + * redValue = red(c); // Get red in 'c' + * println(redValue); // Print "255.0" + * fill(redValue, 0, 0); // Use 'redValue' in new fill + * rect(50, 20, 35, 60); // Draw right rectangle + * </code> + * </div> + * + * <div> + * <code> + * colorMode(RGB, 255); + * var c = color(127, 255, 0); + * colorMode(RGB, 1); + * var myColor = red(c); + * print(myColor); + * </code> + * </div> + */ +p5.prototype.red = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getRed(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Extracts the saturation value from a color or pixel array. + * + * Saturation is scaled differently in HSB and HSL. This function will return + * the HSB saturation when supplied with an HSB color object (or when supplied + * with a pixel array while the color mode is HSB), but will default to the + * HSL saturation otherwise. + * + * @method saturation + * @param {Object} color p5.Color object or pixel array + * @example + * <div> + * <code> + * noStroke(); + * colorMode(HSB, 255); + * c = color(0, 126, 255); + * fill(c); + * rect(15, 20, 35, 60); + * value = saturation(c); // Sets 'value' to 126 + * fill(value); + * rect(50, 20, 35, 60); + * </code> + * </div> + */ +p5.prototype.saturation = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getSaturation(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +module.exports = p5; + +},{"../core/constants":47,"../core/core":48,"./p5.Color":42}],42:[function(_dereq_,module,exports){ +/** + * @module Color + * @submodule Creating & Reading + * @for p5 + * @requires core + * @requires constants + * @requires color_conversion + */ + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); +var color_conversion = _dereq_('./color_conversion'); + +/** + * We define colors to be immutable objects. Each color stores the color mode + * and level maxes that applied at the time of its construction. These are + * used to interpret the input arguments and to format the output e.g. when + * saturation() is requested. + * + * Internally we store an array representing the ideal RGBA values in floating + * point form, normalized from 0 to 1. From this we calculate the closest + * screen color (RGBA levels from 0 to 255) and expose this to the renderer. + * + * We also cache normalized, floating point components of the color in various + * representations as they are calculated. This is done to prevent repeating a + * conversion that has already been performed. + * + * @class p5.Color + * @constructor + */ +p5.Color = function(renderer, vals) { + + // Record color mode and maxes at time of construction. + this.mode = renderer._colorMode; + this.maxes = renderer._colorMaxes; + + // Calculate normalized RGBA values. + if (this.mode !== constants.RGB && + this.mode !== constants.HSL && + this.mode !== constants.HSB) { + throw new Error(this.mode + ' is an invalid colorMode.'); + } else { + this._array = p5.Color._parseInputs.apply(renderer, vals); + } + + // Expose closest screen color. + this.levels = this._array.map(function(level) { + return Math.round(level * 255); + }); + + return this; +}; + +p5.Color.prototype.toString = function() { + var a = this.levels; + a[3] = this._array[3]; // String representation uses normalized alpha. + return 'rgba('+a[0]+','+a[1]+','+a[2]+','+ a[3] +')'; +}; + +p5.Color.prototype._getAlpha = function() { + return this._array[3] * this.maxes[this.mode][3]; +}; + +p5.Color.prototype._getBlue = function() { + return this._array[2] * this.maxes[constants.RGB][2]; +}; + +p5.Color.prototype._getBrightness = function() { + if (!this.hsba) { + this.hsba = color_conversion._rgbaToHSBA(this._array); + } + return this.hsba[2] * this.maxes[constants.HSB][2]; +}; + +p5.Color.prototype._getGreen = function() { + return this._array[1] * this.maxes[constants.RGB][1]; +}; + +/** + * Hue is the same in HSB and HSL, but the maximum value may be different. + * This function will return the HSB-normalized saturation when supplied with + * an HSB color object, but will default to the HSL-normalized saturation + * otherwise. + */ +p5.Color.prototype._getHue = function() { + if (this.mode === constants.HSB) { + if (!this.hsba) { + this.hsba = color_conversion._rgbaToHSBA(this._array); + } + return this.hsba[0] * this.maxes[constants.HSB][0]; + } else { + if (!this.hsla) { + this.hsla = color_conversion._rgbaToHSLA(this._array); + } + return this.hsla[0] * this.maxes[constants.HSL][0]; + } +}; + +p5.Color.prototype._getLightness = function() { + if (!this.hsla) { + this.hsla = color_conversion._rgbaToHSLA(this._array); + } + return this.hsla[2] * this.maxes[constants.HSL][2]; +}; + +p5.Color.prototype._getRed = function() { + return this._array[0] * this.maxes[constants.RGB][0]; +}; + +/** + * Saturation is scaled differently in HSB and HSL. This function will return + * the HSB saturation when supplied with an HSB color object, but will default + * to the HSL saturation otherwise. + */ +p5.Color.prototype._getSaturation = function() { + if (this.mode === constants.HSB) { + if (!this.hsba) { + this.hsba = color_conversion._rgbaToHSBA(this._array); + } + return this.hsba[1] * this.maxes[constants.HSB][1]; + } else { + if (!this.hsla) { + this.hsla = color_conversion._rgbaToHSLA(this._array); + } + return this.hsla[1] * this.maxes[constants.HSL][1]; + } +}; + +/** + * CSS named colors. + */ +var namedColors = { + 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', + darkgrey: '#a9a9a9', + darkkhaki: '#bdb76b', + darkmagenta: '#8b008b', + darkolivegreen: '#556b2f', + darkorange: '#ff8c00', + darkorchid: '#9932cc', + darkred: '#8b0000', + darksalmon: '#e9967a', + darkseagreen: '#8fbc8f', + darkslateblue: '#483d8b', + darkslategray: '#2f4f4f', + darkslategrey: '#2f4f4f', + darkturquoise: '#00ced1', + darkviolet: '#9400d3', + deeppink: '#ff1493', + deepskyblue: '#00bfff', + dimgray: '#696969', + dimgrey: '#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', + grey: '#808080', + 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', + lightgray: '#d3d3d3', + lightgreen: '#90ee90', + lightgrey: '#d3d3d3', + lightpink: '#ffb6c1', + lightsalmon: '#ffa07a', + lightseagreen: '#20b2aa', + lightskyblue: '#87cefa', + lightslategray: '#778899', + lightslategrey: '#778899', + lightsteelblue: '#b0c4de', + lightyellow: '#ffffe0', + lime: '#00ff00', + limegreen: '#32cd32', + linen: '#faf0e6', + magenta: '#ff00ff', + maroon: '#800000', + mediumaquamarine: '#66cdaa', + mediumblue: '#0000cd', + mediumorchid: '#ba55d3', + mediumpurple: '#9370db', + 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: '#db7093', + 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', + slategrey: '#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' +}; + +/** + * These regular expressions are used to build up the patterns for matching + * viable CSS color strings: fragmenting the regexes in this way increases the + * legibility and comprehensibility of the code. + * + * Note that RGB values of .9 are not parsed by IE, but are supported here for + * color string consistency. + */ +var WHITESPACE = /\s*/; // Match zero or more whitespace characters. +var INTEGER = /(\d{1,3})/; // Match integers: 79, 255, etc. +var DECIMAL = /((?:\d+(?:\.\d+)?)|(?:\.\d+))/; // Match 129.6, 79, .9, etc. +var PERCENT = new RegExp(DECIMAL.source + '%'); // Match 12.9%, 79%, .9%, etc. + +/** + * Full color string patterns. The capture groups are necessary. + */ +var colorPatterns = { + // Match colors in format #XXX, e.g. #416. + HEX3: /^#([a-f0-9])([a-f0-9])([a-f0-9])$/i, + + // Match colors in format #XXXXXX, e.g. #b4d455. + HEX6: /^#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i, + + // Match colors in format rgb(R, G, B), e.g. rgb(255, 0, 128). + RGB: new RegExp([ + '^rgb\\(', + INTEGER.source, + ',', + INTEGER.source, + ',', + INTEGER.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format rgb(R%, G%, B%), e.g. rgb(100%, 0%, 28.9%). + RGB_PERCENT: new RegExp([ + '^rgb\\(', + PERCENT.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format rgb(R, G, B, A), e.g. rgb(255, 0, 128, 0.25). + RGBA: new RegExp([ + '^rgba\\(', + INTEGER.source, + ',', + INTEGER.source, + ',', + INTEGER.source, + ',', + DECIMAL.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format rgb(R%, G%, B%, A), e.g. rgb(100%, 0%, 28.9%, 0.5). + RGBA_PERCENT: new RegExp([ + '^rgba\\(', + PERCENT.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + ',', + DECIMAL.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format hsla(H, S%, L%), e.g. hsl(100, 40%, 28.9%). + HSL: new RegExp([ + '^hsl\\(', + INTEGER.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format hsla(H, S%, L%, A), e.g. hsla(100, 40%, 28.9%, 0.5). + HSLA: new RegExp([ + '^hsla\\(', + INTEGER.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + ',', + DECIMAL.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format hsb(H, S%, B%), e.g. hsb(100, 40%, 28.9%). + HSB: new RegExp([ + '^hsb\\(', + INTEGER.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format hsba(H, S%, B%, A), e.g. hsba(100, 40%, 28.9%, 0.5). + HSBA: new RegExp([ + '^hsba\\(', + INTEGER.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + ',', + DECIMAL.source, + '\\)$' + ].join(WHITESPACE.source), 'i') +}; + +/** + * For a number of different inputs, returns a color formatted as [r, g, b, a] + * arrays, with each component normalized between 0 and 1. + * + * @param {Array-like} args An 'array-like' object that represents a list of + * arguments + * @return {Array} a color formatted as [r, g, b, a] + * Example: + * input ==> output + * g ==> [g, g, g, 255] + * g,a ==> [g, g, g, a] + * r, g, b ==> [r, g, b, 255] + * r, g, b, a ==> [r, g, b, a] + * [g] ==> [g, g, g, 255] + * [g, a] ==> [g, g, g, a] + * [r, g, b] ==> [r, g, b, 255] + * [r, g, b, a] ==> [r, g, b, a] + * @example + * <div> + * <code> + * // todo + * </code> + * </div> + */ +p5.Color._parseInputs = function() { + var numArgs = arguments.length; + var mode = this._colorMode; + var maxes = this._colorMaxes; + var results = []; + + if (numArgs >= 3) { // Argument is a list of component values. + + results[0] = arguments[0] / maxes[mode][0]; + results[1] = arguments[1] / maxes[mode][1]; + results[2] = arguments[2] / maxes[mode][2]; + + // Alpha may be undefined, so default it to 100%. + if (typeof arguments[3] === 'number') { + results[3] = arguments[3] / maxes[mode][3]; + } else { + results[3] = 1; + } + + // Constrain components to the range [0,1]. + results = results.map(function(value) { + return Math.max(Math.min(value, 1), 0); + }); + + // Convert to RGBA and return. + if (mode === constants.HSL) { + return color_conversion._hslaToRGBA(results); + } else if (mode === constants.HSB) { + return color_conversion._hsbaToRGBA(results); + } else { + return results; + } + + } else if (numArgs === 1 && typeof arguments[0] === 'string') { + + var str = arguments[0].trim().toLowerCase(); + + // Return if string is a named colour. + if (namedColors[str]) { + return p5.Color._parseInputs.apply(this, [namedColors[str]]); + } + + // Try RGBA pattern matching. + if (colorPatterns.HEX3.test(str)) { // #rgb + results = colorPatterns.HEX3.exec(str).slice(1).map(function(color) { + return parseInt(color + color, 16) / 255; + }); + results[3] = 1; + return results; + } else if (colorPatterns.HEX6.test(str)) { // #rrggbb + results = colorPatterns.HEX6.exec(str).slice(1).map(function(color) { + return parseInt(color, 16) / 255; + }); + results[3] = 1; + return results; + } else if (colorPatterns.RGB.test(str)) { // rgb(R,G,B) + results = colorPatterns.RGB.exec(str).slice(1).map(function(color) { + return color / 255; + }); + results[3] = 1; + return results; + } else if (colorPatterns.RGB_PERCENT.test(str)) { // rgb(R%,G%,B%) + results = colorPatterns.RGB_PERCENT.exec(str).slice(1) + .map(function(color) { + return parseFloat(color) / 100; + }); + results[3] = 1; + return results; + } else if (colorPatterns.RGBA.test(str)) { // rgba(R,G,B,A) + results = colorPatterns.RGBA.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 3) { + return parseFloat(color); + } + return color / 255; + }); + return results; + } else if (colorPatterns.RGBA_PERCENT.test(str)) { // rgba(R%,G%,B%,A%) + results = colorPatterns.RGBA_PERCENT.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 3) { + return parseFloat(color); + } + return parseFloat(color) / 100; + }); + return results; + } + + // Try HSLA pattern matching. + if (colorPatterns.HSL.test(str)) { // hsl(H,S,L) + results = colorPatterns.HSL.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 0) { + return parseInt(color, 10) / 360; + } + return parseInt(color, 10) / 100; + }); + results[3] = 1; + } else if (colorPatterns.HSLA.test(str)) { // hsla(H,S,L,A) + results = colorPatterns.HSLA.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 0) { + return parseInt(color, 10) / 360; + } + else if (idx === 3) { + return parseFloat(color); + } + return parseInt(color, 10) / 100; + }); + } + if (results.length) { + return color_conversion._hslaToRGBA(results); + } + + // Try HSBA pattern matching. + if (colorPatterns.HSB.test(str)) { // hsb(H,S,B) + results = colorPatterns.HSB.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 0) { + return parseInt(color, 10) / 360; + } + return parseInt(color, 10) / 100; + }); + results[3] = 1; + } else if (colorPatterns.HSBA.test(str)) { // hsba(H,S,B,A) + results = colorPatterns.HSBA.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 0) { + return parseInt(color, 10) / 360; + } + else if (idx === 3) { + return parseFloat(color); + } + return parseInt(color, 10) / 100; + }); + } + if (results.length) { + return color_conversion._hsbaToRGBA(results); + } + + // Input did not match any CSS color pattern: default to white. + results = [1, 1, 1, 1]; + + } else if ((numArgs === 1 || numArgs === 2) && + typeof arguments[0] === 'number') { // 'Grayscale' mode. + + /** + * For HSB and HSL, interpret the gray level as a brightness/lightness + * value (they are equivalent when chroma is zero). For RGB, normalize the + * gray level according to the blue maximum. + */ + results[0] = arguments[0] / maxes[mode][2]; + results[1] = arguments[0] / maxes[mode][2]; + results[2] = arguments[0] / maxes[mode][2]; + + // Alpha may be undefined, so default it to 100%. + if (typeof arguments[1] === 'number') { + results[3] = arguments[1] / maxes[mode][3]; + } else { + results[3] = 1; + } + + // Constrain components to the range [0,1]. + results = results.map(function(value) { + return Math.max(Math.min(value, 1), 0); + }); + + } else { + throw new Error (arguments + 'is not a valid color representation.'); + } + + return results; +}; + +module.exports = p5.Color; + +},{"../core/constants":47,"../core/core":48,"./color_conversion":40}],43:[function(_dereq_,module,exports){ +/** + * @module Color + * @submodule Setting + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); +_dereq_('./p5.Color'); + +/** + * The background() function sets the color used for the background of the + * p5.js canvas. The default background is light gray. This function is + * typically used within draw() to clear the display window at the beginning + * of each frame, but it can be used inside setup() to set the background on + * the first frame of animation or if the background need only be set once. + * + * @method background + * @param {Number|String|p5.Color|p5.Image} v1 gray value, red or hue value + * (depending on the current + * color mode), color string, + * p5.Color, or p5.Image + * @param {Number} [v2] green or saturation value + * (depending on the current + * color mode) + * @param {Number} [v3] blue or brightness value + * (depending on the current + * color mode) + * @param {Number} [a] opacity of the background + * + * @example + * <div> + * <code> + * // Grayscale integer value + * background(51); + * </code> + * </div> + * + * <div> + * <code> + * // R, G & B integer values + * background(255, 204, 0); + * </code> + * </div> + * + * <div> + * <code> + * // H, S & B integer values + * colorMode(HSB); + * background(255, 204, 100); + * </code> + * </div> + * + * <div> + * <code> + * // Named SVG/CSS color string + * background('red'); + * </code> + * </div> + * + * <div> + * <code> + * // three-digit hexadecimal RGB notation + * background('#fae'); + * </code> + * </div> + * + * <div> + * <code> + * // six-digit hexadecimal RGB notation + * background('#222222'); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGB notation + * background('rgb(0,255,0)'); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGBA notation + * background('rgba(0,255,0, 0.25)'); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGB notation + * background('rgb(100%,0%,10%)'); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGBA notation + * background('rgba(100%,0%,100%,0.5)'); + * </code> + * </div> + * + * <div> + * <code> + * // p5 Color object + * background(color(0, 0, 255)); + * </code> + * </div> + */ +p5.prototype.background = function() { + if (arguments[0] instanceof p5.Image) { + this.image(arguments[0], 0, 0, this.width, this.height); + } else { + this._renderer.background.apply(this._renderer, arguments); + } + return this; +}; + +/** + * Clears the pixels within a buffer. This function only works on p5.Canvas + * objects created with the createCanvas() function; it won't work with the + * main display window. Unlike the main graphics context, pixels in + * additional graphics areas created with createGraphics() can be entirely + * or partially transparent. This function clears everything to make all of + * the pixels 100% transparent. + * + * @method clear + * @example + * <div> + * <code> + * // Clear the screen on mouse press. + * function setup() { + * createCanvas(100, 100); + * } + * + * function draw() { + * ellipse(mouseX, mouseY, 20, 20); + * } + * + * function mousePressed() { + * clear(); + * } + * </code> + * </div> + */ +p5.prototype.clear = function() { + this._renderer.clear(); + return this; +}; + +/** + * colorMode() changes the way p5.js interprets color data. By default, the + * parameters for fill(), stroke(), background(), and color() are defined by + * values between 0 and 255 using the RGB color model. This is equivalent to + * setting colorMode(RGB, 255). Setting colorMode(HSB) lets you use the HSB + * system instead. By default, this is colorMode(HSB, 360, 100, 100, 1). You + * can also use HSL. + * <br><br> + * Note: existing color objects remember the mode that they were created in, + * so you can change modes as you like without affecting their appearance. + * + * @method colorMode + * @param {Number|Constant} mode either RGB or HSB, corresponding to + * Red/Green/Blue and Hue/Saturation/Brightness + * (or Lightness) + * @param {Number|Constant} [max1] range for the red or hue depending on the + * current color mode, or range for all values + * @param {Number|Constant} [max2] range for the green or saturation depending + * on the current color mode + * @param {Number|Constant} [max3] range for the blue or brightness/lighntess + * depending on the current color mode + * @param {Number|Constant} [maxA] range for the alpha + * @example + * <div> + * <code> + * noStroke(); + * colorMode(RGB, 100); + * for (i = 0; i < 100; i++) { + * for (j = 0; j < 100; j++) { + * stroke(i, j, 0); + * point(i, j); + * } + * } + * </code> + * </div> + * + * <div> + * <code> + * noStroke(); + * colorMode(HSB, 100); + * for (i = 0; i < 100; i++) { + * for (j = 0; j < 100; j++) { + * stroke(i, j, 100); + * point(i, j); + * } + * } + * </code> + * </div> + * + * <div> + * <code> + * colorMode(RGB, 255); + * var c = color(127, 255, 0); + * + * colorMode(RGB, 1); + * var myColor = c._getRed(); + * text(myColor, 10, 10, 80, 80); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * colorMode(RGB, 255, 255, 255, 1); + * background(255); + * + * strokeWeight(4); + * stroke(255, 0 , 10, 0.3); + * ellipse(40, 40, 50, 50); + * ellipse(50, 50, 40, 40); + * </code> + * </div> + */ +p5.prototype.colorMode = function() { + if (arguments[0] === constants.RGB || + arguments[0] === constants.HSB || + arguments[0] === constants.HSL) { + + // Set color mode. + this._renderer._colorMode = arguments[0]; + + // Set color maxes. + var maxes = this._renderer._colorMaxes[this._renderer._colorMode]; + if (arguments.length === 2) { + maxes[0] = arguments[1]; // Red + maxes[1] = arguments[1]; // Green + maxes[2] = arguments[1]; // Blue + maxes[3] = arguments[1]; // Alpha + } else if (arguments.length === 4) { + maxes[0] = arguments[1]; // Red + maxes[1] = arguments[2]; // Green + maxes[2] = arguments[3]; // Blue + } else if (arguments.length === 5) { + maxes[0] = arguments[1]; // Red + maxes[1] = arguments[2]; // Green + maxes[2] = arguments[3]; // Blue + maxes[3] = arguments[4]; // Alpha + } + } + + return this; +}; + +/** + * Sets the color used to fill shapes. For example, if you run + * fill(204, 102, 0), all subsequent shapes will be filled with orange. This + * color is either specified in terms of the RGB or HSB color depending on + * the current colorMode(). (The default color space is RGB, with each value + * in the range from 0 to 255). + * <br><br> + * If a single string argument is provided, RGB, RGBA and Hex CSS color strings + * and all named color strings are supported. A p5 Color object can also be + * provided to set the fill color. + * + * @method fill + * @param {Number|Array|String|p5.Color} v1 gray value, red or hue value + * (depending on the current color + * mode), or color Array, or CSS + * color string + * @param {Number} [v2] green or saturation value + * (depending on the current + * color mode) + * @param {Number} [v3] blue or brightness value + * (depending on the current + * color mode) + * @param {Number} [a] opacity of the background + * + * @example + * <div> + * <code> + * // Grayscale integer value + * fill(51); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // R, G & B integer values + * fill(255, 204, 0); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // H, S & B integer values + * colorMode(HSB); + * fill(255, 204, 100); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // Named SVG/CSS color string + * fill('red'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // three-digit hexadecimal RGB notation + * fill('#fae'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // six-digit hexadecimal RGB notation + * fill('#222222'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGB notation + * fill('rgb(0,255,0)'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGBA notation + * fill('rgba(0,255,0, 0.25)'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGB notation + * fill('rgb(100%,0%,10%)'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGBA notation + * fill('rgba(100%,0%,100%,0.5)'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // p5 Color object + * fill(color(0, 0, 255)); + * rect(20, 20, 60, 60); + * </code> + * </div> + */ +p5.prototype.fill = function() { + this._renderer._setProperty('_fillSet', true); + this._renderer._setProperty('_doFill', true); + this._renderer.fill.apply(this._renderer, arguments); + return this; +}; + +/** + * Disables filling geometry. If both noStroke() and noFill() are called, + * nothing will be drawn to the screen. + * + * @method noFill + * @example + * <div> + * <code> + * rect(15, 10, 55, 55); + * noFill(); + * rect(20, 20, 60, 60); + * </code> + * </div> + */ +p5.prototype.noFill = function() { + this._renderer._setProperty('_doFill', false); + return this; +}; + +/** + * Disables drawing the stroke (outline). If both noStroke() and noFill() + * are called, nothing will be drawn to the screen. + * + * @method noStroke + * @example + * <div> + * <code> + * noStroke(); + * rect(20, 20, 60, 60); + * </code> + * </div> + */ +p5.prototype.noStroke = function() { + this._renderer._setProperty('_doStroke', false); + return this; +}; + +/** + * Sets the color used to draw lines and borders around shapes. This color + * is either specified in terms of the RGB or HSB color depending on the + * current colorMode() (the default color space is RGB, with each value in + * the range from 0 to 255). + * <br><br> + * If a single string argument is provided, RGB, RGBA and Hex CSS color + * strings and all named color strings are supported. A p5 Color object + * can also be provided to set the stroke color. + * + * @method stroke + * @param {Number|Array|String|p5.Color} v1 gray value, red or hue value + * (depending on the current color + * mode), or color Array, or CSS + * color string + * @param {Number} [v2] green or saturation value + * (depending on the current + * color mode) + * @param {Number} [v3] blue or brightness value + * (depending on the current + * color mode) + * @param {Number} [a] opacity of the background + * + * @example + * <div> + * <code> + * // Grayscale integer value + * strokeWeight(4); + * stroke(51); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // R, G & B integer values + * stroke(255, 204, 0); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // H, S & B integer values + * colorMode(HSB); + * strokeWeight(4); + * stroke(255, 204, 100); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // Named SVG/CSS color string + * stroke('red'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // three-digit hexadecimal RGB notation + * stroke('#fae'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // six-digit hexadecimal RGB notation + * stroke('#222222'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGB notation + * stroke('rgb(0,255,0)'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGBA notation + * stroke('rgba(0,255,0,0.25)'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGB notation + * stroke('rgb(100%,0%,10%)'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGBA notation + * stroke('rgba(100%,0%,100%,0.5)'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // p5 Color object + * stroke(color(0, 0, 255)); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + */ +p5.prototype.stroke = function() { + this._renderer._setProperty('_strokeSet', true); + this._renderer._setProperty('_doStroke', true); + this._renderer.stroke.apply(this._renderer, arguments); + return this; +}; + +module.exports = p5; + +},{"../core/constants":47,"../core/core":48,"./p5.Color":42}],44:[function(_dereq_,module,exports){ +/** + * @module Shape + * @submodule 2D Primitives + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); + +_dereq_('./error_helpers'); + +/** + * Draw an arc to the screen. If called with only a, b, c, d, start, and + * stop, the arc will be drawn as an open pie. If mode is provided, the arc + * will be drawn either open, as a chord, or as a pie as specified. The + * origin may be changed with the ellipseMode() function.<br><br> + * Note that drawing a full circle (ex: 0 to TWO_PI) will appear blank + * because 0 and TWO_PI are the same position on the unit circle. The + * best way to handle this is by using the ellipse() function instead + * to create a closed ellipse, and to use the arc() function + * only to draw parts of an ellipse. + * + * @method arc + * @param {Number} a x-coordinate of the arc's ellipse + * @param {Number} b y-coordinate of the arc's ellipse + * @param {Number} c width of the arc's ellipse by default + * @param {Number} d height of the arc's ellipse by default + * @param {Number} start angle to start the arc, specified in radians + * @param {Number} stop angle to stop the arc, specified in radians + * @param {String} [mode] optional parameter to determine the way of drawing + * the arc + * @return {Object} the p5 object + * @example + * <div> + * <code> + * arc(50, 55, 50, 50, 0, HALF_PI); + * noFill(); + * arc(50, 55, 60, 60, HALF_PI, PI); + * arc(50, 55, 70, 70, PI, PI+QUARTER_PI); + * arc(50, 55, 80, 80, PI+QUARTER_PI, TWO_PI); + * </code> + * </div> + * + * <div> + * <code> + * arc(50, 50, 80, 80, 0, PI+QUARTER_PI, OPEN); + * </code> + * </div> + * + * <div> + * <code> + * arc(50, 50, 80, 80, 0, PI+QUARTER_PI, CHORD); + * </code> + * </div> + * + * <div> + * <code> + * arc(50, 50, 80, 80, 0, PI+QUARTER_PI, PIE); + * </code> + * </div> + */ +p5.prototype.arc = function(x, y, w, h, start, stop, mode) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'arc', + args, + [ + ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'], + [ 'Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'String' ] + ] + ); + + if (!this._renderer._doStroke && !this._renderer._doFill) { + return this; + } + if (this._angleMode === constants.DEGREES) { + start = this.radians(start); + stop = this.radians(stop); + } + + // Make all angles positive... + while (start < 0) { + start += constants.TWO_PI; + } + while (stop < 0) { + stop += constants.TWO_PI; + } + // ...and confine them to the interval [0,TWO_PI). + start %= constants.TWO_PI; + stop %= constants.TWO_PI; + + // Adjust angles to counter linear scaling. + if (start <= constants.HALF_PI) { + start = Math.atan(w / h * Math.tan(start)); + } else if (start > constants.HALF_PI && start <= 3 * constants.HALF_PI) { + start = Math.atan(w / h * Math.tan(start)) + constants.PI; + } else { + start = Math.atan(w / h * Math.tan(start)) + constants.TWO_PI; + } + if (stop <= constants.HALF_PI) { + stop = Math.atan(w / h * Math.tan(stop)); + } else if (stop > constants.HALF_PI && stop <= 3 * constants.HALF_PI) { + stop = Math.atan(w / h * Math.tan(stop)) + constants.PI; + } else { + stop = Math.atan(w / h * Math.tan(stop)) + constants.TWO_PI; + } + + // Exceed the interval if necessary in order to preserve the size and + // orientation of the arc. + if (start > stop) { + stop += constants.TWO_PI; + } + // p5 supports negative width and heights for ellipses + w = Math.abs(w); + h = Math.abs(h); + this._renderer.arc(x, y, w, h, start, stop, mode); + return this; +}; + +/** + * Draws an ellipse (oval) to the screen. An ellipse with equal width and + * height is a circle. By default, the first two parameters set the location, + * and the third and fourth parameters set the shape's width and height. The + * origin may be changed with the ellipseMode() function. + * + * @method ellipse + * @param {Number} a x-coordinate of the ellipse. + * @param {Number} b y-coordinate of the ellipse. + * @param {Number} c width of the ellipse. + * @param {Number} d height of the ellipse. + * @return {p5} the p5 object + * @example + * <div> + * <code> + * ellipse(56, 46, 55, 55); + * </code> + * </div> + */ +p5.prototype.ellipse = function(x, y, w, h) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'ellipse', + args, + ['Number', 'Number', 'Number', 'Number'] + ); + + if (!this._renderer._doStroke && !this._renderer._doFill) { + return this; + } + // p5 supports negative width and heights for ellipses + w = Math.abs(w); + h = Math.abs(h); + //@TODO add catch block here if this._renderer + //doesn't have the method implemented yet + this._renderer.ellipse(x, y, w, h); + return this; +}; +/** + * Draws a line (a direct path between two points) to the screen. The version + * of line() with four parameters draws the line in 2D. To color a line, use + * the stroke() function. A line cannot be filled, therefore the fill() + * function will not affect the color of a line. 2D lines are drawn with a + * width of one pixel by default, but this can be changed with the + * strokeWeight() function. + * + * @method line + * @param {Number} x1 the x-coordinate of the first point + * @param {Number} y1 the y-coordinate of the first point + * @param {Number} x2 the x-coordinate of the second point + * @param {Number} y2 the y-coordinate of the second point + * @return {p5} the p5 object + * @example + * <div> + * <code> + * line(30, 20, 85, 75); + * </code> + * </div> + * + * <div> + * <code> + * line(30, 20, 85, 20); + * stroke(126); + * line(85, 20, 85, 75); + * stroke(255); + * line(85, 75, 30, 75); + * </code> + * </div> + */ +////commented out original +// p5.prototype.line = function(x1, y1, x2, y2) { +// if (!this._renderer._doStroke) { +// return this; +// } +// if(this._renderer.isP3D){ +// } else { +// this._renderer.line(x1, y1, x2, y2); +// } +// }; +p5.prototype.line = function() { + if (!this._renderer._doStroke) { + return this; + } + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + //check whether we should draw a 3d line or 2d + if(this._renderer.isP3D){ + this._validateParameters( + 'line', + args, + [ + ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'] + ] + ); + this._renderer.line( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5]); + } else { + this._validateParameters( + 'line', + args, + [ + ['Number', 'Number', 'Number', 'Number'], + ] + ); + this._renderer.line( + args[0], + args[1], + args[2], + args[3]); + } + return this; +}; + +/** + * Draws a point, a coordinate in space at the dimension of one pixel. + * The first parameter is the horizontal value for the point, the second + * value is the vertical value for the point. The color of the point is + * determined by the current stroke. + * + * @method point + * @param {Number} x the x-coordinate + * @param {Number} y the y-coordinate + * @return {p5} the p5 object + * @example + * <div> + * <code> + * point(30, 20); + * point(85, 20); + * point(85, 75); + * point(30, 75); + * </code> + * </div> + */ +p5.prototype.point = function() { + if (!this._renderer._doStroke) { + return this; + } + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + //check whether we should draw a 3d line or 2d + if(this._renderer.isP3D){ + this._validateParameters( + 'point', + args, + [ + ['Number', 'Number', 'Number'] + ] + ); + this._renderer.point( + args[0], + args[1], + args[2] + ); + } else { + this._validateParameters( + 'point', + args, + [ + ['Number', 'Number'] + ] + ); + this._renderer.point( + args[0], + args[1] + ); + } + return this; +}; + + +/** + * Draw a quad. A quad is a quadrilateral, a four sided polygon. It is + * similar to a rectangle, but the angles between its edges are not + * constrained to ninety degrees. The first pair of parameters (x1,y1) + * sets the first vertex and the subsequent pairs should proceed + * clockwise or counter-clockwise around the defined shape. + * + * @method quad + * @param {Number} x1 the x-coordinate of the first point + * @param {Number} y1 the y-coordinate of the first point + * @param {Number} x2 the x-coordinate of the second point + * @param {Number} y2 the y-coordinate of the second point + * @param {Number} x3 the x-coordinate of the third point + * @param {Number} y3 the y-coordinate of the third point + * @param {Number} x4 the x-coordinate of the fourth point + * @param {Number} y4 the y-coordinate of the fourth point + * @return {p5} the p5 object + * @example + * <div> + * <code> + * quad(38, 31, 86, 20, 69, 63, 30, 76); + * </code> + * </div> + */ +p5.prototype.quad = function() { + if (!this._renderer._doStroke && !this._renderer._doFill) { + return this; + } + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + if(this._renderer.isP3D){ + this._validateParameters( + 'quad', + args, + [ + [ 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number'] + ] + ); + this._renderer.quad( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5], + args[6], + args[7], + args[8], + args[9], + args[10], + args[11] + ); + } else { + this._validateParameters( + 'quad', + args, + [ + [ 'Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', 'Number' ] + ] + ); + this._renderer.quad( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5], + args[6], + args[7] + ); + } + return this; +}; + +/** +* Draws a rectangle to the screen. A rectangle is a four-sided shape with +* every angle at ninety degrees. By default, the first two parameters set +* the location of the upper-left corner, the third sets the width, and the +* fourth sets the height. The way these parameters are interpreted, however, +* may be changed with the rectMode() function. +* <br><br> +* The fifth, sixth, seventh and eighth parameters, if specified, +* determine corner radius for the top-right, top-left, lower-right and +* lower-left corners, respectively. An omitted corner radius parameter is set +* to the value of the previously specified radius value in the parameter list. +* +* @method rect +* @param {Number} x x-coordinate of the rectangle. +* @param {Number} y y-coordinate of the rectangle. +* @param {Number} w width of the rectangle. +* @param {Number} h height of the rectangle. +* @param {Number} [tl] optional radius of top-left corner. +* @param {Number} [tr] optional radius of top-right corner. +* @param {Number} [br] optional radius of bottom-right corner. +* @param {Number} [bl] optional radius of bottom-left corner. +* @return {p5} the p5 object. +* @example +* <div> +* <code> +* // Draw a rectangle at location (30, 20) with a width and height of 55. +* rect(30, 20, 55, 55); +* </code> +* </div> +* +* <div> +* <code> +* // Draw a rectangle with rounded corners, each having a radius of 20. +* rect(30, 20, 55, 55, 20); +* </code> +* </div> +* +* <div> +* <code> +* // Draw a rectangle with rounded corners having the following radii: +* // top-left = 20, top-right = 15, bottom-right = 10, bottom-left = 5. +* rect(30, 20, 55, 55, 20, 15, 10, 5) +* </code> +* </div> +*/ +p5.prototype.rect = function (x, y, w, h, tl, tr, br, bl) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'rect', + args, + [ + ['Number', 'Number', 'Number', 'Number'], + ['Number', 'Number', 'Number', 'Number', 'Number'], + ['Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', 'Number'] + ] + ); + + if (!this._renderer._doStroke && !this._renderer._doFill) { + return; + } + this._renderer.rect(x, y, w, h, tl, tr, br, bl); + return this; +}; + +/** +* A triangle is a plane created by connecting three points. The first two +* arguments specify the first point, the middle two arguments specify the +* second point, and the last two arguments specify the third point. +* +* @method triangle +* @param {Number} x1 x-coordinate of the first point +* @param {Number} y1 y-coordinate of the first point +* @param {Number} x2 x-coordinate of the second point +* @param {Number} y2 y-coordinate of the second point +* @param {Number} x3 x-coordinate of the third point +* @param {Number} y3 y-coordinate of the third point +* @return {p5} the p5 object +* @example +* <div> +* <code> +* triangle(30, 75, 58, 20, 86, 75); +* </code> +* </div> +*/ +p5.prototype.triangle = function() { + + if (!this._renderer._doStroke && !this._renderer._doFill) { + return this; + } + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + if(this._renderer.isP3D){ + this._validateParameters( + 'triangle', + args, + [ + ['Number', 'Number', 'Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number'] + ] + ); + this._renderer.triangle( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5], + args[6], + args[7], + args[8] + ); + } else { + this._validateParameters( + 'triangle', + args, + [ + ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'] + ] + ); + this._renderer.triangle( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5] + ); + } + return this; +}; + +module.exports = p5; + +},{"./constants":47,"./core":48,"./error_helpers":51}],45:[function(_dereq_,module,exports){ +/** + * @module Shape + * @submodule Attributes + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); + +/** + * Modifies the location from which ellipses are drawn by changing the way + * in which parameters given to ellipse() are interpreted. + * <br><br> + * The default mode is ellipseMode(CENTER), which interprets the first two + * parameters of ellipse() as the shape's center point, while the third and + * fourth parameters are its width and height. + * <br><br> + * ellipseMode(RADIUS) also uses the first two parameters of ellipse() as + * the shape's center point, but uses the third and fourth parameters to + * specify half of the shapes's width and height. + * <br><br> + * ellipseMode(CORNER) interprets the first two parameters of ellipse() as + * the upper-left corner of the shape, while the third and fourth parameters + * are its width and height. + * <br><br> + * ellipseMode(CORNERS) interprets the first two parameters of ellipse() as + * the location of one corner of the ellipse's bounding box, and the third + * and fourth parameters as the location of the opposite corner. + * <br><br> + * The parameter must be written in ALL CAPS because Javascript is a + * case-sensitive language. + * + * @method ellipseMode + * @param {Number/Constant} mode either CENTER, RADIUS, CORNER, or CORNERS + * @return {p5} the p5 object + * @example + * <div> + * <code> + * ellipseMode(RADIUS); // Set ellipseMode to RADIUS + * fill(255); // Set fill to white + * ellipse(50, 50, 30, 30); // Draw white ellipse using RADIUS mode + * + * ellipseMode(CENTER); // Set ellipseMode to CENTER + * fill(100); // Set fill to gray + * ellipse(50, 50, 30, 30); // Draw gray ellipse using CENTER mode + * </code> + * </div> + * + * <div> + * <code> + * ellipseMode(CORNER); // Set ellipseMode is CORNER + * fill(255); // Set fill to white + * ellipse(25, 25, 50, 50); // Draw white ellipse using CORNER mode + * + * ellipseMode(CORNERS); // Set ellipseMode to CORNERS + * fill(100); // Set fill to gray + * ellipse(25, 25, 50, 50); // Draw gray ellipse using CORNERS mode + * </code> + * </div> + */ +p5.prototype.ellipseMode = function(m) { + if (m === constants.CORNER || + m === constants.CORNERS || + m === constants.RADIUS || + m === constants.CENTER) { + this._renderer._ellipseMode = m; + } + return this; +}; + +/** + * Draws all geometry with jagged (aliased) edges. Note that smooth() is + * active by default, so it is necessary to call noSmooth() to disable + * smoothing of geometry, images, and fonts. + * + * @method noSmooth + * @return {p5} the p5 object + * @example + * <div> + * <code> + * background(0); + * noStroke(); + * smooth(); + * ellipse(30, 48, 36, 36); + * noSmooth(); + * ellipse(70, 48, 36, 36); + * </code> + * </div> + */ +p5.prototype.noSmooth = function() { + this._renderer.noSmooth(); + return this; +}; + +/** + * Modifies the location from which rectangles are drawn by changing the way + * in which parameters given to rect() are interpreted. + * <br><br> + * The default mode is rectMode(CORNER), which interprets the first two + * parameters of rect() as the upper-left corner of the shape, while the + * third and fourth parameters are its width and height. + * <br><br> + * rectMode(CORNERS) interprets the first two parameters of rect() as the + * location of one corner, and the third and fourth parameters as the + * location of the opposite corner. + * <br><br> + * rectMode(CENTER) interprets the first two parameters of rect() as the + * shape's center point, while the third and fourth parameters are its + * width and height. + * <br><br> + * rectMode(RADIUS) also uses the first two parameters of rect() as the + * shape's center point, but uses the third and fourth parameters to specify + * half of the shapes's width and height. + * <br><br> + * The parameter must be written in ALL CAPS because Javascript is a + * case-sensitive language. + * + * @method rectMode + * @param {Number/Constant} mode either CORNER, CORNERS, CENTER, or RADIUS + * @return {p5} the p5 object + * @example + * <div> + * <code> + * rectMode(CORNER); // Default rectMode is CORNER + * fill(255); // Set fill to white + * rect(25, 25, 50, 50); // Draw white rect using CORNER mode + * + * rectMode(CORNERS); // Set rectMode to CORNERS + * fill(100); // Set fill to gray + * rect(25, 25, 50, 50); // Draw gray rect using CORNERS mode + * </code> + * </div> + * + * <div> + * <code> + * rectMode(RADIUS); // Set rectMode to RADIUS + * fill(255); // Set fill to white + * rect(50, 50, 30, 30); // Draw white rect using RADIUS mode + * + * rectMode(CENTER); // Set rectMode to CENTER + * fill(100); // Set fill to gray + * rect(50, 50, 30, 30); // Draw gray rect using CENTER mode + * </code> + * </div> + */ +p5.prototype.rectMode = function(m) { + if (m === constants.CORNER || + m === constants.CORNERS || + m === constants.RADIUS || + m === constants.CENTER) { + this._renderer._rectMode = m; + } + return this; +}; + +/** + * Draws all geometry with smooth (anti-aliased) edges. smooth() will also + * improve image quality of resized images. Note that smooth() is active by + * default; noSmooth() can be used to disable smoothing of geometry, + * images, and fonts. + * + * @method smooth + * @return {p5} the p5 object + * @example + * <div> + * <code> + * background(0); + * noStroke(); + * smooth(); + * ellipse(30, 48, 36, 36); + * noSmooth(); + * ellipse(70, 48, 36, 36); + * </code> + * </div> + */ +p5.prototype.smooth = function() { + this._renderer.smooth(); + return this; +}; + +/** + * Sets the style for rendering line endings. These ends are either squared, + * extended, or rounded, each of which specified with the corresponding + * parameters: SQUARE, PROJECT, and ROUND. The default cap is ROUND. + * + * @method strokeCap + * @param {Number/Constant} cap either SQUARE, PROJECT, or ROUND + * @return {p5} the p5 object + * @example + * <div> + * <code> + * strokeWeight(12.0); + * strokeCap(ROUND); + * line(20, 30, 80, 30); + * strokeCap(SQUARE); + * line(20, 50, 80, 50); + * strokeCap(PROJECT); + * line(20, 70, 80, 70); + * </code> + * </div> + */ +p5.prototype.strokeCap = function(cap) { + if (cap === constants.ROUND || + cap === constants.SQUARE || + cap === constants.PROJECT) { + this._renderer.strokeCap(cap); + } + return this; +}; + +/** + * Sets the style of the joints which connect line segments. These joints + * are either mitered, beveled, or rounded and specified with the + * corresponding parameters MITER, BEVEL, and ROUND. The default joint is + * MITER. + * + * @method strokeJoin + * @param {Number/Constant} join either MITER, BEVEL, ROUND + * @return {p5} the p5 object + * @example + * <div> + * <code> + * noFill(); + * strokeWeight(10.0); + * strokeJoin(MITER); + * beginShape(); + * vertex(35, 20); + * vertex(65, 50); + * vertex(35, 80); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * strokeWeight(10.0); + * strokeJoin(BEVEL); + * beginShape(); + * vertex(35, 20); + * vertex(65, 50); + * vertex(35, 80); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * strokeWeight(10.0); + * strokeJoin(ROUND); + * beginShape(); + * vertex(35, 20); + * vertex(65, 50); + * vertex(35, 80); + * endShape(); + * </code> + * </div> + */ +p5.prototype.strokeJoin = function(join) { + if (join === constants.ROUND || + join === constants.BEVEL || + join === constants.MITER) { + this._renderer.strokeJoin(join); + } + return this; +}; + +/** + * Sets the width of the stroke used for lines, points, and the border + * around shapes. All widths are set in units of pixels. + * + * @method strokeWeight + * @param {Number} weight the weight (in pixels) of the stroke + * @return {p5} the p5 object + * @example + * <div> + * <code> + * strokeWeight(1); // Default + * line(20, 20, 80, 20); + * strokeWeight(4); // Thicker + * line(20, 40, 80, 40); + * strokeWeight(10); // Beastly + * line(20, 70, 80, 70); + * </code> + * </div> + */ +p5.prototype.strokeWeight = function(w) { + this._renderer.strokeWeight(w); + return this; +}; + +module.exports = p5; + +},{"./constants":47,"./core":48}],46:[function(_dereq_,module,exports){ +/** + * @requires constants + */ + +var constants = _dereq_('./constants'); + +module.exports = { + + modeAdjust: function(a, b, c, d, mode) { + if (mode === constants.CORNER) { + return { x: a, y: b, w: c, h: d }; + } else if (mode === constants.CORNERS) { + return { x: a, y: b, w: c-a, h: d-b }; + } else if (mode === constants.RADIUS) { + return { x: a-c, y: b-d, w: 2*c, h: 2*d }; + } else if (mode === constants.CENTER) { + return { x: a-c*0.5, y: b-d*0.5, w: c, h: d }; + } + }, + + arcModeAdjust: function(a, b, c, d, mode) { + if (mode === constants.CORNER) { + return { x: a+c*0.5, y: b+d*0.5, w: c, h: d }; + } else if (mode === constants.CORNERS) { + return { x: a, y: b, w: c+a, h: d+b }; + } else if (mode === constants.RADIUS) { + return { x: a, y: b, w: 2*c, h: 2*d }; + } else if (mode === constants.CENTER) { + return { x: a, y: b, w: c, h: d }; + } + } + +}; + + +},{"./constants":47}],47:[function(_dereq_,module,exports){ +/** + * @module Constants + * @submodule Constants + * @for p5 + */ + +var PI = Math.PI; + +module.exports = { + + // GRAPHICS RENDERER + P2D: 'p2d', + WEBGL: 'webgl', + + // ENVIRONMENT + ARROW: 'default', + CROSS: 'crosshair', + HAND: 'pointer', + MOVE: 'move', + TEXT: 'text', + WAIT: 'wait', + + // TRIGONOMETRY + + /** + * HALF_PI is a mathematical constant with the value + * 1.57079632679489661923. It is half the ratio of the + * circumference of a circle to its diameter. It is useful in + * combination with the trigonometric functions sin() and cos(). + * + * @property HALF_PI + * + * @example + * <div><code> + * arc(50, 50, 80, 80, 0, HALF_PI); + * </code></div> + * + */ + HALF_PI: PI / 2, + /** + * PI is a mathematical constant with the value + * 3.14159265358979323846. It is the ratio of the circumference + * of a circle to its diameter. It is useful in combination with + * the trigonometric functions sin() and cos(). + * + * @property PI + * + * @example + * <div><code> + * arc(50, 50, 80, 80, 0, PI); + * </code></div> + */ + PI: PI, + /** + * QUARTER_PI is a mathematical constant with the value 0.7853982. + * It is one quarter the ratio of the circumference of a circle to + * its diameter. It is useful in combination with the trigonometric + * functions sin() and cos(). + * + * @property QUARTER_PI + * + * @example + * <div><code> + * arc(50, 50, 80, 80, 0, QUARTER_PI); + * </code></div> + * + */ + QUARTER_PI: PI / 4, + /** + * TAU is an alias for TWO_PI, a mathematical constant with the + * value 6.28318530717958647693. It is twice the ratio of the + * circumference of a circle to its diameter. It is useful in + * combination with the trigonometric functions sin() and cos(). + * + * @property TAU + * + * @example + * <div><code> + * arc(50, 50, 80, 80, 0, TAU); + * </code></div> + * + */ + TAU: PI * 2, + /** + * TWO_PI is a mathematical constant with the value + * 6.28318530717958647693. It is twice the ratio of the + * circumference of a circle to its diameter. It is useful in + * combination with the trigonometric functions sin() and cos(). + * + * @property TWO_PI + * + * @example + * <div><code> + * arc(50, 50, 80, 80, 0, TWO_PI); + * </code></div> + * + */ + TWO_PI: PI * 2, + DEGREES: 'degrees', + RADIANS: 'radians', + + // SHAPE + CORNER: 'corner', + CORNERS: 'corners', + RADIUS: 'radius', + RIGHT: 'right', + LEFT: 'left', + CENTER: 'center', + TOP: 'top', + BOTTOM: 'bottom', + BASELINE: 'alphabetic', + POINTS: 'points', + LINES: 'lines', + TRIANGLES: 'triangles', + TRIANGLE_FAN: 'triangles_fan', + TRIANGLE_STRIP: 'triangles_strip', + QUADS: 'quads', + QUAD_STRIP: 'quad_strip', + CLOSE: 'close', + OPEN: 'open', + CHORD: 'chord', + PIE: 'pie', + PROJECT: 'square', // PEND: careful this is counterintuitive + SQUARE: 'butt', + ROUND: 'round', + BEVEL: 'bevel', + MITER: 'miter', + + // COLOR + RGB: 'rgb', + HSB: 'hsb', + HSL: 'hsl', + + // DOM EXTENSION + AUTO: 'auto', + + // INPUT + ALT: 18, + BACKSPACE: 8, + CONTROL: 17, + DELETE: 46, + DOWN_ARROW: 40, + ENTER: 13, + ESCAPE: 27, + LEFT_ARROW: 37, + OPTION: 18, + RETURN: 13, + RIGHT_ARROW: 39, + SHIFT: 16, + TAB: 9, + UP_ARROW: 38, + + // RENDERING + BLEND: 'normal', + ADD: 'lighter', + //ADD: 'add', // + //SUBTRACT: 'subtract', // + DARKEST: 'darken', + LIGHTEST: 'lighten', + DIFFERENCE: 'difference', + EXCLUSION: 'exclusion', + MULTIPLY: 'multiply', + SCREEN: 'screen', + REPLACE: 'source-over', + OVERLAY: 'overlay', + HARD_LIGHT: 'hard-light', + SOFT_LIGHT: 'soft-light', + DODGE: 'color-dodge', + BURN: 'color-burn', + + // FILTERS + THRESHOLD: 'threshold', + GRAY: 'gray', + OPAQUE: 'opaque', + INVERT: 'invert', + POSTERIZE: 'posterize', + DILATE: 'dilate', + ERODE: 'erode', + BLUR: 'blur', + + // TYPOGRAPHY + NORMAL: 'normal', + ITALIC: 'italic', + BOLD: 'bold', + + // TYPOGRAPHY-INTERNAL + _DEFAULT_TEXT_FILL: '#000000', + _DEFAULT_LEADMULT: 1.25, + _CTX_MIDDLE: 'middle', + + // VERTICES + LINEAR: 'linear', + QUADRATIC: 'quadratic', + BEZIER: 'bezier', + CURVE: 'curve', + + // DEFAULTS + _DEFAULT_STROKE: '#000000', + _DEFAULT_FILL: '#FFFFFF' + +}; + +},{}],48:[function(_dereq_,module,exports){ +/** + * @module Structure + * @submodule Structure + * @for p5 + * @requires constants + */ + +'use strict'; + +_dereq_('./shim'); + +// Core needs the PVariables object +var constants = _dereq_('./constants'); + +/** + * This is the p5 instance constructor. + * + * A p5 instance holds all the properties and methods related to + * a p5 sketch. It expects an incoming sketch closure and it can also + * take an optional node parameter for attaching the generated p5 canvas + * to a node. The sketch closure takes the newly created p5 instance as + * its sole argument and may optionally set preload(), setup(), and/or + * draw() properties on it for running a sketch. + * + * A p5 sketch can run in "global" or "instance" mode: + * "global" - all properties and methods are attached to the window + * "instance" - all properties and methods are bound to this p5 object + * + * @param {Function} sketch a closure that can set optional preload(), + * setup(), and/or draw() properties on the + * given p5 instance + * @param {HTMLElement|boolean} node element to attach canvas to, if a + * boolean is passed in use it as sync + * @param {boolean} [sync] start synchronously (optional) + * @return {p5} a p5 instance + */ +var p5 = function(sketch, node, sync) { + + if (arguments.length === 2 && typeof node === 'boolean') { + sync = node; + node = undefined; + } + + ////////////////////////////////////////////// + // PUBLIC p5 PROPERTIES AND METHODS + ////////////////////////////////////////////// + + + /** + * Called directly before setup(), the preload() function is used to handle + * asynchronous loading of external files. If a preload function is + * defined, setup() will wait until any load calls within have finished. + * Nothing besides load calls should be inside preload (loadImage, + * loadJSON, loadFont, loadStrings, etc). + * + * @method preload + * @example + * <div><code> + * var img; + * var c; + * function preload() { // preload() runs once + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { // setup() waits until preload() is done + * img.loadPixels(); + * // get color of middle pixel + * c = img.get(img.width/2, img.height/2); + * } + * + * function draw() { + * background(c); + * image(img, 25, 25, 50, 50); + * } + * </code></div> + */ + + /** + * The setup() function is called once when the program starts. It's used to + * define initial environment properties such as screen size and background + * color and to load media such as images and fonts as the program starts. + * There can only be one setup() function for each program and it shouldn't + * be called again after its initial execution. + * <br><br> + * Note: Variables declared within setup() are not accessible within other + * functions, including draw(). + * + * @method setup + * @example + * <div><code> + * var a = 0; + * + * function setup() { + * background(0); + * noStroke(); + * fill(102); + * } + * + * function draw() { + * rect(a++%width, 10, 2, 80); + * } + * </code></div> + */ + + /** + * Called directly after setup(), the draw() function continuously executes + * the lines of code contained inside its block until the program is stopped + * or noLoop() is called. draw() is called automatically and should never be + * called explicitly. + * <br><br> + * It should always be controlled with noLoop(), redraw() and loop(). After + * noLoop() stops the code in draw() from executing, redraw() causes the + * code inside draw() to execute once, and loop() will cause the code + * inside draw() to resume executing continuously. + * <br><br> + * The number of times draw() executes in each second may be controlled with + * the frameRate() function. + * <br><br> + * There can only be one draw() function for each sketch, and draw() must + * exist if you want the code to run continuously, or to process events such + * as mousePressed(). Sometimes, you might have an empty call to draw() in + * your program, as shown in the above example. + * + * @method draw + * @example + * <div><code> + * var yPos = 0; + * function setup() { // setup() runs once + * frameRate(30); + * } + * function draw() { // draw() loops forever, until stopped + * background(204); + * yPos = yPos - 1; + * if (yPos < 0) { + * yPos = height; + * } + * line(0, yPos, width, yPos); + * } + * </code></div> + */ + + + ////////////////////////////////////////////// + // PRIVATE p5 PROPERTIES AND METHODS + ////////////////////////////////////////////// + + this._setupDone = false; + // for handling hidpi + this._pixelDensity = Math.ceil(window.devicePixelRatio) || 1; + this._userNode = node; + this._curElement = null; + this._elements = []; + this._requestAnimId = 0; + this._preloadCount = 0; + this._isGlobal = false; + this._loop = true; + this._styles = []; + this._defaultCanvasSize = { + width: 100, + height: 100 + }; + this._events = { // keep track of user-events for unregistering later + 'mousemove': null, + 'mousedown': null, + 'mouseup': null, + 'dragend': null, + 'dragover': null, + 'click': null, + 'mouseover': null, + 'mouseout': null, + 'keydown': null, + 'keyup': null, + 'keypress': null, + 'touchstart': null, + 'touchmove': null, + 'touchend': null, + 'resize': null, + 'blur': null + }; + + if (window.DeviceOrientationEvent) { + this._events.deviceorientation = null; + } + if (window.DeviceMotionEvent && !window._isNodeWebkit) { + this._events.devicemotion = null; + } + + this._events.wheel = null; + + + this._loadingScreenId = 'p5_loading'; + + this._start = function () { + // Find node if id given + if (this._userNode) { + if (typeof this._userNode === 'string') { + this._userNode = document.getElementById(this._userNode); + } + } + + // Always create a default canvas. + // Later on if the user calls createCanvas, this default one + // will be replaced + this.createCanvas( + this._defaultCanvasSize.width, + this._defaultCanvasSize.height, + 'p2d', + true + ); + + var userPreload = this.preload || window.preload; // look for "preload" + if (userPreload) { + + // Setup loading screen + // Set loading scfeen into dom if not present + // Otherwise displays and removes user provided loading screen + var loadingScreen = document.getElementById(this._loadingScreenId); + if(!loadingScreen){ + loadingScreen = document.createElement('div'); + loadingScreen.innerHTML = 'Loading...'; + loadingScreen.style.position = 'absolute'; + loadingScreen.id = this._loadingScreenId; + var node = this._userNode || document.body; + node.appendChild(loadingScreen); + } + // var methods = this._preloadMethods; + for (var method in this._preloadMethods){ + // default to p5 if no object defined + this._preloadMethods[method] = this._preloadMethods[method] || p5; + var obj = this._preloadMethods[method]; + //it's p5, check if it's global or instance + if (obj === p5.prototype || obj === p5){ + obj = this._isGlobal ? window : this; + } + this._registeredPreloadMethods[method] = obj[method]; + obj[method] = this._wrapPreload(obj, method); + } + + userPreload(); + this._runIfPreloadsAreDone(); + } else { + this._setup(); + this._runFrames(); + this._draw(); + } + }.bind(this); + + this._runIfPreloadsAreDone = function(){ + var context = this._isGlobal ? window : this; + if (context._preloadCount === 0) { + var loadingScreen = document.getElementById(context._loadingScreenId); + if (loadingScreen) { + loadingScreen.parentNode.removeChild(loadingScreen); + } + context._setup(); + context._runFrames(); + context._draw(); + } + }; + + this._decrementPreload = function(){ + var context = this._isGlobal ? window : this; + context._setProperty('_preloadCount', context._preloadCount - 1); + context._runIfPreloadsAreDone(); + }; + + this._wrapPreload = function(obj, fnName){ + return function(){ + //increment counter + this._incrementPreload(); + //call original function + var args = Array.prototype.slice.call(arguments); + args.push(this._decrementPreload.bind(this)); + return this._registeredPreloadMethods[fnName].apply(obj, args); + }.bind(this); + }; + + this._incrementPreload = function(){ + var context = this._isGlobal ? window : this; + context._setProperty('_preloadCount', context._preloadCount + 1); + }; + + this._setup = function() { + + // return preload functions to their normal vals if switched by preload + var context = this._isGlobal ? window : this; + if (typeof context.preload === 'function') { + for (var f in this._preloadMethods) { + context[f] = this._preloadMethods[f][f]; + if (context[f] && this) { + context[f] = context[f].bind(this); + } + } + } + + // Short-circuit on this, in case someone used the library in "global" + // mode earlier + if (typeof context.setup === 'function') { + context.setup(); + } + + // // unhide hidden canvas that was created + // this.canvas.style.visibility = ''; + // this.canvas.className = this.canvas.className.replace('p5_hidden', ''); + + // unhide any hidden canvases that were created + var reg = new RegExp(/(^|\s)p5_hidden(?!\S)/g); + var canvases = document.getElementsByClassName('p5_hidden'); + for (var i = 0; i < canvases.length; i++) { + var k = canvases[i]; + k.style.visibility = ''; + k.className = k.className.replace(reg, ''); + } + this._setupDone = true; + + }.bind(this); + + this._draw = function () { + var now = window.performance.now(); + var time_since_last = now - this._lastFrameTime; + var target_time_between_frames = 1000 / this._targetFrameRate; + + // only draw if we really need to; don't overextend the browser. + // draw if we're within 5ms of when our next frame should paint + // (this will prevent us from giving up opportunities to draw + // again when it's really about time for us to do so). fixes an + // issue where the frameRate is too low if our refresh loop isn't + // in sync with the browser. note that we have to draw once even + // if looping is off, so we bypass the time delay if that + // is the case. + var epsilon = 5; + if (!this._loop || + time_since_last >= target_time_between_frames - epsilon) { + + //mandatory update values(matrixs and stack) for 3d + if(this._renderer.isP3D){ + this._renderer._update(); + } + + this._setProperty('frameCount', this.frameCount + 1); + this._updateMouseCoords(); + this._updateTouchCoords(); + this.redraw(); + this._frameRate = 1000.0/(now - this._lastFrameTime); + this._lastFrameTime = now; + } + + // get notified the next time the browser gives us + // an opportunity to draw. + if (this._loop) { + this._requestAnimId = window.requestAnimationFrame(this._draw); + } + }.bind(this); + + this._runFrames = function() { + if (this._updateInterval) { + clearInterval(this._updateInterval); + } + }.bind(this); + + this._setProperty = function(prop, value) { + this[prop] = value; + if (this._isGlobal) { + window[prop] = value; + } + }.bind(this); + + /** + * Removes the entire p5 sketch. This will remove the canvas and any + * elements created by p5.js. It will also stop the draw loop and unbind + * any properties or methods from the window global scope. It will + * leave a variable p5 in case you wanted to create a new p5 sketch. + * If you like, you can set p5 = null to erase it. + * @method remove + * @example + * <div class='norender'><code> + * function draw() { + * ellipse(50, 50, 10, 10); + * } + * + * function mousePressed() { + * remove(); // remove whole sketch on mouse press + * } + * </code></div> + */ + this.remove = function() { + if (this._curElement) { + + // stop draw + this._loop = false; + if (this._requestAnimId) { + window.cancelAnimationFrame(this._requestAnimId); + } + + // unregister events sketch-wide + for (var ev in this._events) { + window.removeEventListener(ev, this._events[ev]); + } + + // remove DOM elements created by p5, and listeners + for (var i=0; i<this._elements.length; i++) { + var e = this._elements[i]; + if (e.elt.parentNode) { + e.elt.parentNode.removeChild(e.elt); + } + for (var elt_ev in e._events) { + e.elt.removeEventListener(elt_ev, e._events[elt_ev]); + } + } + + // call any registered remove functions + var self = this; + this._registeredMethods.remove.forEach(function (f) { + if (typeof(f) !== 'undefined') { + f.call(self); + } + }); + + // remove window bound properties and methods + if (this._isGlobal) { + for (var p in p5.prototype) { + try { + delete window[p]; + } catch (x) { + window[p] = undefined; + } + } + for (var p2 in this) { + if (this.hasOwnProperty(p2)) { + try { + delete window[p2]; + } catch (x) { + window[p2] = undefined; + } + } + } + } + } + // window.p5 = undefined; + }.bind(this); + + + // attach constants to p5 instance + for (var k in constants) { + p5.prototype[k] = constants[k]; + } + + // If the user has created a global setup or draw function, + // assume "global" mode and make everything global (i.e. on the window) + if (!sketch) { + this._isGlobal = true; + // Loop through methods on the prototype and attach them to the window + for (var p in p5.prototype) { + if(typeof p5.prototype[p] === 'function') { + var ev = p.substring(2); + if (!this._events.hasOwnProperty(ev)) { + window[p] = p5.prototype[p].bind(this); + } + } else { + window[p] = p5.prototype[p]; + } + } + // Attach its properties to the window + for (var p2 in this) { + if (this.hasOwnProperty(p2)) { + window[p2] = this[p2]; + } + } + + } else { + // Else, the user has passed in a sketch closure that may set + // user-provided 'setup', 'draw', etc. properties on this instance of p5 + sketch(this); + } + + // Bind events to window (not using container div bc key events don't work) + + for (var e in this._events) { + var f = this['_on'+e]; + if (f) { + var m = f.bind(this); + window.addEventListener(e, m); + this._events[e] = m; + } + } + + var focusHandler = function() { + this._setProperty('focused', true); + }.bind(this); + var blurHandler = function() { + this._setProperty('focused', false); + }.bind(this); + window.addEventListener('focus', focusHandler); + window.addEventListener('blur', blurHandler); + this.registerMethod('remove', function() { + window.removeEventListener('focus', focusHandler); + window.removeEventListener('blur', blurHandler); + }); + + // TODO: ??? + + if (sync) { + this._start(); + } else { + if (document.readyState === 'complete') { + this._start(); + } else { + window.addEventListener('load', this._start.bind(this), false); + } + } +}; + + +// functions that cause preload to wait +// more can be added by using registerPreloadMethod(func) +p5.prototype._preloadMethods = { + loadJSON: p5.prototype, + loadImage: p5.prototype, + loadStrings: p5.prototype, + loadXML: p5.prototype, + loadShape: p5.prototype, + loadTable: p5.prototype, + loadFont: p5.prototype +}; + +p5.prototype._registeredMethods = { pre: [], post: [], remove: [] }; + +p5.prototype._registeredPreloadMethods = {}; + +p5.prototype.registerPreloadMethod = function(fnString, obj) { + // obj = obj || p5.prototype; + if (!p5.prototype._preloadMethods.hasOwnProperty(fnString)) { + p5.prototype._preloadMethods[fnString] = obj; + } +}; + +p5.prototype.registerMethod = function(name, m) { + if (!p5.prototype._registeredMethods.hasOwnProperty(name)) { + p5.prototype._registeredMethods[name] = []; + } + p5.prototype._registeredMethods[name].push(m); +}; + +module.exports = p5; + +},{"./constants":47,"./shim":57}],49:[function(_dereq_,module,exports){ +/** + * @module Shape + * @submodule Curves + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('./core'); + +_dereq_('./error_helpers'); + +var bezierDetail = 20; +var curveDetail = 20; + +/** + * Draws a cubic Bezier curve on the screen. These curves are defined by a + * series of anchor and control points. The first two parameters specify + * the first anchor point and the last two parameters specify the other + * anchor point, which become the first and last points on the curve. The + * middle parameters specify the two control points which define the shape + * of the curve. Approximately speaking, control points "pull" the curve + * towards them.<br /><br />Bezier curves were developed by French + * automotive engineer Pierre Bezier, and are commonly used in computer + * graphics to define gently sloping curves. See also curve(). + * + * @method bezier + * @param {Number} x1 x-coordinate for the first anchor point + * @param {Number} y1 y-coordinate for the first anchor point + * @param {Number} x2 x-coordinate for the first control point + * @param {Number} y2 y-coordinate for the first control point + * @param {Number} x3 x-coordinate for the second control point + * @param {Number} y3 y-coordinate for the second control point + * @param {Number} x4 x-coordinate for the second anchor point + * @param {Number} y4 y-coordinate for the second anchor point + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * stroke(255, 102, 0); + * line(85, 20, 10, 10); + * line(90, 90, 15, 80); + * stroke(0, 0, 0); + * bezier(85, 20, 10, 10, 90, 90, 15, 80); + * </code> + * </div> + */ +p5.prototype.bezier = function(x1, y1, x2, y2, x3, y3, x4, y4) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'bezier', + args, + [ 'Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', 'Number' ] + ); + + if (!this._renderer._doStroke) { + return this; + } + this._renderer.bezier(x1, y1, x2, y2, x3, y3, x4, y4); + return this; +}; + +/** + * Sets the resolution at which Beziers display. + * + * The default value is 20. + * + * @param {Number} detail resolution of the curves + * @return {Object} the p5 object + * @example + * <div> + * <code> + * background(204); + * bezierDetail(50); + * bezier(85, 20, 10, 10, 90, 90, 15, 80); + * </code> + * </div> + */ +p5.prototype.bezierDetail = function(d) { + bezierDetail = d; + return this; +}; + +/** + * Evaluates the Bezier at position t for points a, b, c, d. + * The parameters a and d are the first and last points + * on the curve, and b and c are the control points. + * The final parameter t varies between 0 and 1. + * This can be done once with the x coordinates and a second time + * with the y coordinates to get the location of a bezier curve at t. + * + * @method bezierPoint + * @param {Number} a coordinate of first point on the curve + * @param {Number} b coordinate of first control point + * @param {Number} c coordinate of second control point + * @param {Number} d coordinate of second point on the curve + * @param {Number} t value between 0 and 1 + * @return {Number} the value of the Bezier at position t + * @example + * <div> + * <code> + * noFill(); + * x1 = 85, x2 = 10, x3 = 90, x4 = 15; + * y1 = 20, y2 = 10, y3 = 90, y4 = 80; + * bezier(x1, y1, x2, y2, x3, y3, x4, y4); + * fill(255); + * steps = 10; + * for (i = 0; i <= steps; i++) { + * t = i / steps; + * x = bezierPoint(x1, x2, x3, x4, t); + * y = bezierPoint(y1, y2, y3, y4, t); + * ellipse(x, y, 5, 5); + * } + * </code> + * </div> + */ +p5.prototype.bezierPoint = function(a, b, c, d, t) { + var adjustedT = 1-t; + return Math.pow(adjustedT,3)*a + + 3*(Math.pow(adjustedT,2))*t*b + + 3*adjustedT*Math.pow(t,2)*c + + Math.pow(t,3)*d; +}; + +/** + * Evaluates the tangent to the Bezier at position t for points a, b, c, d. + * The parameters a and d are the first and last points + * on the curve, and b and c are the control points. + * The final parameter t varies between 0 and 1. + * + * @method bezierTangent + * @param {Number} a coordinate of first point on the curve + * @param {Number} b coordinate of first control point + * @param {Number} c coordinate of second control point + * @param {Number} d coordinate of second point on the curve + * @param {Number} t value between 0 and 1 + * @return {Number} the tangent at position t + * @example + * <div> + * <code> + * noFill(); + * bezier(85, 20, 10, 10, 90, 90, 15, 80); + * steps = 6; + * fill(255); + * for (i = 0; i <= steps; i++) { + * t = i / steps; + * // Get the location of the point + * x = bezierPoint(85, 10, 90, 15, t); + * y = bezierPoint(20, 10, 90, 80, t); + * // Get the tangent points + * tx = bezierTangent(85, 10, 90, 15, t); + * ty = bezierTangent(20, 10, 90, 80, t); + * // Calculate an angle from the tangent points + * a = atan2(ty, tx); + * a += PI; + * stroke(255, 102, 0); + * line(x, y, cos(a)*30 + x, sin(a)*30 + y); + * // The following line of code makes a line + * // inverse of the above line + * //line(x, y, cos(a)*-30 + x, sin(a)*-30 + y); + * stroke(0); + * ellipse(x, y, 5, 5); + * } + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * bezier(85, 20, 10, 10, 90, 90, 15, 80); + * stroke(255, 102, 0); + * steps = 16; + * for (i = 0; i <= steps; i++) { + * t = i / steps; + * x = bezierPoint(85, 10, 90, 15, t); + * y = bezierPoint(20, 10, 90, 80, t); + * tx = bezierTangent(85, 10, 90, 15, t); + * ty = bezierTangent(20, 10, 90, 80, t); + * a = atan2(ty, tx); + * a -= HALF_PI; + * line(x, y, cos(a)*8 + x, sin(a)*8 + y); + * } + * </code> + * </div> + */ +p5.prototype.bezierTangent = function(a, b, c, d, t) { + var adjustedT = 1-t; + return 3*d*Math.pow(t,2) - + 3*c*Math.pow(t,2) + + 6*c*adjustedT*t - + 6*b*adjustedT*t + + 3*b*Math.pow(adjustedT,2) - + 3*a*Math.pow(adjustedT,2); +}; + +/** + * Draws a curved line on the screen between two points, given as the + * middle four parameters. The first two parameters are a control point, as + * if the curve came from this point even though it's not drawn. The last + * two parameters similarly describe the other control point. <br /><br /> + * Longer curves can be created by putting a series of curve() functions + * together or using curveVertex(). An additional function called + * curveTightness() provides control for the visual quality of the curve. + * The curve() function is an implementation of Catmull-Rom splines. + * + * @method curve + * @param {Number} x1 x-coordinate for the beginning control point + * @param {Number} y1 y-coordinate for the beginning control point + * @param {Number} x2 x-coordinate for the first point + * @param {Number} y2 y-coordinate for the first point + * @param {Number} x3 x-coordinate for the second point + * @param {Number} y3 y-coordinate for the second point + * @param {Number} x4 x-coordinate for the ending control point + * @param {Number} y4 y-coordinate for the ending control point + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * stroke(255, 102, 0); + * curve(5, 26, 5, 26, 73, 24, 73, 61); + * stroke(0); + * curve(5, 26, 73, 24, 73, 61, 15, 65); + * stroke(255, 102, 0); + * curve(73, 24, 73, 61, 15, 65, 15, 65); + * </code> + * </div> + * <div> + * <code> + * // Define the curve points as JavaScript objects + * p1 = {x: 5, y: 26}, p2 = {x: 73, y: 24} + * p3 = {x: 73, y: 61}, p4 = {x: 15, y: 65} + * noFill(); + * stroke(255, 102, 0); + * curve(p1.x, p1.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y) + * stroke(0); + * curve(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y) + * stroke(255, 102, 0); + * curve(p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, p4.x, p4.y) + * </code> + * </div> + */ +p5.prototype.curve = function(x1, y1, x2, y2, x3, y3, x4, y4) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'curve', + args, + [ 'Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', 'Number' ] + ); + + if (!this._renderer._doStroke) { + return; + } + this._renderer.curve(x1, y1, x2, y2, x3, y3, x4, y4); + return this; +}; + +/** + * Sets the resolution at which curves display. + * + * The default value is 20. + * + * @param {Number} resolution of the curves + * @return {Object} the p5 object + * @example + * <div> + * <code> + * background(204); + * curveDetail(20); + * curve(5, 26, 5, 26, 73, 24, 73, 61); + * </code> + * </div> + */ +p5.prototype.curveDetail = function(d) { + curveDetail = d; + return this; +}; + +/** + * Modifies the quality of forms created with curve() and curveVertex(). + * The parameter tightness determines how the curve fits to the vertex + * points. The value 0.0 is the default value for tightness (this value + * defines the curves to be Catmull-Rom splines) and the value 1.0 connects + * all the points with straight lines. Values within the range -5.0 and 5.0 + * will deform the curves but will leave them recognizable and as values + * increase in magnitude, they will continue to deform. + * + * @method curveTightness + * @param {Number} amount of deformation from the original vertices + * @return {Object} the p5 object + * @example + * <div> + * <code> + * // Move the mouse left and right to see the curve change + * + * function setup() { + * createCanvas(100, 100); + * noFill(); + * } + * + * function draw() { + * background(204); + * var t = map(mouseX, 0, width, -5, 5); + * curveTightness(t); + * beginShape(); + * curveVertex(10, 26); + * curveVertex(10, 26); + * curveVertex(83, 24); + * curveVertex(83, 61); + * curveVertex(25, 65); + * curveVertex(25, 65); + * endShape(); + * } + * </code> + * </div> + */ +p5.prototype.curveTightness = function (t) { + this._renderer._curveTightness = t; +}; + +/** + * Evaluates the curve at position t for points a, b, c, d. + * The parameter t varies between 0 and 1, a and d are points + * on the curve, and b and c are the control points. + * This can be done once with the x coordinates and a second time + * with the y coordinates to get the location of a curve at t. + * + * @method curvePoint + * @param {Number} a coordinate of first point on the curve + * @param {Number} b coordinate of first control point + * @param {Number} c coordinate of second control point + * @param {Number} d coordinate of second point on the curve + * @param {Number} t value between 0 and 1 + * @return {Number} bezier value at position t + * @example + * <div> + * <code> + * noFill(); + * curve(5, 26, 5, 26, 73, 24, 73, 61); + * curve(5, 26, 73, 24, 73, 61, 15, 65); + * fill(255); + * ellipseMode(CENTER); + * steps = 6; + * for (i = 0; i <= steps; i++) { + * t = i / steps; + * x = curvePoint(5, 5, 73, 73, t); + * y = curvePoint(26, 26, 24, 61, t); + * ellipse(x, y, 5, 5); + * x = curvePoint(5, 73, 73, 15, t); + * y = curvePoint(26, 24, 61, 65, t); + * ellipse(x, y, 5, 5); + * } + * </code> + * </div> + */ +p5.prototype.curvePoint = function(a, b, c, d, t) { + var t3 = t*t*t, + t2 = t*t, + f1 = -0.5 * t3 + t2 - 0.5 * t, + f2 = 1.5 * t3 - 2.5 * t2 + 1.0, + f3 = -1.5 * t3 + 2.0 * t2 + 0.5 * t, + f4 = 0.5 * t3 - 0.5 * t2; + return a*f1 + b*f2 + c*f3 + d*f4; +}; + +/** + * Evaluates the tangent to the curve at position t for points a, b, c, d. + * The parameter t varies between 0 and 1, a and d are points on the curve, + * and b and c are the control points. + * + * @method curveTangent + * @param {Number} a coordinate of first point on the curve + * @param {Number} b coordinate of first control point + * @param {Number} c coordinate of second control point + * @param {Number} d coordinate of second point on the curve + * @param {Number} t value between 0 and 1 + * @return {Number} the tangent at position t + * @example + * <div> + * <code> + * noFill(); + * curve(5, 26, 73, 24, 73, 61, 15, 65); + * steps = 6; + * for (i = 0; i <= steps; i++) { + * t = i / steps; + * x = curvePoint(5, 73, 73, 15, t); + * y = curvePoint(26, 24, 61, 65, t); + * //ellipse(x, y, 5, 5); + * tx = curveTangent(5, 73, 73, 15, t); + * ty = curveTangent(26, 24, 61, 65, t); + * a = atan2(ty, tx); + * a -= PI/2.0; + * line(x, y, cos(a)*8 + x, sin(a)*8 + y); + * } + * </code> + * </div> + */ +p5.prototype.curveTangent = function(a, b,c, d, t) { + var t2 = t*t, + f1 = (-3*t2)/2 + 2*t - 0.5, + f2 = (9*t2)/2 - 5*t, + f3 = (-9*t2)/2 + 4*t + 0.5, + f4 = (3*t2)/2 - t; + return a*f1 + b*f2 + c*f3 + d*f4; +}; + +module.exports = p5; + +},{"./core":48,"./error_helpers":51}],50:[function(_dereq_,module,exports){ +/** + * @module Environment + * @submodule Environment + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('./core'); +var C = _dereq_('./constants'); + +var standardCursors = [C.ARROW, C.CROSS, C.HAND, C.MOVE, C.TEXT, C.WAIT]; + +p5.prototype._frameRate = 0; +p5.prototype._lastFrameTime = window.performance.now(); +p5.prototype._targetFrameRate = 60; + + +if (window.console && console.log) { + /** + * The print() function writes to the console area of your browser. + * This function is often helpful for looking at the data a program is + * producing. This function creates a new line of text for each call to + * the function. Individual elements can be + * separated with quotes ("") and joined with the addition operator (+). + * <br><br> + * While print() is similar to console.log(), it does not directly map to + * it in order to simulate easier to understand behavior than + * console.log(). Due to this, it is slower. For fastest results, use + * console.log(). + * + * @method print + * @param {Any} contents any combination of Number, String, Object, Boolean, + * Array to print + * @example + * <div><code class='norender'> + * var x = 10; + * print("The value of x is " + x); + * // prints "The value of x is 10" + * </code></div> + */ + // Converts passed args into a string and then parses that string to + // simulate synchronous behavior. This is a hack and is gross. + // Since this will not work on all objects, particularly circular + // structures, simply console.log() on error. + p5.prototype.print = function(args) { + try { + var newArgs = JSON.parse(JSON.stringify(args)); + console.log(newArgs); + } catch(err) { + console.log(args); + } + }; +} else { + p5.prototype.print = function() {}; +} + +p5.prototype.println = p5.prototype.print; + +/** + * The system variable frameCount contains the number of frames that have + * been displayed since the program started. Inside setup() the value is 0, + * after the first iteration of draw it is 1, etc. + * + * @property frameCount + * @example + * <div><code> + * function setup() { + * frameRate(30); + * textSize(20); + * textSize(30); + * textAlign(CENTER); + * } + * + * function draw() { + * background(200); + * text(frameCount, width/2, height/2); + * } + * </code></div> + */ +p5.prototype.frameCount = 0; + +/** + * Confirms if the window a p5.js program is in is "focused," meaning that + * the sketch will accept mouse or keyboard input. This variable is + * "true" if the window is focused and "false" if not. + * + * @property focused + * @example + * <div><code> + * // To demonstrate, put two windows side by side. + * // Click on the window that the p5 sketch isn't in! + * function draw() { + * if (focused) { // or "if (focused === true)" + * noStroke(); + * fill(0, 200, 0); + * ellipse(25, 25, 50, 50); + * } else { + * stroke(200,0,0); + * line(0, 0, 100, 100); + * line(100, 0, 0, 100); + * } + * } + * + * </code></div> + */ +p5.prototype.focused = (document.hasFocus()); + +/** + * Sets the cursor to a predefined symbol or an image, or makes it visible + * if already hidden. If you are trying to set an image as the cursor, the + * recommended size is 16x16 or 32x32 pixels. It is not possible to load an + * image as the cursor if you are exporting your program for the Web, and not + * all MODES work with all browsers. The values for parameters x and y must + * be less than the dimensions of the image. + * + * @method cursor + * @param {Number/Constant} type either ARROW, CROSS, HAND, MOVE, TEXT, or + * WAIT, or path for image + * @param {Number} [x] the horizontal active spot of the cursor + * @param {Number} [y] the vertical active spot of the cursor + * @example + * <div><code> + * // Move the mouse left and right across the image + * // to see the cursor change from a cross to a hand + * function draw() { + * line(width/2, 0, width/2, height); + * if (mouseX < 50) { + * cursor(CROSS); + * } else { + * cursor(HAND); + * } + * } + * </code></div> + */ +p5.prototype.cursor = function(type, x, y) { + var cursor = 'auto'; + var canvas = this._curElement.elt; + if (standardCursors.indexOf(type) > -1) { + // Standard css cursor + cursor = type; + } else if (typeof type === 'string') { + var coords = ''; + if (x && y && (typeof x === 'number' && typeof y === 'number')) { + // Note that x and y values must be unit-less positive integers < 32 + // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor + coords = x + ' ' + y; + } + if (type.substring(0, 6) !== 'http://') { + // Image (absolute url) + cursor = 'url(' + type + ') ' + coords + ', auto'; + } else if (/\.(cur|jpg|jpeg|gif|png|CUR|JPG|JPEG|GIF|PNG)$/.test(type)) { + // Image file (relative path) - Separated for performance reasons + cursor = 'url(' + type + ') ' + coords + ', auto'; + } else { + // Any valid string for the css cursor property + cursor = type; + } + } + canvas.style.cursor = cursor; +}; + +/** + * Specifies the number of frames to be displayed every second. For example, + * the function call frameRate(30) will attempt to refresh 30 times a second. + * If the processor is not fast enough to maintain the specified rate, the + * frame rate will not be achieved. Setting the frame rate within setup() is + * recommended. The default rate is 60 frames per second. This is the same as + * setFrameRate(val). + * <br><br> + * Calling frameRate() with no arguments returns the current framerate. This + * is the same as getFrameRate(). + * <br><br> + * Calling frameRate() with arguments that are not of the type numbers + * or are non positive also returns current framerate. + * + * @method frameRate + * @param {Number} [fps] number of frames to be displayed every second + * @return {Number} current frameRate + * @example + * + * <div><code> + * var rectX = 0; + * var fr = 30; //starting FPS + * var clr; + * + * function setup() { + * background(200); + * frameRate(fr); // Attempt to refresh at starting FPS + * clr = color(255,0,0); + * } + * + * function draw() { + * background(200); + * rectX = rectX += 1; // Move Rectangle + * + * if (rectX >= width) { // If you go off screen. + * if (fr == 30) { + * clr = color(0,0,255); + * fr = 10; + * frameRate(fr); // make frameRate 10 FPS + * } else { + * clr = color(255,0,0); + * fr = 30; + * frameRate(fr); // make frameRate 30 FPS + * } + * rectX = 0; + * } + * fill(clr); + * rect(rectX, 40, 20,20); + * } + * </div></code> + * + */ +p5.prototype.frameRate = function(fps) { + if (typeof fps !== 'number' || fps <= 0) { + return this._frameRate; + } else { + this._setProperty('_targetFrameRate', fps); + this._runFrames(); + return this; + } +}; +/** + * Returns the current framerate. + * + * @return {Number} current frameRate + */ +p5.prototype.getFrameRate = function() { + return this.frameRate(); +}; + +/** + * Specifies the number of frames to be displayed every second. For example, + * the function call frameRate(30) will attempt to refresh 30 times a second. + * If the processor is not fast enough to maintain the specified rate, the + * frame rate will not be achieved. Setting the frame rate within setup() is + * recommended. The default rate is 60 frames per second. + * + * Calling frameRate() with no arguments returns the current framerate. + * + * @param {Number} [fps] number of frames to be displayed every second + */ +p5.prototype.setFrameRate = function(fps) { + return this.frameRate(fps); +}; + +/** + * Hides the cursor from view. + * + * @method noCursor + * @example + * <div><code> + * function setup() { + * noCursor(); + * } + * + * function draw() { + * background(200); + * ellipse(mouseX, mouseY, 10, 10); + * } + * </code></div> + */ +p5.prototype.noCursor = function() { + this._curElement.elt.style.cursor = 'none'; +}; + + +/** + * System variable that stores the width of the entire screen display. This + * is used to run a full-screen program on any display size. + * + * @property displayWidth + * @example + * <div class="norender"><code> + * createCanvas(displayWidth, displayHeight); + * </code></div> + */ +p5.prototype.displayWidth = screen.width; + +/** + * System variable that stores the height of the entire screen display. This + * is used to run a full-screen program on any display size. + * + * @property displayHeight + * @example + * <div class="norender"><code> + * createCanvas(displayWidth, displayHeight); + * </code></div> + */ +p5.prototype.displayHeight = screen.height; + +/** + * System variable that stores the width of the inner window, it maps to + * window.innerWidth. + * + * @property windowWidth + * @example + * <div class="norender"><code> + * createCanvas(windowWidth, windowHeight); + * </code></div> + */ +p5.prototype.windowWidth = window.innerWidth; +/** + * System variable that stores the height of the inner window, it maps to + * window.innerHeight. + * + * @property windowHeight + * @example + * <div class="norender"><code> + * createCanvas(windowWidth, windowHeight); + * </code></div> + */ +p5.prototype.windowHeight = window.innerHeight; + +/** + * The windowResized() function is called once every time the browser window + * is resized. This is a good place to resize the canvas or do any other + * adjustements to accomodate the new window size. + * + * @method windowResized + * @example + * <div class="norender"><code> + * function setup() { + * createCanvas(windowWidth, windowHeight); + * } + * + * function draw() { + * background(0, 100, 200); + * } + * + * function windowResized() { + * resizeCanvas(windowWidth, windowHeight); + * } + * </code></div> + */ +p5.prototype._onresize = function(e){ + this._setProperty('windowWidth', window.innerWidth); + this._setProperty('windowHeight', window.innerHeight); + var context = this._isGlobal ? window : this; + var executeDefault; + if (typeof context.windowResized === 'function') { + executeDefault = context.windowResized(e); + if (executeDefault !== undefined && !executeDefault) { + e.preventDefault(); + } + } +}; + +/** + * System variable that stores the width of the drawing canvas. This value + * is set by the first parameter of the createCanvas() function. + * For example, the function call createCanvas(320, 240) sets the width + * variable to the value 320. The value of width defaults to 100 if + * createCanvas() is not used in a program. + * + * @property width + */ +p5.prototype.width = 0; + +/** + * System variable that stores the height of the drawing canvas. This value + * is set by the second parameter of the createCanvas() function. For + * example, the function call createCanvas(320, 240) sets the height + * variable to the value 240. The value of height defaults to 100 if + * createCanvas() is not used in a program. + * + * @property height + */ +p5.prototype.height = 0; + +/** + * If argument is given, sets the sketch to fullscreen or not based on the + * value of the argument. If no argument is given, returns the current + * fullscreen state. Note that due to browser restrictions this can only + * be called on user input, for example, on mouse press like the example + * below. + * + * @method fullscreen + * @param {Boolean} [val] whether the sketch should be in fullscreen mode + * or not + * @return {Boolean} current fullscreen state + * @example + * <div> + * <code> + * // Clicking in the box toggles fullscreen on and off. + * function setup() { + * background(200); + * } + * function mousePressed() { + * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { + * var fs = fullscreen(); + * fullscreen(!fs); + * } + * } + * </code> + * </div> + */ +p5.prototype.fullscreen = function(val) { + // no arguments, return fullscreen or not + if (typeof val === 'undefined') { + return document.fullscreenElement || + document.webkitFullscreenElement || + document.mozFullScreenElement || + document.msFullscreenElement; + } else { // otherwise set to fullscreen or not + if (val) { + launchFullscreen(document.documentElement); + } else { + exitFullscreen(); + } + } +}; + +/** + * Sets the pixel scaling for high pixel density displays. By default + * pixel density is set to match display density, call pixelDensity(1) + * to turn this off. Calling pixelDensity() with no arguments returns + * the current pixel density of the sketch. + * + * + * @method pixelDensity + * @param {Number} [val] whether or how much the sketch should scale + * @returns {Number} current pixel density of the sketch + * @example + * <div> + * <code> + * function setup() { + * pixelDensity(1); + * createCanvas(100, 100); + * background(200); + * ellipse(width/2, height/2, 50, 50); + * } + * </code> + * </div> + * <div> + * <code> + * function setup() { + * pixelDensity(3.0); + * createCanvas(100, 100); + * background(200); + * ellipse(width/2, height/2, 50, 50); + * } + * </code> + * </div> + */ +p5.prototype.pixelDensity = function(val) { + if (typeof val === 'number') { + this._pixelDensity = val; + } else { + return this._pixelDensity; + } + this.resizeCanvas(this.width, this.height, true); +}; + +/** + * Returns the pixel density of the current display the sketch is running on. + * + * @method displayDensity + * @returns {Number} current pixel density of the display + * @example + * <div> + * <code> + * function setup() { + * var density = displayDensity(); + * pixelDensity(density); + * createCanvas(100, 100); + * background(200); + * ellipse(width/2, height/2, 50, 50); + * } + * </code> + * </div> + */ +p5.prototype.displayDensity = function() { + return window.devicePixelRatio; +}; + +function launchFullscreen(element) { + var enabled = document.fullscreenEnabled || + document.webkitFullscreenEnabled || + document.mozFullScreenEnabled || + document.msFullscreenEnabled; + if (!enabled) { + throw new Error('Fullscreen not enabled in this browser.'); + } + if(element.requestFullscreen) { + element.requestFullscreen(); + } else if(element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if(element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); + } else if(element.msRequestFullscreen) { + element.msRequestFullscreen(); + } +} + +function exitFullscreen() { + if(document.exitFullscreen) { + document.exitFullscreen(); + } else if(document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if(document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } +} + + +/** + * Gets the current URL. + * @method getURL + * @return {String} url + * @example + * <div> + * <code> + * var url; + * var x = 100; + * + * function setup() { + * fill(0); + * noStroke(); + * url = getURL(); + * } + * + * function draw() { + * background(200); + * text(url, x, height/2); + * x--; + * } + * </code> + * </div> + */ +p5.prototype.getURL = function() { + return location.href; +}; +/** + * Gets the current URL path as an array. + * @method getURLPath + * @return {Array} path components + * @example + * <div class='norender'><code> + * function setup() { + * var urlPath = getURLPath(); + * for (var i=0; i<urlPath.length; i++) { + * text(urlPath[i], 10, i*20+20); + * } + * } + * </code></div> + */ +p5.prototype.getURLPath = function() { + return location.pathname.split('/').filter(function(v){return v!=='';}); +}; +/** + * Gets the current URL params as an Object. + * @method getURLParams + * @return {Object} URL params + * @example + * <div class='norender'> + * <code> + * // Example: http://p5js.org?year=2014&month=May&day=15 + * + * function setup() { + * var params = getURLParams(); + * text(params.day, 10, 20); + * text(params.month, 10, 40); + * text(params.year, 10, 60); + * } + * </code> + * </div> + */ +p5.prototype.getURLParams = function() { + var re = /[?&]([^&=]+)(?:[&=])([^&=]+)/gim; + var m; + var v={}; + while ((m = re.exec(location.search)) != null) { + if (m.index === re.lastIndex) { + re.lastIndex++; + } + v[m[1]]=m[2]; + } + return v; +}; + +module.exports = p5; + +},{"./constants":47,"./core":48}],51:[function(_dereq_,module,exports){ +/** + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('./core'); +var doFriendlyWelcome = false; // TEMP until we get it all working LM + +// -- Borrowed from jQuery 1.11.3 -- +var class2type = {}; +var toString = class2type.toString; +var names = ['Boolean', 'Number', 'String', 'Function', + 'Array', 'Date', 'RegExp', 'Object', 'Error']; +for (var n=0; n<names.length; n++) { + class2type[ '[object ' + names[n] + ']' ] = names[n].toLowerCase(); +} +var getType = function( obj ) { + if ( obj == null ) { + return obj + ''; + } + return typeof obj === 'object' || typeof obj === 'function' ? + class2type[ toString.call(obj) ] || 'object' : + typeof obj; +}; +var isArray = Array.isArray || function( obj ) { + return getType(obj) === 'array'; +}; +var isNumeric =function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals + // subtraction forces infinities to NaN + // adding 1 corrects loss of precision from parseFloat (#15100) + return !isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0; +}; +// -- End borrow -- + +/** + * Checks the definition type against the argument type + * If any of these passes (in order), it matches: + * + * - p5.* definitions are checked with instanceof + * - Booleans are let through (everything is truthy or falsey) + * - Lowercase of the definition is checked against the js type + * - Number types are checked to see if they are numerically castable + */ +var numberTypes = ['Number', 'Integer', 'Number/Constant']; +function typeMatches(defType, argType, arg) { + if(defType.match(/^p5\./)) { + var parts = defType.split('.'); + return arg instanceof p5[parts[1]]; + } + return defType === 'Boolean' || // Anything is truthy, cover in Debug Guide + (defType.toLowerCase() === argType) || + (numberTypes.indexOf(defType) > -1 && isNumeric(arg)); +} + +/** + * Prints out a fancy, colorful message to the console log + * + * @param {String} message the words to be said + * @param {String} func the name of the function to link + * @param {Integer/Color String} color CSS color string or error type + * + * @return console logs + */ +// Wrong number of params, undefined param, wrong type +var PARAM_COUNT = 0; +var EMPTY_VAR = 1; +var WRONG_TYPE = 2; +var FILE_LOAD = 3; +// p5.js blue, p5.js orange, auto dark green; fallback p5.js darkened magenta +// See testColors below for all the color codes and names +var typeColors = ['#2D7BB6', '#EE9900', '#4DB200', '#C83C00']; +function report(message, func, color) { + if(doFriendlyWelcome){ + friendlyWelcome(); + doFriendlyWelcome =false; + } + if ('undefined' === getType(color)) { + color = '#B40033'; // dark magenta + } else if (getType(color) === 'number') { // Type to color + color = typeColors[color]; + } + // LM TEMP commenting this out until we get the whole system working + // if (func.substring(0,4) === 'load'){ + // console.log( + // '%c> p5.js says: '+message+'%c'+ + // '[https://github.com/processing/p5.js/wiki/Local-server]', + // 'background-color:' + color + ';color:#FFF;', + // 'background-color:transparent;color:' + color +';', + // 'background-color:' + color + ';color:#FFF;', + // 'background-color:transparent;color:' + color +';' + // ); + // } + // else{ + // console.log( + // '%c> p5.js says: '+message+'%c [http://p5js.org/reference/#p5/'+func+ + // ']', 'background-color:' + color + ';color:#FFF;', + // 'background-color:transparent;color:' + color +';' + // ); + // } +} + +/** + * Validate all the parameters of a function for number and type + * NOTE THIS FUNCTION IS TEMPORARILY DISABLED UNTIL FURTHER WORK + * AND UPDATES ARE IMPLEMENTED. -LMCCART + * + * @param {String} func name of function we're checking + * @param {Array} args pass of the JS default arguments array + * @param {Array} types List of types accepted ['Number', 'String, ...] OR + * a list of lists for each format: [ + * ['String', 'Number', 'Number'], + * ['String', 'Number', 'Number', 'Number', 'Number' + * ] + * + * @return console logs + */ +p5.prototype._validateParameters = function(func, args, types) { + if (!isArray(types[0])) { + types = [types]; + } + // Check number of parameters + // Example: "You wrote ellipse(X,X,X). ellipse was expecting 4 + // parameters. Try ellipse(X,X,X,X)." + var diff = Math.abs(args.length-types[0].length); + var message, tindex = 0; + for (var i=1, len=types.length; i<len; i++) { + var d = Math.abs(args.length-types[i].length); + if (d <= diff) { + tindex = i; + diff = d; + } + } + var symbol = 'X'; // Parameter placeholder + if(diff > 0) { + message = 'You wrote ' + func + '('; + // Concat an appropriate number of placeholders for call + if (args.length > 0) { + message += symbol + Array(args.length).join(',' + symbol); + } + message += '). ' + func + ' was expecting ' + types[tindex].length + + ' parameters. Try ' + func + '('; + // Concat an appropriate number of placeholders for definition + if (types[tindex].length > 0) { + message += symbol + Array(types[tindex].length).join(',' + symbol); + } + message += ').'; + // If multiple definitions + if (types.length > 1) { + message += ' ' + func + ' takes different numbers of parameters ' + + 'depending on what you want to do. Click this link to learn more: '; + } + report(message, func, PARAM_COUNT); + } + // Type checking + // Example: "It looks like ellipse received an empty variable in spot #2." + // Example: "ellipse was expecting a number for parameter #1, + // received "foo" instead." + for (var format=0; format<types.length; format++) { + for (var p=0; p < types[format].length && p < args.length; p++) { + var defType = types[format][p]; + var argType = getType(args[p]); + if ('undefined' === argType || null === argType) { + report('It looks like ' + func + + ' received an empty variable in spot #' + (p+1) + + '. If not intentional, this is often a problem with scope: ' + + '[link to scope].', func, EMPTY_VAR); + } else if (defType !== '*' && !typeMatches(defType, argType, args[p])) { + message = func + ' was expecting a ' + defType.toLowerCase() + + ' for parameter #' + (p+1) + ', received '; + // Wrap strings in quotes + message += 'string' === argType ? '"' + args[p] + '"' : args[p]; + message += ' instead.'; + // If multiple definitions + if (types.length > 1) { + message += ' ' + func + ' takes different numbers of parameters ' + + 'depending on what you want to do. ' + + 'Click this link to learn more:'; + } + report(message, func, WRONG_TYPE); + } + } + } +}; +/* + * NOTE THIS FUNCTION IS TEMPORARILY DISABLED UNTIL FURTHER WORK + * AND UPDATES ARE IMPLEMENTED. -LMCCART + */ +p5.prototype._validateParameters = function() { + return true; +}; + +var errorCases = { + '0': { + fileType: 'image', + method: 'loadImage', + message: ' hosting the image online,' + }, + '1': { + fileType: 'XML file', + method: 'loadXML' + }, + '2': { + fileType: 'table file', + method: 'loadTable' + }, + '3': { + fileType: 'text file', + method: 'loadStrings' + } +}; +p5._friendlyFileLoadError = function (errorType, filePath) { + var errorInfo = errorCases[ errorType ]; + var message = 'It looks like there was a problem' + + ' loading your ' + errorInfo.fileType + '.' + + ' Try checking if the file path%c [' + filePath + '] %cis correct,' + + (errorInfo.message || '') + ' or running a local server.'; + report(message, errorInfo.method, FILE_LOAD); +}; + +function friendlyWelcome() { + // p5.js brand - magenta: #ED225D + var astrixBgColor = 'transparent'; + var astrixTxtColor = '#ED225D'; + var welcomeBgColor = '#ED225D'; + var welcomeTextColor = 'white'; + console.log( + '%c _ \n'+ + ' /\\| |/\\ \n'+ + ' \\ ` \' / \n'+ + ' / , . \\ \n'+ + ' \\/|_|\\/ '+ + '\n\n%c> p5.js says: Welcome! '+ + 'This is your friendly debugger. ' + + 'To turn me off switch to using “p5.min.js”.', + 'background-color:'+astrixBgColor+';color:' + astrixTxtColor +';', + 'background-color:'+welcomeBgColor+';color:' + welcomeTextColor +';' + ); +} + +/** + * Prints out all the colors in the color pallete with white text. + * For color blindness testing. + */ +/* function testColors() { + var str = 'A box of biscuits, a box of mixed biscuits and a biscuit mixer'; + report(str, 'println', '#ED225D'); // p5.js magenta + report(str, 'println', '#2D7BB6'); // p5.js blue + report(str, 'println', '#EE9900'); // p5.js orange + report(str, 'println', '#A67F59'); // p5.js light brown + report(str, 'println', '#704F21'); // p5.js gold + report(str, 'println', '#1CC581'); // auto cyan + report(str, 'println', '#FF6625'); // auto orange + report(str, 'println', '#79EB22'); // auto green + report(str, 'println', '#B40033'); // p5.js darkened magenta + report(str, 'println', '#084B7F'); // p5.js darkened blue + report(str, 'println', '#945F00'); // p5.js darkened orange + report(str, 'println', '#6B441D'); // p5.js darkened brown + report(str, 'println', '#2E1B00'); // p5.js darkened gold + report(str, 'println', '#008851'); // auto dark cyan + report(str, 'println', '#C83C00'); // auto dark orange + report(str, 'println', '#4DB200'); // auto dark green +} */ + +// This is a lazily-defined list of p5 symbols that may be +// misused by beginners at top-level code, outside of setup/draw. We'd like +// to detect these errors and help the user by suggesting they move them +// into setup/draw. +// +// For more details, see https://github.com/processing/p5.js/issues/1121. +var misusedAtTopLevelCode = null; +var FAQ_URL = 'https://github.com/processing/p5.js/wiki/' + + 'Frequently-Asked-Questions' + + '#why-cant-i-assign-variables-using-p5-functions-and-' + + 'variables-before-setup'; + +function defineMisusedAtTopLevelCode() { + var uniqueNamesFound = {}; + + var getSymbols = function(obj) { + return Object.getOwnPropertyNames(obj).filter(function(name) { + if (name[0] === '_') { + return false; + } + if (name in uniqueNamesFound) { + return false; + } + + uniqueNamesFound[name] = true; + + return true; + }).map(function(name) { + var type; + + if (typeof(obj[name]) === 'function') { + type = 'function'; + } else if (name === name.toUpperCase()) { + type = 'constant'; + } else { + type = 'variable'; + } + + return {name: name, type: type}; + }); + }; + + misusedAtTopLevelCode = [].concat( + getSymbols(p5.prototype), + // At present, p5 only adds its constants to p5.prototype during + // construction, which may not have happened at the time a + // ReferenceError is thrown, so we'll manually add them to our list. + getSymbols(_dereq_('./constants')) + ); + + // This will ultimately ensure that we report the most specific error + // possible to the user, e.g. advising them about HALF_PI instead of PI + // when their code misuses the former. + misusedAtTopLevelCode.sort(function(a, b) { + return b.name.length - a.name.length; + }); +} + +function helpForMisusedAtTopLevelCode(e, log) { + if (!log) { + log = console.log.bind(console); + } + + if (!misusedAtTopLevelCode) { + defineMisusedAtTopLevelCode(); + } + + // If we find that we're logging lots of false positives, we can + // uncomment the following code to avoid displaying anything if the + // user's code isn't likely to be using p5's global mode. (Note that + // setup/draw are more likely to be defined due to JS function hoisting.) + // + //if (!('setup' in window || 'draw' in window)) { + // return; + //} + + misusedAtTopLevelCode.some(function(symbol) { + // Note that while just checking for the occurrence of the + // symbol name in the error message could result in false positives, + // a more rigorous test is difficult because different browsers + // log different messages, and the format of those messages may + // change over time. + // + // For example, if the user uses 'PI' in their code, it may result + // in any one of the following messages: + // + // * 'PI' is undefined (Microsoft Edge) + // * ReferenceError: PI is undefined (Firefox) + // * Uncaught ReferenceError: PI is not defined (Chrome) + + if (e.message && e.message.indexOf(symbol.name) !== -1) { + log('%cDid you just try to use p5.js\'s ' + symbol.name + + (symbol.type === 'function' ? '() ' : ' ') + symbol.type + + '? If so, you may want to ' + + 'move it into your sketch\'s setup() function.\n\n' + + 'For more details, see: ' + FAQ_URL, + 'color: #B40033' /* Dark magenta */); + return true; + } + }); +} + +// Exposing this primarily for unit testing. +p5.prototype._helpForMisusedAtTopLevelCode = helpForMisusedAtTopLevelCode; + +if (document.readyState !== 'complete') { + window.addEventListener('error', helpForMisusedAtTopLevelCode, false); + + // Our job is only to catch ReferenceErrors that are thrown when + // global (non-instance mode) p5 APIs are used at the top-level + // scope of a file, so we'll unbind our error listener now to make + // sure we don't log false positives later. + window.addEventListener('load', function() { + window.removeEventListener('error', helpForMisusedAtTopLevelCode, false); + }); +} + +module.exports = p5; + +},{"./constants":47,"./core":48}],52:[function(_dereq_,module,exports){ +/** + * @module DOM + * @submodule DOM + * @for p5.Element + */ + +var p5 = _dereq_('./core'); + +/** + * Base class for all elements added to a sketch, including canvas, + * graphics buffers, and other HTML elements. Methods in blue are + * included in the core functionality, methods in brown are added + * with the <a href="http://p5js.org/libraries/">p5.dom library</a>. + * It is not called directly, but p5.Element + * objects are created by calling createCanvas, createGraphics, + * or in the p5.dom library, createDiv, createImg, createInput, etc. + * + * @class p5.Element + * @constructor + * @param {String} elt DOM node that is wrapped + * @param {Object} [pInst] pointer to p5 instance + */ +p5.Element = function(elt, pInst) { + /** + * Underlying HTML element. All normal HTML methods can be called on this. + * + * @property elt + */ + this.elt = elt; + this._pInst = pInst; + this._events = {}; + this.width = this.elt.offsetWidth; + this.height = this.elt.offsetHeight; +}; + +/** + * + * Attaches the element to the parent specified. A way of setting + * the container for the element. Accepts either a string ID, DOM + * node, or p5.Element. If no arguments given, parent node is returned. + * + * @method parent + * @param {String|Object} parent the ID, DOM node, or p5.Element + * of desired parent element + * @return {p5.Element} + * @example + * <div class="norender"><code> + * // in the html file: + * <div id="myContainer"></div> + * // in the js file: + * var cnv = createCanvas(100, 100); + * cnv.parent("myContainer"); + * </code></div> + * <div class='norender'><code> + * var div0 = createDiv('this is the parent'); + * var div1 = createDiv('this is the child'); + * div1.parent(div0); // use p5.Element + * </code></div> + * <div class='norender'><code> + * var div0 = createDiv('this is the parent'); + * div0.id('apples'); + * var div1 = createDiv('this is the child'); + * div1.parent('apples'); // use id + * </code></div> + * <div class='norender'><code> + * var elt = document.getElementById('myParentDiv'); + * var div1 = createDiv('this is the child'); + * div1.parent(elt); // use element from page + * </code></div> + */ +p5.Element.prototype.parent = function(p) { + if (arguments.length === 0){ + return this.elt.parentNode; + } else { + if (typeof p === 'string') { + if (p[0] === '#') { + p = p.substring(1); + } + p = document.getElementById(p); + } else if (p instanceof p5.Element) { + p = p.elt; + } + p.appendChild(this.elt); + return this; + } +}; + +/** + * + * Sets the ID of the element + * + * @method id + * @param {String} id ID of the element + * @return {p5.Element} + */ +p5.Element.prototype.id = function(id) { + if (arguments.length === 0) { + return this.elt.id; + } else { + this.elt.id = id; + this.width = this.elt.offsetWidth; + this.height = this.elt.offsetHeight; + return this; + } +}; + +/** + * + * Adds given class to the element + * + * @method class + * @param {String} class class to add + * @return {p5.Element} + */ +p5.Element.prototype.class = function(c) { + if (arguments.length === 0) { + return this.elt.className; + } else { + this.elt.className = c; + this.width = this.elt.offsetWidth; + this.height = this.elt.offsetHeight; + return this; + } +}; + +/** + * The .mousePressed() function is called once after every time a + * mouse button is pressed over the element. This can be used to + * attach element specific event listeners. + * + * @method mousePressed + * @param {Function} fxn function to be fired when mouse is + * pressed over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mousePressed(changeGray); // attach listener for + * // canvas click only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires with any click anywhere + * function mousePressed() { + * d = d + 10; + * } + * + * // this function fires only when cnv is clicked + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.mousePressed = function (fxn) { + attachListener('mousedown', fxn, this); + attachListener('touchstart', fxn, this); + return this; +}; + +/** + * The .mouseWheel() function is called once after every time a + * mouse wheel is scrolled over the element. This can be used to + * attach element specific event listeners.<br><br> + * The event.wheelDelta or event.detail property returns negative values if + * the mouse wheel if rotated up or away from the user and positive in the + * other direction. On OS X with "natural" scrolling enabled, the values are + * opposite. + * + * @method mouseWheel + * @param {Function} fxn function to be fired when mouse wheel is + * scrolled over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseWheel(changeSize); // attach listener for + * // activity on canvas only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires with mousewheel movement + * // anywhere on screen + * function mouseWheel() { + * g = g + 10; + * } + * + * // this function fires with mousewheel movement + * // over canvas only + * function changeSize() { + * if (event.wheelDelta > 0) { + * d = d + 10; + * } else { + * d = d - 10; + * } + * } + * </code></div> + * + */ +p5.Element.prototype.mouseWheel = function (fxn) { + attachListener('wheel', fxn, this); + return this; +}; + +/** + * The .mouseReleased() function is called once after every time a + * mouse button is released over the element. This can be used to + * attach element specific event listeners. + * + * @method mouseReleased + * @param {Function} fxn function to be fired when mouse is + * released over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseReleased(changeGray); // attach listener for + * // activity on canvas only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires after the mouse has been + * // released + * function mouseReleased() { + * d = d + 10; + * } + * + * // this function fires after the mouse has been + * // released while on canvas + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.mouseReleased = function (fxn) { + attachListener('mouseup', fxn, this); + attachListener('touchend', fxn, this); + return this; +}; + + +/** + * The .mouseClicked() function is called once after a mouse button is + * pressed and released over the element. This can be used to + * attach element specific event listeners. + * + * @method mouseClicked + * @param {Function} fxn function to be fired when mouse is + * clicked over the element. + * @return {p5.Element} + * @example + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseClicked(changeGray); // attach listener for + * // activity on canvas only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires after the mouse has been + * // clicked anywhere + * function mouseClicked() { + * d = d + 10; + * } + * + * // this function fires after the mouse has been + * // clicked on canvas + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.mouseClicked = function (fxn) { + attachListener('click', fxn, this); + return this; +}; + +/** + * The .mouseMoved() function is called once every time a + * mouse moves over the element. This can be used to attach an + * element specific event listener. + * + * @method mouseMoved + * @param {Function} fxn function to be fired when mouse is + * moved over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d = 30; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseMoved(changeSize); // attach listener for + * // activity on canvas only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * fill(200); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires when mouse moves anywhere on + * // page + * function mouseMoved() { + * g = g + 5; + * if (g > 255) { + * g = 0; + * } + * } + * + * // this function fires when mouse moves over canvas + * function changeSize() { + * d = d + 2; + * if (d > 100) { + * d = 0; + * } + * } + * </code></div> + * + */ +p5.Element.prototype.mouseMoved = function (fxn) { + attachListener('mousemove', fxn, this); + attachListener('touchmove', fxn, this); + return this; +}; + +/** + * The .mouseOver() function is called once after every time a + * mouse moves onto the element. This can be used to attach an + * element specific event listener. + * + * @method mouseOver + * @param {Function} fxn function to be fired when mouse is + * moved over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseOver(changeGray); + * d = 10; + * } + * + * function draw() { + * ellipse(width/2, height/2, d, d); + * } + * + * function changeGray() { + * d = d + 10; + * if (d > 100) { + * d = 0; + * } + * } + * </code></div> + * + */ +p5.Element.prototype.mouseOver = function (fxn) { + attachListener('mouseover', fxn, this); + return this; +}; + + +/** + * The .changed() function is called when the value of an + * element is changed. + * This can be used to attach an element specific event listener. + * + * @method changed + * @param {Function} fxn function to be fired when the value of an + * element changes. + * @return {p5.Element} + * @example + * <div><code> + * var sel; + * + * function setup() { + * textAlign(CENTER); + * background(200); + * sel = createSelect(); + * sel.position(10, 10); + * sel.option('pear'); + * sel.option('kiwi'); + * sel.option('grape'); + * sel.changed(mySelectEvent); + * } + * + * function mySelectEvent() { + * var item = sel.value(); + * background(200); + * text("it's a "+item+"!", 50, 50); + * } + * </code></div> + * <div><code> + * var checkbox; + * var cnv; + * + * function setup() { + * checkbox = createCheckbox(" fill"); + * checkbox.changed(changeFill); + * cnv = createCanvas(100, 100); + * cnv.position(0, 30); + * noFill(); + * } + * + * function draw() { + * background(200); + * ellipse(50, 50, 50, 50); + * } + * + * function changeFill() { + * if (checkbox.checked()) { + * fill(0); + * } else { + * noFill(); + * } + * } + * </code></div> + */ +p5.Element.prototype.changed = function (fxn) { + attachListener('change', fxn, this); + return this; +}; + +/** + * The .input() function is called when any user input is + * detected with an element. The input event is often used + * to detect keystrokes in a input element, or changes on a + * slider element. This can be used to attach an element specific + * event listener. + * + * @method input + * @param {Function} fxn function to be fired on user input. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * // Open your console to see the output + * function setup() { + * var inp = createInput(''); + * inp.input(myInputEvent); + * } + * + * function myInputEvent() { + * console.log('you are typing: ', this.value()); + * } + * </code></div> + * + */ +p5.Element.prototype.input = function (fxn) { + attachListener('input', fxn, this); + return this; +}; + +/** + * The .mouseOut() function is called once after every time a + * mouse moves off the element. This can be used to attach an + * element specific event listener. + * + * @method mouseOut + * @param {Function} fxn function to be fired when mouse is + * moved off the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseOut(changeGray); + * d = 10; + * } + * + * function draw() { + * ellipse(width/2, height/2, d, d); + * } + * + * function changeGray() { + * d = d + 10; + * if (d > 100) { + * d = 0; + * } + * } + * </code></div> + * + */ +p5.Element.prototype.mouseOut = function (fxn) { + attachListener('mouseout', fxn, this); + return this; +}; + +/** + * The .touchStarted() function is called once after every time a touch is + * registered. This can be used to attach element specific event listeners. + * + * @method touchStarted + * @param {Function} fxn function to be fired when touch is + * started over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.touchStarted(changeGray); // attach listener for + * // canvas click only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires with any touch anywhere + * function touchStarted() { + * d = d + 10; + * } + * + * // this function fires only when cnv is clicked + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.touchStarted = function (fxn) { + attachListener('touchstart', fxn, this); + attachListener('mousedown', fxn, this); + return this; +}; + +/** + * The .touchMoved() function is called once after every time a touch move is + * registered. This can be used to attach element specific event listeners. + * + * @method touchMoved + * @param {Function} fxn function to be fired when touch is moved + * over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.touchMoved(changeGray); // attach listener for + * // canvas click only + * g = 100; + * } + * + * function draw() { + * background(g); + * } + * + * // this function fires only when cnv is clicked + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.touchMoved = function (fxn) { + attachListener('touchmove', fxn, this); + attachListener('mousemove', fxn, this); + return this; +}; + +/** + * The .touchEnded() function is called once after every time a touch is + * registered. This can be used to attach element specific event listeners. + * + * @method touchEnded + * @param {Function} fxn function to be fired when touch is + * ended over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.touchEnded(changeGray); // attach listener for + * // canvas click only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires with any touch anywhere + * function touchEnded() { + * d = d + 10; + * } + * + * // this function fires only when cnv is clicked + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.touchEnded = function (fxn) { + attachListener('touchend', fxn, this); + attachListener('mouseup', fxn, this); + return this; +}; + + + +/** + * The .dragOver() function is called once after every time a + * file is dragged over the element. This can be used to attach an + * element specific event listener. + * + * @method dragOver + * @param {Function} fxn function to be fired when mouse is + * dragged over the element. + * @return {p5.Element} + */ +p5.Element.prototype.dragOver = function (fxn) { + attachListener('dragover', fxn, this); + return this; +}; + +/** + * The .dragLeave() function is called once after every time a + * dragged file leaves the element area. This can be used to attach an + * element specific event listener. + * + * @method dragLeave + * @param {Function} fxn function to be fired when mouse is + * dragged over the element. + * @return {p5.Element} + */ +p5.Element.prototype.dragLeave = function (fxn) { + attachListener('dragleave', fxn, this); + return this; +}; + +/** + * The .drop() function is called for each file dropped on the element. + * It requires a callback that is passed a p5.File object. You can + * optionally pass two callbacks, the first one (required) is triggered + * for each file dropped when the file is loaded. The second (optional) + * is triggered just once when a file (or files) are dropped. + * + * @method drop + * @param {Function} callback triggered when files are dropped. + * @param {Function} callback to receive loaded file. + * @return {p5.Element} + */ +p5.Element.prototype.drop = function (callback, fxn) { + // Make a file loader callback and trigger user's callback + function makeLoader(theFile) { + // Making a p5.File object + var p5file = new p5.File(theFile); + return function(e) { + p5file.data = e.target.result; + callback(p5file); + }; + } + + // Is the file stuff supported? + if (window.File && window.FileReader && window.FileList && window.Blob) { + + // If you want to be able to drop you've got to turn off + // a lot of default behavior + attachListener('dragover',function(evt) { + evt.stopPropagation(); + evt.preventDefault(); + },this); + + // If this is a drag area we need to turn off the default behavior + attachListener('dragleave',function(evt) { + evt.stopPropagation(); + evt.preventDefault(); + },this); + + // If just one argument it's the callback for the files + if (arguments.length > 1) { + attachListener('drop', fxn, this); + } + + // Deal with the files + attachListener('drop', function(evt) { + + evt.stopPropagation(); + evt.preventDefault(); + + // A FileList + var files = evt.dataTransfer.files; + + // Load each one and trigger the callback + for (var i = 0; i < files.length; i++) { + var f = files[i]; + var reader = new FileReader(); + reader.onload = makeLoader(f); + + + // Text or data? + // This should likely be improved + if (f.type.indexOf('text') > -1) { + reader.readAsText(f); + } else { + reader.readAsDataURL(f); + } + } + }, this); + } else { + console.log('The File APIs are not fully supported in this browser.'); + } + + return this; +}; + + + + +function attachListener(ev, fxn, ctx) { + // LM removing, not sure why we had this? + // var _this = ctx; + // var f = function (e) { fxn(e, _this); }; + var f = fxn.bind(ctx); + ctx.elt.addEventListener(ev, f, false); + ctx._events[ev] = f; +} + +/** + * Helper fxn for sharing pixel methods + * + */ +p5.Element.prototype._setProperty = function (prop, value) { + this[prop] = value; +}; + + +module.exports = p5.Element; + +},{"./core":48}],53:[function(_dereq_,module,exports){ +/** + * @module Rendering + * @submodule Rendering + * @for p5 + */ + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); + +/** + * Thin wrapper around a renderer, to be used for creating a + * graphics buffer object. Use this class if you need + * to draw into an off-screen graphics buffer. The two parameters define the + * width and height in pixels. The fields and methods for this class are + * extensive, but mirror the normal drawing API for p5. + * + * @class p5.Graphics + * @constructor + * @extends p5.Element + * @param {String} elt DOM node that is wrapped + * @param {Object} [pInst] pointer to p5 instance + * @param {Boolean} whether we're using it as main canvas + */ +p5.Graphics = function(w, h, renderer, pInst) { + + var r = renderer || constants.P2D; + + var c = document.createElement('canvas'); + var node = this._userNode || document.body; + node.appendChild(c); + + p5.Element.call(this, c, pInst, false); + this._styles = []; + this.width = w; + this.height = h; + this._pixelDensity = pInst._pixelDensity; + + if (r === constants.WEBGL) { + this._renderer = new p5.Renderer3D(c, pInst, false); + } else { + this._renderer = new p5.Renderer2D(c, pInst, false); + } + + this._renderer.resize(w, h); + this._renderer._applyDefaults(); + + pInst._elements.push(this); + + // bind methods and props of p5 to the new object + for (var p in p5.prototype) { + if (!this[p]) { + if (typeof p5.prototype[p] === 'function') { + this[p] = p5.prototype[p].bind(this); + } else { + this[p] = p5.prototype[p]; + } + } + } + + return this; +}; + +p5.Graphics.prototype = Object.create(p5.Element.prototype); + +module.exports = p5.Graphics; + +},{"./constants":47,"./core":48}],54:[function(_dereq_,module,exports){ +/** + * @module Rendering + * @submodule Rendering + * @for p5 + */ + +var p5 = _dereq_('./core'); +var constants = _dereq_('../core/constants'); + +/** + * Main graphics and rendering context, as well as the base API + * implementation for p5.js "core". To be used as the superclass for + * Renderer2D and Renderer3D classes, respecitvely. + * + * @class p5.Renderer + * @constructor + * @extends p5.Element + * @param {String} elt DOM node that is wrapped + * @param {Object} [pInst] pointer to p5 instance + * @param {Boolean} whether we're using it as main canvas + */ +p5.Renderer = function(elt, pInst, isMainCanvas) { + p5.Element.call(this, elt, pInst); + this.canvas = elt; + this._pInst = pInst; + if (isMainCanvas) { + this._isMainCanvas = true; + // for pixel method sharing with pimage + this._pInst._setProperty('_curElement', this); + this._pInst._setProperty('canvas', this.canvas); + this._pInst._setProperty('width', this.width); + this._pInst._setProperty('height', this.height); + } else { // hide if offscreen buffer by default + this.canvas.style.display = 'none'; + this._styles = []; // non-main elt styles stored in p5.Renderer + } + + + this._textSize = 12; + this._textLeading = 15; + this._textFont = 'sans-serif'; + this._textStyle = constants.NORMAL; + this._textAscent = null; + this._textDescent = null; + + + this._rectMode = constants.CORNER; + this._ellipseMode = constants.CENTER; + this._curveTightness = 0; + this._imageMode = constants.CORNER; + + this._tint = null; + this._doStroke = true; + this._doFill = true; + this._strokeSet = false; + this._fillSet = false; + this._colorMode = constants.RGB; + this._colorMaxes = { + rgb: [255, 255, 255, 255], + hsb: [360, 100, 100, 1], + hsl: [360, 100, 100, 1] + }; + +}; + +p5.Renderer.prototype = Object.create(p5.Element.prototype); + + + + +/** + * Resize our canvas element. + */ +p5.Renderer.prototype.resize = function(w, h) { + this.width = w; + this.height = h; + this.elt.width = w * this._pInst._pixelDensity; + this.elt.height = h * this._pInst._pixelDensity; + this.elt.style.width = w +'px'; + this.elt.style.height = h + 'px'; + if (this._isMainCanvas) { + this._pInst._setProperty('width', this.width); + this._pInst._setProperty('height', this.height); + } +}; + +p5.Renderer.prototype.textLeading = function(l) { + + if (arguments.length && arguments[0]) { + + this._setProperty('_textLeading', l); + return this; + } + + return this._textLeading; +}; + +p5.Renderer.prototype.textSize = function(s) { + + if (arguments.length && arguments[0]) { + + this._setProperty('_textSize', s); + this._setProperty('_textLeading', s * constants._DEFAULT_LEADMULT); + return this._applyTextProperties(); + } + + return this._textSize; +}; + +p5.Renderer.prototype.textStyle = function(s) { + + if (arguments.length && arguments[0]) { + + if (s === constants.NORMAL || + s === constants.ITALIC || + s === constants.BOLD) { + this._setProperty('_textStyle', s); + } + + return this._applyTextProperties(); + } + + return this._textStyle; +}; + +p5.Renderer.prototype.textAscent = function() { + if (this._textAscent === null) { + this._updateTextMetrics(); + } + return this._textAscent; +}; + +p5.Renderer.prototype.textDescent = function() { + + if (this._textDescent === null) { + this._updateTextMetrics(); + } + return this._textDescent; +}; + +/** + * Helper fxn to check font type (system or otf) + */ +p5.Renderer.prototype._isOpenType = function(f) { + + f = f || this._textFont; + return (typeof f === 'object' && f.font && f.font.supported); +}; + +p5.Renderer.prototype._updateTextMetrics = function() { + + if (this._isOpenType()) { + + this._setProperty('_textAscent', this._textFont._textAscent()); + this._setProperty('_textDescent', this._textFont._textDescent()); + return this; + } + + // Adapted from http://stackoverflow.com/a/25355178 + var text = document.createElement('span'); + text.style.fontFamily = this._textFont; + text.style.fontSize = this._textSize + 'px'; + text.innerHTML = 'ABCjgq|'; + + var block = document.createElement('div'); + block.style.display = 'inline-block'; + block.style.width = '1px'; + block.style.height = '0px'; + + var container = document.createElement('div'); + container.appendChild(text); + container.appendChild(block); + + container.style.height = '0px'; + container.style.overflow = 'hidden'; + document.body.appendChild(container); + + block.style.verticalAlign = 'baseline'; + var blockOffset = calculateOffset(block); + var textOffset = calculateOffset(text); + var ascent = blockOffset[1] - textOffset[1]; + + block.style.verticalAlign = 'bottom'; + blockOffset = calculateOffset(block); + textOffset = calculateOffset(text); + var height = blockOffset[1] - textOffset[1]; + var descent = height - ascent; + + document.body.removeChild(container); + + this._setProperty('_textAscent', ascent); + this._setProperty('_textDescent', descent); + + return this; +}; + +/** + * Helper fxn to measure ascent and descent. + * Adapted from http://stackoverflow.com/a/25355178 + */ +function calculateOffset(object) { + var currentLeft = 0, + currentTop = 0; + if (object.offsetParent) { + do { + currentLeft += object.offsetLeft; + currentTop += object.offsetTop; + } while (object = object.offsetParent); + } else { + currentLeft += object.offsetLeft; + currentTop += object.offsetTop; + } + return [currentLeft, currentTop]; +} + +module.exports = p5.Renderer; + +},{"../core/constants":47,"./core":48}],55:[function(_dereq_,module,exports){ + +var p5 = _dereq_('./core'); +var canvas = _dereq_('./canvas'); +var constants = _dereq_('./constants'); +var filters = _dereq_('../image/filters'); + +_dereq_('./p5.Renderer'); + +/** + * p5.Renderer2D + * The 2D graphics canvas renderer class. + * extends p5.Renderer + */ +var styleEmpty = 'rgba(0,0,0,0)'; +// var alphaThreshold = 0.00125; // minimum visible + +p5.Renderer2D = function(elt, pInst, isMainCanvas){ + p5.Renderer.call(this, elt, pInst, isMainCanvas); + this.drawingContext = this.canvas.getContext('2d'); + this._pInst._setProperty('drawingContext', this.drawingContext); + return this; +}; + +p5.Renderer2D.prototype = Object.create(p5.Renderer.prototype); + +p5.Renderer2D.prototype._applyDefaults = function() { + this.drawingContext.fillStyle = constants._DEFAULT_FILL; + this.drawingContext.strokeStyle = constants._DEFAULT_STROKE; + this.drawingContext.lineCap = constants.ROUND; + this.drawingContext.font = 'normal 12px sans-serif'; +}; + +p5.Renderer2D.prototype.resize = function(w,h) { + p5.Renderer.prototype.resize.call(this, w,h); + this.drawingContext.scale(this._pInst._pixelDensity, + this._pInst._pixelDensity); +}; + +////////////////////////////////////////////// +// COLOR | Setting +////////////////////////////////////////////// + +p5.Renderer2D.prototype.background = function() { + this.drawingContext.save(); + this.drawingContext.setTransform(1, 0, 0, 1, 0, 0); + this.drawingContext.scale(this._pInst._pixelDensity, + this._pInst._pixelDensity); + + if (arguments[0] instanceof p5.Image) { + this._pInst.image(arguments[0], 0, 0, this.width, this.height); + } else { + var curFill = this.drawingContext.fillStyle; + // create background rect + var color = this._pInst.color.apply(this, arguments); + var newFill = color.toString(); + this.drawingContext.fillStyle = newFill; + this.drawingContext.fillRect(0, 0, this.width, this.height); + // reset fill + this.drawingContext.fillStyle = curFill; + } + this.drawingContext.restore(); +}; + +p5.Renderer2D.prototype.clear = function() { + this.drawingContext.clearRect(0, 0, this.width, this.height); +}; + +p5.Renderer2D.prototype.fill = function() { + + var ctx = this.drawingContext; + var color = this._pInst.color.apply(this, arguments); + ctx.fillStyle = color.toString(); +}; + +p5.Renderer2D.prototype.stroke = function() { + var ctx = this.drawingContext; + var color = this._pInst.color.apply(this, arguments); + ctx.strokeStyle = color.toString(); +}; + +////////////////////////////////////////////// +// IMAGE | Loading & Displaying +////////////////////////////////////////////// + +p5.Renderer2D.prototype.image = + function (img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) { + var cnv; + try { + if (this._tint) { + if (p5.MediaElement && img instanceof p5.MediaElement) { + img.loadPixels(); + } + if (img.canvas) { + cnv = this._getTintedImageCanvas(img); + } + } + if (!cnv) { + cnv = img.canvas || img.elt; + } + this.drawingContext.drawImage(cnv, sx, sy, sWidth, sHeight, dx, dy, + dWidth, dHeight); + } catch (e) { + if (e.name !== 'NS_ERROR_NOT_AVAILABLE') { + throw e; + } + } +}; + +p5.Renderer2D.prototype._getTintedImageCanvas = function (img) { + if (!img.canvas) { + return img; + } + var pixels = filters._toPixels(img.canvas); + var tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = img.canvas.width; + tmpCanvas.height = img.canvas.height; + var tmpCtx = tmpCanvas.getContext('2d'); + var id = tmpCtx.createImageData(img.canvas.width, img.canvas.height); + var newPixels = id.data; + for (var i = 0; i < pixels.length; i += 4) { + var r = pixels[i]; + var g = pixels[i + 1]; + var b = pixels[i + 2]; + var a = pixels[i + 3]; + newPixels[i] = r * this._tint[0] / 255; + newPixels[i + 1] = g * this._tint[1] / 255; + newPixels[i + 2] = b * this._tint[2] / 255; + newPixels[i + 3] = a * this._tint[3] / 255; + } + tmpCtx.putImageData(id, 0, 0); + return tmpCanvas; +}; + + +////////////////////////////////////////////// +// IMAGE | Pixels +////////////////////////////////////////////// + +p5.Renderer2D.prototype.blendMode = function(mode) { + this.drawingContext.globalCompositeOperation = mode; +}; +p5.Renderer2D.prototype.blend = function() { + var currBlend = this.drawingContext.globalCompositeOperation; + var blendMode = arguments[arguments.length - 1]; + + var copyArgs = Array.prototype.slice.call( + arguments, + 0, + arguments.length - 1 + ); + + this.drawingContext.globalCompositeOperation = blendMode; + this._pInst.copy.apply(this._pInst, copyArgs); + this.drawingContext.globalCompositeOperation = currBlend; +}; + +p5.Renderer2D.prototype.copy = function () { + var srcImage, sx, sy, sw, sh, dx, dy, dw, dh; + if (arguments.length === 9) { + srcImage = arguments[0]; + sx = arguments[1]; + sy = arguments[2]; + sw = arguments[3]; + sh = arguments[4]; + dx = arguments[5]; + dy = arguments[6]; + dw = arguments[7]; + dh = arguments[8]; + } else if (arguments.length === 8) { + srcImage = this._pInst; + sx = arguments[0]; + sy = arguments[1]; + sw = arguments[2]; + sh = arguments[3]; + dx = arguments[4]; + dy = arguments[5]; + dw = arguments[6]; + dh = arguments[7]; + } else { + throw new Error('Signature not supported'); + } + p5.Renderer2D._copyHelper(srcImage, sx, sy, sw, sh, dx, dy, dw, dh); +}; + +p5.Renderer2D._copyHelper = +function (srcImage, sx, sy, sw, sh, dx, dy, dw, dh) { + var s = srcImage.canvas.width / srcImage.width; + this.drawingContext.drawImage(srcImage.canvas, + s * sx, s * sy, s * sw, s * sh, dx, dy, dw, dh); +}; + +p5.Renderer2D.prototype.get = function(x, y, w, h) { + if (x === undefined && y === undefined && + w === undefined && h === undefined){ + x = 0; + y = 0; + w = this.width; + h = this.height; + } else if (w === undefined && h === undefined) { + w = 1; + h = 1; + } + + // if the section does not overlap the canvas + if(x + w < 0 || y + h < 0 || x > this.width || y > this.height){ + return [0, 0, 0, 255]; + } + + var ctx = this._pInst || this; + + var pd = ctx._pixelDensity; + + this.loadPixels.call(ctx); + + // round down to get integer numbers + x = Math.floor(x); + y = Math.floor(y); + + if (w === 1 && h === 1){ + + return [ + ctx.pixels[pd*4*(y*this.width+x)], + ctx.pixels[pd*(4*(y*this.width+x)+1)], + ctx.pixels[pd*(4*(y*this.width+x)+2)], + ctx.pixels[pd*(4*(y*this.width+x)+3)] + ]; + } else { + var sx = x * pd; + var sy = y * pd; + //auto constrain the width and height to + //dimensions of the source image + var dw = Math.min(w, ctx.width); + var dh = Math.min(h, ctx.height); + var sw = dw * pd; + var sh = dh * pd; + + var region = new p5.Image(dw, dh); + region.canvas.getContext('2d').drawImage(this.canvas, sx, sy, sw, sh, + 0, 0, dw, dh); + + return region; + } +}; + +p5.Renderer2D.prototype.loadPixels = function () { + var pd = this._pixelDensity || this._pInst._pixelDensity; + var w = this.width * pd; + var h = this.height * pd; + var imageData = this.drawingContext.getImageData(0, 0, w, h); + // @todo this should actually set pixels per object, so diff buffers can + // have diff pixel arrays. + if (this._pInst) { + this._pInst._setProperty('imageData', imageData); + this._pInst._setProperty('pixels', imageData.data); + } else { // if called by p5.Image + this._setProperty('imageData', imageData); + this._setProperty('pixels', imageData.data); + } +}; + +p5.Renderer2D.prototype.set = function (x, y, imgOrCol) { + // round down to get integer numbers + x = Math.floor(x); + y = Math.floor(y); + if (imgOrCol instanceof p5.Image) { + this.drawingContext.save(); + this.drawingContext.setTransform(1, 0, 0, 1, 0, 0); + this.drawingContext.scale(this._pInst._pixelDensity, + this._pInst._pixelDensity); + this.drawingContext.drawImage(imgOrCol.canvas, x, y); + this.loadPixels.call(this._pInst); + this.drawingContext.restore(); + } else { + var ctx = this._pInst || this; + var r = 0, g = 0, b = 0, a = 0; + var idx = 4*((y * ctx._pixelDensity) * + (this.width * ctx._pixelDensity) + (x * ctx._pixelDensity)); + if (!ctx.imageData) { + ctx.loadPixels.call(ctx); + } + if (typeof imgOrCol === 'number') { + if (idx < ctx.pixels.length) { + r = imgOrCol; + g = imgOrCol; + b = imgOrCol; + a = 255; + //this.updatePixels.call(this); + } + } + else if (imgOrCol instanceof Array) { + if (imgOrCol.length < 4) { + throw new Error('pixel array must be of the form [R, G, B, A]'); + } + if (idx < ctx.pixels.length) { + r = imgOrCol[0]; + g = imgOrCol[1]; + b = imgOrCol[2]; + a = imgOrCol[3]; + //this.updatePixels.call(this); + } + } else if (imgOrCol instanceof p5.Color) { + if (idx < ctx.pixels.length) { + r = imgOrCol.levels[0]; + g = imgOrCol.levels[1]; + b = imgOrCol.levels[2]; + a = imgOrCol.levels[3]; + //this.updatePixels.call(this); + } + } + // loop over pixelDensity * pixelDensity + for (var i = 0; i < ctx._pixelDensity; i++) { + for (var j = 0; j < ctx._pixelDensity; j++) { + // loop over + idx = 4*((y * ctx._pixelDensity + j) * this.width * + ctx._pixelDensity + (x * ctx._pixelDensity + i)); + ctx.pixels[idx] = r; + ctx.pixels[idx+1] = g; + ctx.pixels[idx+2] = b; + ctx.pixels[idx+3] = a; + } + } + } +}; + +p5.Renderer2D.prototype.updatePixels = function (x, y, w, h) { + var pd = this._pixelDensity || this._pInst._pixelDensity; + if (x === undefined && + y === undefined && + w === undefined && + h === undefined) { + x = 0; + y = 0; + w = this.width; + h = this.height; + } + w *= pd; + h *= pd; + + if (this._pInst) { + this.drawingContext.putImageData(this._pInst.imageData, x, y, 0, 0, w, h); + } else { + this.drawingContext.putImageData(this.imageData, x, y, 0, 0, w, h); + } +}; + +////////////////////////////////////////////// +// SHAPE | 2D Primitives +////////////////////////////////////////////// + +/** + * Generate a cubic Bezier representing an arc on the unit circle of total + * angle `size` radians, beginning `start` radians above the x-axis. Up to + * four of these curves are combined to make a full arc. + * + * See www.joecridge.me/bezier.pdf for an explanation of the method. + */ +p5.Renderer2D.prototype._acuteArcToBezier = + function _acuteArcToBezier(start, size) { + // Evauate constants. + var alpha = size / 2.0, + cos_alpha = Math.cos(alpha), + sin_alpha = Math.sin(alpha), + cot_alpha = 1.0 / Math.tan(alpha), + phi = start + alpha, // This is how far the arc needs to be rotated. + cos_phi = Math.cos(phi), + sin_phi = Math.sin(phi), + lambda = (4.0 - cos_alpha) / 3.0, + mu = sin_alpha + (cos_alpha - lambda) * cot_alpha; + + // Return rotated waypoints. + return { + ax: Math.cos(start), + ay: Math.sin(start), + bx: lambda * cos_phi + mu * sin_phi, + by: lambda * sin_phi - mu * cos_phi, + cx: lambda * cos_phi - mu * sin_phi, + cy: lambda * sin_phi + mu * cos_phi, + dx: Math.cos(start + size), + dy: Math.sin(start + size) + }; +}; + +p5.Renderer2D.prototype.arc = + function(x, y, w, h, start, stop, mode) { + var ctx = this.drawingContext; + var vals = canvas.arcModeAdjust(x, y, w, h, this._ellipseMode); + var rx = vals.w / 2.0; + var ry = vals.h / 2.0; + var epsilon = 0.00001; // Smallest visible angle on displays up to 4K. + var arcToDraw = 0; + var curves = []; + + // Create curves + while(stop - start > epsilon) { + arcToDraw = Math.min(stop - start, constants.HALF_PI); + curves.push(this._acuteArcToBezier(start, arcToDraw)); + start += arcToDraw; + } + + // Fill curves + if (this._doFill) { + ctx.beginPath(); + curves.forEach(function (curve, index) { + if (index === 0) { + ctx.moveTo(vals.x + curve.ax * rx, vals.y + curve.ay * ry); + } + ctx.bezierCurveTo(vals.x + curve.bx * rx, vals.y + curve.by * ry, + vals.x + curve.cx * rx, vals.y + curve.cy * ry, + vals.x + curve.dx * rx, vals.y + curve.dy * ry); + }); + if (mode === constants.PIE || mode == null) { + ctx.lineTo(vals.x, vals.y); + } + ctx.closePath(); + ctx.fill(); + } + + // Stroke curves + if (this._doStroke) { + ctx.beginPath(); + curves.forEach(function (curve, index) { + if (index === 0) { + ctx.moveTo(vals.x + curve.ax * rx, vals.y + curve.ay * ry); + } + ctx.bezierCurveTo(vals.x + curve.bx * rx, vals.y + curve.by * ry, + vals.x + curve.cx * rx, vals.y + curve.cy * ry, + vals.x + curve.dx * rx, vals.y + curve.dy * ry); + }); + if (mode === constants.PIE) { + ctx.lineTo(vals.x, vals.y); + ctx.closePath(); + } else if (mode === constants.CHORD) { + ctx.closePath(); + } + ctx.stroke(); + } + return this; +}; + +p5.Renderer2D.prototype.ellipse = function(x, y, w, h) { + var ctx = this.drawingContext; + var doFill = this._doFill, doStroke = this._doStroke; + if (doFill && !doStroke) { + if(ctx.fillStyle === styleEmpty) { + return this; + } + } else if (!doFill && doStroke) { + if(ctx.strokeStyle === styleEmpty) { + return this; + } + } + var vals = canvas.modeAdjust(x, y, w, h, this._ellipseMode); + var kappa = 0.5522847498, + ox = (vals.w / 2) * kappa, // control point offset horizontal + oy = (vals.h / 2) * kappa, // control point offset vertical + xe = vals.x + vals.w, // x-end + ye = vals.y + vals.h, // y-end + xm = vals.x + vals.w / 2, // x-middle + ym = vals.y + vals.h / 2; // y-middle + ctx.beginPath(); + ctx.moveTo(vals.x, ym); + ctx.bezierCurveTo(vals.x, ym - oy, xm - ox, vals.y, xm, vals.y); + ctx.bezierCurveTo(xm + ox, vals.y, xe, ym - oy, xe, ym); + ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + ctx.bezierCurveTo(xm - ox, ye, vals.x, ym + oy, vals.x, ym); + ctx.closePath(); + if (doFill) { + ctx.fill(); + } + if (doStroke) { + ctx.stroke(); + } +}; + +p5.Renderer2D.prototype.line = function(x1, y1, x2, y2) { + var ctx = this.drawingContext; + if (!this._doStroke) { + return this; + } else if(ctx.strokeStyle === styleEmpty){ + return this; + } + // Translate the line by (0.5, 0.5) to draw it crisp + if (ctx.lineWidth % 2 === 1) { + ctx.translate(0.5, 0.5); + } + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + if (ctx.lineWidth % 2 === 1) { + ctx.translate(-0.5, -0.5); + } + return this; +}; + +p5.Renderer2D.prototype.point = function(x, y) { + var ctx = this.drawingContext; + var s = ctx.strokeStyle; + var f = ctx.fillStyle; + if (!this._doStroke) { + return this; + } else if(ctx.strokeStyle === styleEmpty){ + return this; + } + x = Math.round(x); + y = Math.round(y); + ctx.fillStyle = s; + if (ctx.lineWidth > 1) { + ctx.beginPath(); + ctx.arc( + x, + y, + ctx.lineWidth / 2, + 0, + constants.TWO_PI, + false + ); + ctx.fill(); + } else { + ctx.fillRect(x, y, 1, 1); + } + ctx.fillStyle = f; +}; + +p5.Renderer2D.prototype.quad = + function(x1, y1, x2, y2, x3, y3, x4, y4) { + var ctx = this.drawingContext; + var doFill = this._doFill, doStroke = this._doStroke; + if (doFill && !doStroke) { + if(ctx.fillStyle === styleEmpty) { + return this; + } + } else if (!doFill && doStroke) { + if(ctx.strokeStyle === styleEmpty) { + return this; + } + } + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.lineTo(x3, y3); + ctx.lineTo(x4, y4); + ctx.closePath(); + if (doFill) { + ctx.fill(); + } + if (doStroke) { + ctx.stroke(); + } + return this; +}; + +p5.Renderer2D.prototype.rect = function(x, y, w, h, tl, tr, br, bl) { + var ctx = this.drawingContext; + var doFill = this._doFill, doStroke = this._doStroke; + if (doFill && !doStroke) { + if(ctx.fillStyle === styleEmpty) { + return this; + } + } else if (!doFill && doStroke) { + if(ctx.strokeStyle === styleEmpty) { + return this; + } + } + var vals = canvas.modeAdjust(x, y, w, h, this._rectMode); + // Translate the line by (0.5, 0.5) to draw a crisp rectangle border + if (this._doStroke && ctx.lineWidth % 2 === 1) { + ctx.translate(0.5, 0.5); + } + ctx.beginPath(); + + if (typeof tl === 'undefined') { + // No rounded corners + ctx.rect(vals.x, vals.y, vals.w, vals.h); + } else { + // At least one rounded corner + // Set defaults when not specified + if (typeof tr === 'undefined') { tr = tl; } + if (typeof br === 'undefined') { br = tr; } + if (typeof bl === 'undefined') { bl = br; } + + // Cache and compute several values + var _x = vals.x; + var _y = vals.y; + var _w = vals.w; + var _h = vals.h; + var hw = _w / 2; + var hh = _h / 2; + + // Clip radii + if (_w < 2 * tl) { tl = hw; } + if (_h < 2 * tl) { tl = hh; } + if (_w < 2 * tr) { tr = hw; } + if (_h < 2 * tr) { tr = hh; } + if (_w < 2 * br) { br = hw; } + if (_h < 2 * br) { br = hh; } + if (_w < 2 * bl) { bl = hw; } + if (_h < 2 * bl) { bl = hh; } + + // Draw shape + ctx.beginPath(); + ctx.moveTo(_x + tl, _y); + ctx.arcTo(_x + _w, _y, _x + _w, _y + _h, tr); + ctx.arcTo(_x + _w, _y + _h, _x, _y + _h, br); + ctx.arcTo(_x, _y + _h, _x, _y, bl); + ctx.arcTo(_x, _y, _x + _w, _y, tl); + ctx.closePath(); + } + if (this._doFill) { + ctx.fill(); + } + if (this._doStroke) { + ctx.stroke(); + } + if (this._doStroke && ctx.lineWidth % 2 === 1) { + ctx.translate(-0.5, -0.5); + } + return this; +}; + +p5.Renderer2D.prototype.triangle = function(x1, y1, x2, y2, x3, y3) { + var ctx = this.drawingContext; + var doFill = this._doFill, doStroke = this._doStroke; + if (doFill && !doStroke) { + if(ctx.fillStyle === styleEmpty) { + return this; + } + } else if (!doFill && doStroke) { + if(ctx.strokeStyle === styleEmpty) { + return this; + } + } + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.lineTo(x3, y3); + ctx.closePath(); + if (doFill) { + ctx.fill(); + } + if (doStroke) { + ctx.stroke(); + } +}; + +p5.Renderer2D.prototype.endShape = +function (mode, vertices, isCurve, isBezier, + isQuadratic, isContour, shapeKind) { + if (vertices.length === 0) { + return this; + } + if (!this._doStroke && !this._doFill) { + return this; + } + var closeShape = mode === constants.CLOSE; + var v; + if (closeShape && !isContour) { + vertices.push(vertices[0]); + } + var i, j; + var numVerts = vertices.length; + if (isCurve && (shapeKind === constants.POLYGON || shapeKind === null)) { + if (numVerts > 3) { + var b = [], s = 1 - this._curveTightness; + this.drawingContext.beginPath(); + this.drawingContext.moveTo(vertices[1][0], vertices[1][1]); + for (i = 1; i + 2 < numVerts; i++) { + v = vertices[i]; + b[0] = [ + v[0], + v[1] + ]; + b[1] = [ + v[0] + (s * vertices[i + 1][0] - s * vertices[i - 1][0]) / 6, + v[1] + (s * vertices[i + 1][1] - s * vertices[i - 1][1]) / 6 + ]; + b[2] = [ + vertices[i + 1][0] + + (s * vertices[i][0]-s * vertices[i + 2][0]) / 6, + vertices[i + 1][1]+(s * vertices[i][1] - s*vertices[i + 2][1]) / 6 + ]; + b[3] = [ + vertices[i + 1][0], + vertices[i + 1][1] + ]; + this.drawingContext.bezierCurveTo(b[1][0],b[1][1], + b[2][0],b[2][1],b[3][0],b[3][1]); + } + if (closeShape) { + this.drawingContext.lineTo(vertices[i + 1][0], vertices[i + 1][1]); + } + this._doFillStrokeClose(); + } + } else if (isBezier&&(shapeKind===constants.POLYGON ||shapeKind === null)) { + this.drawingContext.beginPath(); + for (i = 0; i < numVerts; i++) { + if (vertices[i].isVert) { + if (vertices[i].moveTo) { + this.drawingContext.moveTo(vertices[i][0], vertices[i][1]); + } else { + this.drawingContext.lineTo(vertices[i][0], vertices[i][1]); + } + } else { + this.drawingContext.bezierCurveTo(vertices[i][0], vertices[i][1], + vertices[i][2], vertices[i][3], vertices[i][4], vertices[i][5]); + } + } + this._doFillStrokeClose(); + } else if (isQuadratic && + (shapeKind === constants.POLYGON || shapeKind === null)) { + this.drawingContext.beginPath(); + for (i = 0; i < numVerts; i++) { + if (vertices[i].isVert) { + if (vertices[i].moveTo) { + this.drawingContext.moveTo([0], vertices[i][1]); + } else { + this.drawingContext.lineTo(vertices[i][0], vertices[i][1]); + } + } else { + this.drawingContext.quadraticCurveTo(vertices[i][0], vertices[i][1], + vertices[i][2], vertices[i][3]); + } + } + this._doFillStrokeClose(); + } else { + if (shapeKind === constants.POINTS) { + for (i = 0; i < numVerts; i++) { + v = vertices[i]; + if (this._doStroke) { + this._pInst.stroke(v[6]); + } + this._pInst.point(v[0], v[1]); + } + } else if (shapeKind === constants.LINES) { + for (i = 0; i + 1 < numVerts; i += 2) { + v = vertices[i]; + if (this._doStroke) { + this._pInst.stroke(vertices[i + 1][6]); + } + this._pInst.line(v[0], v[1], vertices[i + 1][0], vertices[i + 1][1]); + } + } else if (shapeKind === constants.TRIANGLES) { + for (i = 0; i + 2 < numVerts; i += 3) { + v = vertices[i]; + this.drawingContext.beginPath(); + this.drawingContext.moveTo(v[0], v[1]); + this.drawingContext.lineTo(vertices[i + 1][0], vertices[i + 1][1]); + this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]); + this.drawingContext.lineTo(v[0], v[1]); + if (this._doFill) { + this._pInst.fill(vertices[i + 2][5]); + this.drawingContext.fill(); + } + if (this._doStroke) { + this._pInst.stroke(vertices[i + 2][6]); + this.drawingContext.stroke(); + } + this.drawingContext.closePath(); + } + } else if (shapeKind === constants.TRIANGLE_STRIP) { + for (i = 0; i + 1 < numVerts; i++) { + v = vertices[i]; + this.drawingContext.beginPath(); + this.drawingContext.moveTo(vertices[i + 1][0], vertices[i + 1][1]); + this.drawingContext.lineTo(v[0], v[1]); + if (this._doStroke) { + this._pInst.stroke(vertices[i + 1][6]); + } + if (this._doFill) { + this._pInst.fill(vertices[i + 1][5]); + } + if (i + 2 < numVerts) { + this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]); + if (this._doStroke) { + this._pInst.stroke(vertices[i + 2][6]); + } + if (this._doFill) { + this._pInst.fill(vertices[i + 2][5]); + } + } + this._doFillStrokeClose(); + } + } else if (shapeKind === constants.TRIANGLE_FAN) { + if (numVerts > 2) { + this.drawingContext.beginPath(); + this.drawingContext.moveTo(vertices[0][0], vertices[0][1]); + this.drawingContext.lineTo(vertices[1][0], vertices[1][1]); + this.drawingContext.lineTo(vertices[2][0], vertices[2][1]); + if (this._doFill) { + this._pInst.fill(vertices[2][5]); + } + if (this._doStroke) { + this._pInst.stroke(vertices[2][6]); + } + this._doFillStrokeClose(); + for (i = 3; i < numVerts; i++) { + v = vertices[i]; + this.drawingContext.beginPath(); + this.drawingContext.moveTo(vertices[0][0], vertices[0][1]); + this.drawingContext.lineTo(vertices[i - 1][0], vertices[i - 1][1]); + this.drawingContext.lineTo(v[0], v[1]); + if (this._doFill) { + this._pInst.fill(v[5]); + } + if (this._doStroke) { + this._pInst.stroke(v[6]); + } + this._doFillStrokeClose(); + } + } + } else if (shapeKind === constants.QUADS) { + for (i = 0; i + 3 < numVerts; i += 4) { + v = vertices[i]; + this.drawingContext.beginPath(); + this.drawingContext.moveTo(v[0], v[1]); + for (j = 1; j < 4; j++) { + this.drawingContext.lineTo(vertices[i + j][0], vertices[i + j][1]); + } + this.drawingContext.lineTo(v[0], v[1]); + if (this._doFill) { + this._pInst.fill(vertices[i + 3][5]); + } + if (this._doStroke) { + this._pInst.stroke(vertices[i + 3][6]); + } + this._doFillStrokeClose(); + } + } else if (shapeKind === constants.QUAD_STRIP) { + if (numVerts > 3) { + for (i = 0; i + 1 < numVerts; i += 2) { + v = vertices[i]; + this.drawingContext.beginPath(); + if (i + 3 < numVerts) { + this.drawingContext.moveTo(vertices[i + 2][0], vertices[i+2][1]); + this.drawingContext.lineTo(v[0], v[1]); + this.drawingContext.lineTo(vertices[i + 1][0], vertices[i+1][1]); + this.drawingContext.lineTo(vertices[i + 3][0], vertices[i+3][1]); + if (this._doFill) { + this._pInst.fill(vertices[i + 3][5]); + } + if (this._doStroke) { + this._pInst.stroke(vertices[i + 3][6]); + } + } else { + this.drawingContext.moveTo(v[0], v[1]); + this.drawingContext.lineTo(vertices[i + 1][0], vertices[i+1][1]); + } + this._doFillStrokeClose(); + } + } + } else { + this.drawingContext.beginPath(); + this.drawingContext.moveTo(vertices[0][0], vertices[0][1]); + for (i = 1; i < numVerts; i++) { + v = vertices[i]; + if (v.isVert) { + if (v.moveTo) { + this.drawingContext.moveTo(v[0], v[1]); + } else { + this.drawingContext.lineTo(v[0], v[1]); + } + } + } + this._doFillStrokeClose(); + } + } + isCurve = false; + isBezier = false; + isQuadratic = false; + isContour = false; + if (closeShape) { + vertices.pop(); + } + return this; +}; +////////////////////////////////////////////// +// SHAPE | Attributes +////////////////////////////////////////////// + +p5.Renderer2D.prototype.noSmooth = function() { + if ('imageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.imageSmoothingEnabled = false; + } + else if ('mozImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.mozImageSmoothingEnabled = false; + } + else if ('webkitImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.webkitImageSmoothingEnabled = false; + } + else if ('msImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.msImageSmoothingEnabled = false; + } + return this; +}; + +p5.Renderer2D.prototype.smooth = function() { + if ('imageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.imageSmoothingEnabled = true; + } + else if ('mozImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.mozImageSmoothingEnabled = true; + } + else if ('webkitImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.webkitImageSmoothingEnabled = true; + } + else if ('msImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.msImageSmoothingEnabled = true; + } + return this; +}; + +p5.Renderer2D.prototype.strokeCap = function(cap) { + if (cap === constants.ROUND || + cap === constants.SQUARE || + cap === constants.PROJECT) { + this.drawingContext.lineCap = cap; + } + return this; +}; + +p5.Renderer2D.prototype.strokeJoin = function(join) { + if (join === constants.ROUND || + join === constants.BEVEL || + join === constants.MITER) { + this.drawingContext.lineJoin = join; + } + return this; +}; + +p5.Renderer2D.prototype.strokeWeight = function(w) { + if (typeof w === 'undefined' || w === 0) { + // hack because lineWidth 0 doesn't work + this.drawingContext.lineWidth = 0.0001; + } else { + this.drawingContext.lineWidth = w; + } + return this; +}; + +p5.Renderer2D.prototype._getFill = function(){ + return this.drawingContext.fillStyle; +}; + +p5.Renderer2D.prototype._getStroke = function(){ + return this.drawingContext.strokeStyle; +}; + +////////////////////////////////////////////// +// SHAPE | Curves +////////////////////////////////////////////// +p5.Renderer2D.prototype.bezier = function (x1, y1, x2, y2, x3, y3, x4, y4) { + this._pInst.beginShape(); + this._pInst.vertex(x1, y1); + this._pInst.bezierVertex(x2, y2, x3, y3, x4, y4); + this._pInst.endShape(); + return this; +}; + +p5.Renderer2D.prototype.curve = function (x1, y1, x2, y2, x3, y3, x4, y4) { + this._pInst.beginShape(); + this._pInst.curveVertex(x1, y1); + this._pInst.curveVertex(x2, y2); + this._pInst.curveVertex(x3, y3); + this._pInst.curveVertex(x4, y4); + this._pInst.endShape(); + return this; +}; + +////////////////////////////////////////////// +// SHAPE | Vertex +////////////////////////////////////////////// + +p5.Renderer2D.prototype._doFillStrokeClose = function () { + if (this._doFill) { + this.drawingContext.fill(); + } + if (this._doStroke) { + this.drawingContext.stroke(); + } + this.drawingContext.closePath(); +}; + +////////////////////////////////////////////// +// TRANSFORM +////////////////////////////////////////////// + +p5.Renderer2D.prototype.applyMatrix = +function(n00, n01, n02, n10, n11, n12) { + this.drawingContext.transform(n00, n01, n02, n10, n11, n12); +}; + +p5.Renderer2D.prototype.resetMatrix = function() { + this.drawingContext.setTransform(1, 0, 0, 1, 0, 0); + this.drawingContext.scale(this._pInst._pixelDensity, + this._pInst._pixelDensity); + return this; +}; + +p5.Renderer2D.prototype.rotate = function(r) { + this.drawingContext.rotate(r); +}; + +p5.Renderer2D.prototype.scale = function(x,y) { + this.drawingContext.scale(x, y); + return this; +}; + +p5.Renderer2D.prototype.shearX = function(angle) { + if (this._pInst._angleMode === constants.DEGREES) { + angle = this._pInst.radians(angle); + } + this.drawingContext.transform(1, 0, this._pInst.tan(angle), 1, 0, 0); + return this; +}; + +p5.Renderer2D.prototype.shearY = function(angle) { + if (this._pInst._angleMode === constants.DEGREES) { + angle = this._pInst.radians(angle); + } + this.drawingContext.transform(1, this._pInst.tan(angle), 0, 1, 0, 0); + return this; +}; + +p5.Renderer2D.prototype.translate = function(x, y) { + this.drawingContext.translate(x, y); + return this; +}; + +////////////////////////////////////////////// +// TYPOGRAPHY +// +////////////////////////////////////////////// + +p5.Renderer2D.prototype.text = function (str, x, y, maxWidth, maxHeight) { + + var p = this._pInst, cars, n, ii, jj, line, testLine, + testWidth, words, totalHeight, baselineHacked, + finalMaxHeight = Number.MAX_VALUE; + + // baselineHacked: (HACK) + // A temporary fix to conform to Processing's implementation + // of BASELINE vertical alignment in a bounding box + + if (!(this._doFill || this._doStroke)) { + return; + } + + if (typeof str !== 'string') { + str = str.toString(); + } + + str = str.replace(/(\t)/g, ' '); + cars = str.split('\n'); + + if (typeof maxWidth !== 'undefined') { + + totalHeight = 0; + for (ii = 0; ii < cars.length; ii++) { + line = ''; + words = cars[ii].split(' '); + for (n = 0; n < words.length; n++) { + testLine = line + words[n] + ' '; + testWidth = this.textWidth(testLine); + if (testWidth > maxWidth) { + line = words[n] + ' '; + totalHeight += p.textLeading(); + } else { + line = testLine; + } + } + } + + if (this._rectMode === constants.CENTER) { + + x -= maxWidth / 2; + y -= maxHeight / 2; + } + + switch (this.drawingContext.textAlign) { + + case constants.CENTER: + x += maxWidth / 2; + break; + case constants.RIGHT: + x += maxWidth; + break; + } + + if (typeof maxHeight !== 'undefined') { + + switch (this.drawingContext.textBaseline) { + case constants.BOTTOM: + y += (maxHeight - totalHeight); + break; + case constants._CTX_MIDDLE: + y += (maxHeight - totalHeight) / 2; + break; + case constants.BASELINE: + baselineHacked = true; + this.drawingContext.textBaseline = constants.TOP; + break; + } + + // remember the max-allowed y-position for any line (fix to #928) + finalMaxHeight = (y + maxHeight) - p.textAscent(); + } + + for (ii = 0; ii < cars.length; ii++) { + + line = ''; + words = cars[ii].split(' '); + for (n = 0; n < words.length; n++) { + testLine = line + words[n] + ' '; + testWidth = this.textWidth(testLine); + if (testWidth > maxWidth && line.length > 0) { + this._renderText(p, line, x, y, finalMaxHeight); + line = words[n] + ' '; + y += p.textLeading(); + } else { + line = testLine; + } + } + + this._renderText(p, line, x, y, finalMaxHeight); + y += p.textLeading(); + } + } + else { + for (jj = 0; jj < cars.length; jj++) { + + this._renderText(p, cars[jj], x, y, finalMaxHeight); + y += p.textLeading(); + } + } + + if (baselineHacked) { + this.drawingContext.textBaseline = constants.BASELINE; + } + + return p; +}; + +p5.Renderer2D.prototype._renderText = function(p, line, x, y, maxY) { + + if (y >= maxY) { + return; // don't render lines beyond our maxY position + } + + p.push(); // fix to #803 + + if (!this._isOpenType()) { // a system/browser font + + // no stroke unless specified by user + if (this._doStroke && this._strokeSet) { + + this.drawingContext.strokeText(line, x, y); + } + + if (this._doFill) { + + // if fill hasn't been set by user, use default text fill + this.drawingContext.fillStyle = this._fillSet ? + this.drawingContext.fillStyle : constants._DEFAULT_TEXT_FILL; + + this.drawingContext.fillText(line, x, y); + } + } + else { // an opentype font, let it handle the rendering + + this._textFont._renderPath(line, x, y, { renderer: this }); + } + + p.pop(); + + return p; +}; + +p5.Renderer2D.prototype.textWidth = function(s) { + + if (this._isOpenType()) { + + return this._textFont._textWidth(s); + } + + return this.drawingContext.measureText(s).width; +}; + +p5.Renderer2D.prototype.textAlign = function(h, v) { + + if (arguments.length) { + + if (h === constants.LEFT || + h === constants.RIGHT || + h === constants.CENTER) { + + this.drawingContext.textAlign = h; + } + + if (v === constants.TOP || + v === constants.BOTTOM || + v === constants.CENTER || + v === constants.BASELINE) { + + if (v === constants.CENTER) { + this.drawingContext.textBaseline = constants._CTX_MIDDLE; + } else { + this.drawingContext.textBaseline = v; + } + } + + return this._pInst; + + } else { + + var valign = this.drawingContext.textBaseline; + + if (valign === constants._CTX_MIDDLE) { + + valign = constants.CENTER; + } + + return { + + horizontal: this.drawingContext.textAlign, + vertical: valign + }; + } +}; + +p5.Renderer2D.prototype._applyTextProperties = function() { + + var font, p = this._pInst; + + this._setProperty('_textAscent', null); + this._setProperty('_textDescent', null); + + font = this._textFont; + + if (this._isOpenType()) { + + font = this._textFont.font.familyName; + this._setProperty('_textStyle', this._textFont.font.styleName); + } + + this.drawingContext.font = this._textStyle + ' ' + + this._textSize + 'px ' + font; + + return p; +}; + + +////////////////////////////////////////////// +// STRUCTURE +////////////////////////////////////////////// + +p5.Renderer2D.prototype.push = function() { + this.drawingContext.save(); +}; + +p5.Renderer2D.prototype.pop = function() { + this.drawingContext.restore(); +}; + +module.exports = p5.Renderer2D; + +},{"../image/filters":65,"./canvas":46,"./constants":47,"./core":48,"./p5.Renderer":54}],56:[function(_dereq_,module,exports){ +/** + * @module Rendering + * @submodule Rendering + * @for p5 + */ + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); +_dereq_('./p5.Graphics'); +_dereq_('./p5.Renderer2D'); +_dereq_('../3d/p5.Renderer3D'); +var defaultId = 'defaultCanvas0'; // this gets set again in createCanvas + +/** + * Creates a canvas element in the document, and sets the dimensions of it + * in pixels. This method should be called only once at the start of setup. + * Calling createCanvas more than once in a sketch will result in very + * unpredicable behavior. If you want more than one drawing canvas + * you could use createGraphics (hidden by default but it can be shown). + * <br><br> + * The system variables width and height are set by the parameters passed + * to this function. If createCanvas() is not used, the window will be + * given a default size of 100x100 pixels. + * + * @method createCanvas + * @param {Number} w width of the canvas + * @param {Number} h height of the canvas + * @param {String} [renderer] 'p2d' | 'webgl' + * @return {Object} canvas generated + * @example + * <div> + * <code> + * function setup() { + * createCanvas(100, 50); + * background(153); + * line(0, 0, width, height); + * } + * </code> + * </div> + */ + +p5.prototype.createCanvas = function(w, h, renderer) { + //optional: renderer, otherwise defaults to p2d + var r = renderer || constants.P2D; + var isDefault, c; + + //4th arg (isDefault) used when called onLoad, + //otherwise hidden to the public api + if(arguments[3]){ + isDefault = + (typeof arguments[3] === 'boolean') ? arguments[3] : false; + } + + if(r === constants.WEBGL){ + c = document.getElementById(defaultId); + if(c){ //if defaultCanvas already exists + c.parentNode.removeChild(c); //replace the existing defaultCanvas + } + c = document.createElement('canvas'); + c.id = defaultId; + } + else { + if (isDefault) { + c = document.createElement('canvas'); + var i = 0; + while (document.getElementById('defaultCanvas'+i)) { + i++; + } + defaultId = 'defaultCanvas'+i; + c.id = defaultId; + } else { // resize the default canvas if new one is created + c = this.canvas; + } + } + + // set to invisible if still in setup (to prevent flashing with manipulate) + if (!this._setupDone) { + c.className += ' p5_hidden'; // tag to show later + c.style.visibility='hidden'; + } + + if (this._userNode) { // user input node case + this._userNode.appendChild(c); + } else { + document.body.appendChild(c); + } + + + + // Init our graphics renderer + //webgl mode + if (r === constants.WEBGL) { + this._setProperty('_renderer', new p5.Renderer3D(c, this, true)); + this._isdefaultGraphics = true; + } + //P2D mode + else { + if (!this._isdefaultGraphics) { + this._setProperty('_renderer', new p5.Renderer2D(c, this, true)); + this._isdefaultGraphics = true; + } + } + this._renderer.resize(w, h); + this._renderer._applyDefaults(); + if (isDefault) { // only push once + this._elements.push(this._renderer); + } + return this._renderer; +}; + +/** + * Resizes the canvas to given width and height. The canvas will be cleared + * and draw will be called immediately, allowing the sketch to re-render itself + * in the resized canvas. + * @method resizeCanvas + * @example + * <div class="norender"><code> + * function setup() { + * createCanvas(windowWidth, windowHeight); + * } + * + * function draw() { + * background(0, 100, 200); + * } + * + * function windowResized() { + * resizeCanvas(windowWidth, windowHeight); + * } + * </code></div> + */ +p5.prototype.resizeCanvas = function (w, h, noRedraw) { + if (this._renderer) { + + // save canvas properties + var props = {}; + for (var key in this.drawingContext) { + var val = this.drawingContext[key]; + if (typeof val !== 'object' && typeof val !== 'function') { + props[key] = val; + } + } + this._renderer.resize(w, h); + // reset canvas properties + for (var savedKey in props) { + this.drawingContext[savedKey] = props[savedKey]; + } + if (!noRedraw) { + this.redraw(); + } + } +}; + + +/** + * Removes the default canvas for a p5 sketch that doesn't + * require a canvas + * @method noCanvas + * @example + * <div> + * <code> + * function setup() { + * noCanvas(); + * } + * </code> + * </div> + */ +p5.prototype.noCanvas = function() { + if (this.canvas) { + this.canvas.parentNode.removeChild(this.canvas); + } +}; + +/** + * Creates and returns a new p5.Renderer object. Use this class if you need + * to draw into an off-screen graphics buffer. The two parameters define the + * width and height in pixels. + * + * @method createGraphics + * @param {Number} w width of the offscreen graphics buffer + * @param {Number} h height of the offscreen graphics buffer + * @param {String} renderer either 'p2d' or 'webgl'. + * undefined defaults to p2d + * @return {Object} offscreen graphics buffer + * @example + * <div> + * <code> + * var pg; + * function setup() { + * createCanvas(100, 100); + * pg = createGraphics(100, 100); + * } + * function draw() { + * background(200); + * pg.background(100); + * pg.noStroke(); + * pg.ellipse(pg.width/2, pg.height/2, 50, 50); + * image(pg, 50, 50); + * image(pg, 0, 0, 50, 50); + * } + * </code> + * </div> + */ +p5.prototype.createGraphics = function(w, h, renderer){ + return new p5.Graphics(w, h, renderer, this); +}; + +/** + * Blends the pixels in the display window according to the defined mode. + * There is a choice of the following modes to blend the source pixels (A) + * with the ones of pixels already in the display window (B): + * <ul> + * <li><code>BLEND</code> - linear interpolation of colours: C = + * A*factor + B. This is the default blending mode.</li> + * <li><code>ADD</code> - sum of A and B</li> + * <li><code>DARKEST</code> - only the darkest colour succeeds: C = + * min(A*factor, B).</li> + * <li><code>LIGHTEST</code> - only the lightest colour succeeds: C = + * max(A*factor, B).</li> + * <li><code>DIFFERENCE</code> - subtract colors from underlying image.</li> + * <li><code>EXCLUSION</code> - similar to <code>DIFFERENCE</code>, but less + * extreme.</li> + * <li><code>MULTIPLY</code> - multiply the colors, result will always be + * darker.</li> + * <li><code>SCREEN</code> - opposite multiply, uses inverse values of the + * colors.</li> + * <li><code>REPLACE</code> - the pixels entirely replace the others and + * don't utilize alpha (transparency) values.</li> + * <li><code>OVERLAY</code> - mix of <code>MULTIPLY</code> and <code>SCREEN + * </code>. Multiplies dark values, and screens light values.</li> + * <li><code>HARD_LIGHT</code> - <code>SCREEN</code> when greater than 50% + * gray, <code>MULTIPLY</code> when lower.</li> + * <li><code>SOFT_LIGHT</code> - mix of <code>DARKEST</code> and + * <code>LIGHTEST</code>. Works like <code>OVERLAY</code>, but not as harsh. + * </li> + * <li><code>DODGE</code> - lightens light tones and increases contrast, + * ignores darks.</li> + * <li><code>BURN</code> - darker areas are applied, increasing contrast, + * ignores lights.</li> + * </ul> + * + * @method blendMode + * @param {String/Constant} mode blend mode to set for canvas + * @example + * <div> + * <code> + * blendMode(LIGHTEST); + * strokeWeight(30); + * stroke(80, 150, 255); + * line(25, 25, 75, 75); + * stroke(255, 50, 50); + * line(75, 25, 25, 75); + * </code> + * </div> + * <div> + * <code> + * blendMode(MULTIPLY); + * strokeWeight(30); + * stroke(80, 150, 255); + * line(25, 25, 75, 75); + * stroke(255, 50, 50); + * line(75, 25, 25, 75); + * </code> + * </div> + */ +p5.prototype.blendMode = function(mode) { + if (mode === constants.BLEND || mode === constants.DARKEST || + mode === constants.LIGHTEST || mode === constants.DIFFERENCE || + mode === constants.MULTIPLY || mode === constants.EXCLUSION || + mode === constants.SCREEN || mode === constants.REPLACE || + mode === constants.OVERLAY || mode === constants.HARD_LIGHT || + mode === constants.SOFT_LIGHT || mode === constants.DODGE || + mode === constants.BURN || mode === constants.ADD || + mode === constants.NORMAL) { + this._renderer.blendMode(mode); + } else { + throw new Error('Mode '+mode+' not recognized.'); + } +}; + +module.exports = p5; + +},{"../3d/p5.Renderer3D":36,"./constants":47,"./core":48,"./p5.Graphics":53,"./p5.Renderer2D":55}],57:[function(_dereq_,module,exports){ + +// requestAnim shim layer by Paul Irish +window.requestAnimationFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback, element){ + // should '60' here be framerate? + window.setTimeout(callback, 1000 / 60); + }; +})(); + +// use window.performance() to get max fast and accurate time in milliseconds +window.performance = window.performance || {}; +window.performance.now = (function(){ + var load_date = Date.now(); + return window.performance.now || + window.performance.mozNow || + window.performance.msNow || + window.performance.oNow || + window.performance.webkitNow || + function () { + return Date.now() - load_date; + }; +})(); + +/* +// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +// http://my.opera.com/emoller/blog/2011/12/20/ +// requestanimationframe-for-smart-er-animating +// requestAnimationFrame polyfill by Erik Möller +// fixes from Paul Irish and Tino Zijdel +(function() { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = + window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = + window[vendors[x]+'CancelAnimationFrame'] || + window[vendors[x]+'CancelRequestAnimationFrame']; + } + + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() + { callback(currTime + timeToCall); }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + } + + if (!window.cancelAnimationFrame) { + window.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; + } +}()); +*/ + +/** + * shim for Uint8ClampedArray.slice + * (allows arrayCopy to work with pixels[]) + * with thanks to http://halfpapstudios.com/blog/tag/html5-canvas/ + * Enumerable set to false to protect for...in from + * Uint8ClampedArray.prototype pollution. + */ +(function () { + 'use strict'; + if (typeof Uint8ClampedArray !== 'undefined' && + !Uint8ClampedArray.prototype.slice) { + Object.defineProperty(Uint8ClampedArray.prototype, 'slice', { + value: Array.prototype.slice, + writable: true, configurable: true, enumerable: false + }); + } +}()); + +},{}],58:[function(_dereq_,module,exports){ +/** + * @module Structure + * @submodule Structure + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('./core'); + +p5.prototype.exit = function() { + throw 'exit() not implemented, see remove()'; +}; +/** + * Stops p5.js from continuously executing the code within draw(). + * If loop() is called, the code in draw() begins to run continuously again. + * If using noLoop() in setup(), it should be the last line inside the block. + * <br><br> + * 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. + * <br><br> + * 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. + * + * @method noLoop + * @example + * <div><code> + * function setup() { + * createCanvas(100, 100); + * background(200); + * noLoop(); + * } + + * function draw() { + * line(10, 10, 90, 90); + * } + * </code></div> + * + * <div><code> + * var x = 0; + * function setup() { + * createCanvas(100, 100); + * } + * + * function draw() { + * background(204); + * x = x + 0.1; + * if (x > width) { + * x = 0; + * } + * line(x, 0, x, height); + * } + * + * function mousePressed() { + * noLoop(); + * } + * + * function mouseReleased() { + * loop(); + * } + * </code></div> + */ +p5.prototype.noLoop = function() { + this._loop = false; +}; +/** + * By default, p5.js loops through draw() continuously, executing the code + * within it. However, the draw() loop may be stopped by calling noLoop(). + * In that case, the draw() loop can be resumed with loop(). + * + * @method loop + * @example + * <div><code> + * var x = 0; + * function setup() { + * createCanvas(100, 100); + * noLoop(); + * } + * + * function draw() { + * background(204); + * x = x + 0.1; + * if (x > width) { + * x = 0; + * } + * line(x, 0, x, height); + * } + * + * function mousePressed() { + * loop(); + * } + * + * function mouseReleased() { + * noLoop(); + * } + * </code></div> + */ + +p5.prototype.loop = function() { + this._loop = true; + this._draw(); +}; + +/** + * The push() function saves the current drawing style settings and + * transformations, while pop() restores these settings. Note that these + * functions are always used together. They allow you to change the style + * and transformation settings and later return to what you had. When a new + * state is started with push(), it builds on the current style and transform + * information. The push() and pop() functions can be embedded to provide + * more control. (See the second example for a demonstration.) + * <br><br> + * push() stores information related to the current transformation state + * and style settings controlled by the following functions: fill(), + * stroke(), tint(), strokeWeight(), strokeCap(), strokeJoin(), + * imageMode(), rectMode(), ellipseMode(), colorMode(), textAlign(), + * textFont(), textMode(), textSize(), textLeading(). + * + * @method push + * @example + * <div> + * <code> + * ellipse(0, 50, 33, 33); // Left circle + * + * push(); // Start a new drawing state + * strokeWeight(10); + * fill(204, 153, 0); + * translate(50, 0); + * ellipse(0, 50, 33, 33); // Middle circle + * pop(); // Restore original state + * + * ellipse(100, 50, 33, 33); // Right circle + * </code> + * </div> + * <div> + * <code> + * ellipse(0, 50, 33, 33); // Left circle + * + * push(); // Start a new drawing state + * strokeWeight(10); + * fill(204, 153, 0); + * ellipse(33, 50, 33, 33); // Left-middle circle + * + * push(); // Start another new drawing state + * stroke(0, 102, 153); + * ellipse(66, 50, 33, 33); // Right-middle circle + * pop(); // Restore previous state + * + * pop(); // Restore original state + * + * ellipse(100, 50, 33, 33); // Right circle + * </code> + * </div> + */ +p5.prototype.push = function () { + this._renderer.push(); + this._styles.push({ + _doStroke: this._renderer._doStroke, + _doFill: this._renderer._doFill, + _tint: this._renderer._tint, + _imageMode: this._renderer._imageMode, + _rectMode: this._renderer._rectMode, + _ellipseMode: this._renderer._ellipseMode, + _colorMode: this._renderer._colorMode, + _textFont: this._renderer._textFont, + _textLeading: this._renderer._textLeading, + _textSize: this._renderer._textSize, + _textStyle: this._renderer._textStyle + }); +}; + +/** + * The push() function saves the current drawing style settings and + * transformations, while pop() restores these settings. Note that these + * functions are always used together. They allow you to change the style + * and transformation settings and later return to what you had. When a new + * state is started with push(), it builds on the current style and transform + * information. The push() and pop() functions can be embedded to provide + * more control. (See the second example for a demonstration.) + * <br><br> + * push() stores information related to the current transformation state + * and style settings controlled by the following functions: fill(), + * stroke(), tint(), strokeWeight(), strokeCap(), strokeJoin(), + * imageMode(), rectMode(), ellipseMode(), colorMode(), textAlign(), + * textFont(), textMode(), textSize(), textLeading(). + * + * @method pop + * @example + * <div> + * <code> + * ellipse(0, 50, 33, 33); // Left circle + * + * push(); // Start a new drawing state + * translate(50, 0); + * strokeWeight(10); + * fill(204, 153, 0); + * ellipse(0, 50, 33, 33); // Middle circle + * pop(); // Restore original state + * + * ellipse(100, 50, 33, 33); // Right circle + * </code> + * </div> + * <div> + * <code> + * ellipse(0, 50, 33, 33); // Left circle + * + * push(); // Start a new drawing state + * strokeWeight(10); + * fill(204, 153, 0); + * ellipse(33, 50, 33, 33); // Left-middle circle + * + * push(); // Start another new drawing state + * stroke(0, 102, 153); + * ellipse(66, 50, 33, 33); // Right-middle circle + * pop(); // Restore previous state + * + * pop(); // Restore original state + * + * ellipse(100, 50, 33, 33); // Right circle + * </code> + * </div> + */ +p5.prototype.pop = function () { + this._renderer.pop(); + var lastS = this._styles.pop(); + for(var prop in lastS){ + this._renderer[prop] = lastS[prop]; + } +}; + +p5.prototype.pushStyle = function() { + throw new Error('pushStyle() not used, see push()'); +}; + +p5.prototype.popStyle = function() { + throw new Error('popStyle() not used, see pop()'); +}; + +/** + * + * 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. + * <br><br> + * 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). + * <br><br> + * The redraw() function does not work properly when called inside draw(). + * To enable/disable animations, use loop() and noLoop(). + * + * @method redraw + * @example + * <div><code> + * var x = 0; + * + * function setup() { + * createCanvas(100, 100); + * noLoop(); + * } + * + * function draw() { + * background(204); + * line(x, 0, x, height); + * } + * + * function mousePressed() { + * x += 1; + * redraw(); + * } + * </code></div> + */ +p5.prototype.redraw = function () { + var userSetup = this.setup || window.setup; + var userDraw = this.draw || window.draw; + if (typeof userDraw === 'function') { + this.push(); + if (typeof userSetup === 'undefined') { + this.scale(this._pixelDensity, this._pixelDensity); + } + var self = this; + this._registeredMethods.pre.forEach(function (f) { + f.call(self); + }); + userDraw(); + this._registeredMethods.post.forEach(function (f) { + f.call(self); + }); + this.pop(); + } +}; + +p5.prototype.size = function() { + var s = 'size() is not a valid p5 function, to set the size of the '; + s += 'drawing canvas, please use createCanvas() instead'; + throw s; +}; + + +module.exports = p5; + +},{"./core":48}],59:[function(_dereq_,module,exports){ +/** + * @module Transform + * @submodule Transform + * @for p5 + * @requires core + * @requires constants + */ + + +'use strict'; + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); + +/** + * 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. + * + * @method applyMatrix + * @param {Number} n00 numbers which define the 3x2 matrix to be multiplied + * @param {Number} n01 numbers which define the 3x2 matrix to be multiplied + * @param {Number} n02 numbers which define the 3x2 matrix to be multiplied + * @param {Number} n10 numbers which define the 3x2 matrix to be multiplied + * @param {Number} n11 numbers which define the 3x2 matrix to be multiplied + * @param {Number} n12 numbers which define the 3x2 matrix to be multiplied + * @return {p5} the p5 object + * @example + * <div> + * <code> + * // Example in the works. + * </code> + * </div> + */ +p5.prototype.applyMatrix = function(n00, n01, n02, n10, n11, n12) { + this._renderer.applyMatrix(n00, n01, n02, n10, n11, n12); + return this; +}; + +p5.prototype.popMatrix = function() { + throw new Error('popMatrix() not used, see pop()'); +}; + +p5.prototype.printMatrix = function() { + throw new Error('printMatrix() not implemented'); +}; + +p5.prototype.pushMatrix = function() { + throw new Error('pushMatrix() not used, see push()'); +}; + +/** + * Replaces the current matrix with the identity matrix. + * + * @method resetMatrix + * @return {p5} the p5 object + * @example + * <div> + * <code> + * // Example in the works. + * </code> + * </div> + */ +p5.prototype.resetMatrix = function() { + this._renderer.resetMatrix(); + return this; +}; + +/** + * Rotates a shape the amount specified by the angle parameter. This + * function accounts for angleMode, so angles can be entered in either + * RADIANS or DEGREES. + * <br><br> + * 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. + * <br><br> + * Technically, rotate() multiplies the current transformation matrix + * by a rotation matrix. This function can be further controlled by + * the push() and pop(). + * + * @method rotate + * @param {Number} angle the angle of rotation, specified in radians + * or degrees, depending on current angleMode + * @return {p5} the p5 object + * @example + * <div> + * <code> + * translate(width/2, height/2); + * rotate(PI/3.0); + * rect(-26, -26, 52, 52); + * </code> + * </div> + */ +p5.prototype.rotate = function() { + var r = arguments[0]; + if (this._angleMode === constants.DEGREES) { + r = this.radians(r); + } + //in webgl mode + if(arguments.length > 1){ + this._renderer.rotate(r, arguments[1]); + } + else { + this._renderer.rotate(r); + } + return this; +}; + +/** + * [rotateX description] + * @param {[type]} rad [description] + * @return {[type]} [description] + */ +p5.prototype.rotateX = function(rad) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + if (this._renderer.isP3D) { + this._validateParameters( + 'rotateX', + args, + [ + ['Number'] + ] + ); + this._renderer.rotateX(rad); + } else { + throw 'not supported in p2d. Please use webgl mode'; + } + return this; +}; + +/** + * [rotateY description] + * @param {[type]} rad [description] + * @return {[type]} [description] + */ +p5.prototype.rotateY = function(rad) { + if (this._renderer.isP3D) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'rotateY', + args, + [ + ['Number'] + ] + ); + this._renderer.rotateY(rad); + } else { + throw 'not supported in p2d. Please use webgl mode'; + } + return this; +}; + +/** + * [rotateZ description] + * @param {[type]} rad [description] + * @return {[type]} [description] + */ +p5.prototype.rotateZ = function(rad) { + if (this._renderer.isP3D) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'rotateZ', + args, + [ + ['Number'] + ] + ); + this._renderer.rotateZ(rad); + } else { + throw 'not supported in p2d. Please use webgl mode'; + } + return this; +}; + +/** + * 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%. + * <br><br> + * 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. + * <br><br> + * Using this fuction with the z parameter requires using P3D as a + * parameter for size(), as shown in the third example above. This function + * can be further controlled with push() and pop(). + * + * @method scale + * @param {Number | p5.Vector | Array} s + * percent to scale the object, or percentage to + * scale the object in the x-axis if multiple arguments + * are given + * @param {Number} [y] percent to scale the object in the y-axis + * @param {Number} [z] percent to scale the object in the z-axis (webgl only) + * @return {p5} the p5 object + * @example + * <div> + * <code> + * translate(width/2, height/2); + * rotate(PI/3.0); + * rect(-26, -26, 52, 52); + * </code> + * </div> + * + * <div> + * <code> + * rect(30, 20, 50, 50); + * scale(0.5, 1.3); + * rect(30, 20, 50, 50); + * </code> + * </div> + */ +p5.prototype.scale = function() { + var x,y,z; + var args = new Array(arguments.length); + for(var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + if(args[0] instanceof p5.Vector){ + x = args[0].x; + y = args[0].y; + z = args[0].z; + } + else if(args[0] instanceof Array){ + x = args[0][0]; + y = args[0][1]; + z = args[0][2] || 1; + } + else { + if(args.length === 1){ + x = y = z = args[0]; + } + else { + x = args[0]; + y = args[1]; + z = args[2] || 1; + } + } + + if(this._renderer.isP3D){ + this._renderer.scale.call(this._renderer, x,y,z); + } + else { + this._renderer.scale.call(this._renderer, x,y); + } + return this; +}; + +/** + * Shears a shape around the x-axis the amount specified by the angle + * parameter. Angles should be specified in the current angleMode. + * Objects are always sheared around their relative position to the origin + * and positive numbers shear objects in a clockwise direction. + * <br><br> + * Transformations apply to everything that happens after and subsequent + * calls to the function accumulates the effect. For example, calling + * shearX(PI/2) and then shearX(PI/2) is the same as shearX(PI). + * If shearX() is called within the draw(), the transformation is reset when + * the loop begins again. + * <br><br> + * Technically, shearX() multiplies the current transformation matrix by a + * rotation matrix. This function can be further controlled by the + * push() and pop() functions. + * + * @method shearX + * @param {Number} angle angle of shear specified in radians or degrees, + * depending on current angleMode + * @return {p5} the p5 object + * @example + * <div> + * <code> + * translate(width/4, height/4); + * shearX(PI/4.0); + * rect(0, 0, 30, 30); + * </code> + * </div> + */ +p5.prototype.shearX = function(angle) { + if (this._angleMode === constants.DEGREES) { + angle = this.radians(angle); + } + this._renderer.shearX(angle); + return this; +}; + +/** + * Shears a shape around the y-axis the amount specified by the angle + * parameter. Angles should be specified in the current angleMode. Objects + * are always sheared around their relative position to the origin and + * positive numbers shear objects in a clockwise direction. + * <br><br> + * Transformations apply to everything that happens after and subsequent + * calls to the function accumulates the effect. For example, calling + * shearY(PI/2) and then shearY(PI/2) is the same as shearY(PI). If + * shearY() is called within the draw(), the transformation is reset when + * the loop begins again. + * <br><br> + * Technically, shearY() multiplies the current transformation matrix by a + * rotation matrix. This function can be further controlled by the + * push() and pop() functions. + * + * @method shearY + * @param {Number} angle angle of shear specified in radians or degrees, + * depending on current angleMode + * @return {p5} the p5 object + * @example + * <div> + * <code> + * translate(width/4, height/4); + * shearY(PI/4.0); + * rect(0, 0, 30, 30); + * </code> + * </div> + */ +p5.prototype.shearY = function(angle) { + if (this._angleMode === constants.DEGREES) { + angle = this.radians(angle); + } + this._renderer.shearY(angle); + return this; +}; + +/** + * Specifies an amount to displace objects within the display window. + * The x parameter specifies left/right translation, the y parameter + * specifies up/down translation. + * <br><br> + * Transformations are cumulative and 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 using push() and pop(). + * + * @method translate + * @param {Number} x left/right translation + * @param {Number} y up/down translation + * @return {p5} the p5 object + * @example + * <div> + * <code> + * translate(30, 20); + * rect(0, 0, 55, 55); + * </code> + * </div> + * + * <div> + * <code> + * rect(0, 0, 55, 55); // Draw rect at original 0,0 + * translate(30, 20); + * rect(0, 0, 55, 55); // Draw rect at new 0,0 + * translate(14, 14); + * rect(0, 0, 55, 55); // Draw rect at new 0,0 + * </code> + * </div> + */ +p5.prototype.translate = function(x, y, z) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + + if (this._renderer.isP3D) { + this._validateParameters( + 'translate', + args, + [ + //p3d + ['Number', 'Number', 'Number'] + ] + ); + this._renderer.translate(x, y, z); + } else { + this._validateParameters( + 'translate', + args, + [ + //p2d + ['Number', 'Number'] + ] + ); + this._renderer.translate(x, y); + } + return this; +}; + +module.exports = p5; + +},{"./constants":47,"./core":48}],60:[function(_dereq_,module,exports){ +/** + * @module Shape + * @submodule Vertex + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); +var shapeKind = null; +var vertices = []; +var contourVertices = []; +var isBezier = false; +var isCurve = false; +var isQuadratic = false; +var isContour = false; + +/** + * Use the beginContour() and endContour() functions to create negative + * shapes within shapes such as the center of the letter 'O'. beginContour() + * begins recording vertices for the shape and endContour() stops recording. + * The vertices that define a negative shape must "wind" in the opposite + * direction from the exterior shape. First draw vertices for the exterior + * clockwise order, then for internal shapes, draw vertices + * shape in counter-clockwise. + * <br><br> + * These functions can only be used within a beginShape()/endShape() pair and + * transformations such as translate(), rotate(), and scale() do not work + * within a beginContour()/endContour() pair. It is also not possible to use + * other shapes, such as ellipse() or rect() within. + * + * @method beginContour + * @return {Object} the p5 object + * @example + * <div> + * <code> + * translate(50, 50); + * stroke(255, 0, 0); + * beginShape(); + * // Exterior part of shape, clockwise winding + * vertex(-40, -40); + * vertex(40, -40); + * vertex(40, 40); + * vertex(-40, 40); + * // Interior part of shape, counter-clockwise winding + * beginContour(); + * vertex(-20, -20); + * vertex(-20, 20); + * vertex(20, 20); + * vertex(20, -20); + * endContour(); + * endShape(CLOSE); + * </code> + * </div> + */ +p5.prototype.beginContour = function() { + contourVertices = []; + isContour = true; + return this; +}; + +/** + * Using the beginShape() and endShape() functions allow creating more + * complex forms. beginShape() begins recording vertices for a shape and + * endShape() stops recording. The value of the kind parameter tells it which + * types of shapes to create from the provided vertices. With no mode + * specified, the shape can be any irregular polygon. + * <br><br> + * The parameters available for beginShape() are POINTS, LINES, TRIANGLES, + * TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, and QUAD_STRIP. After calling the + * beginShape() function, a series of vertex() commands must follow. To stop + * drawing the shape, call endShape(). Each shape will be outlined with the + * current stroke color and filled with the fill color. + * <br><br> + * Transformations such as translate(), rotate(), and scale() do not work + * within beginShape(). It is also not possible to use other shapes, such as + * ellipse() or rect() within beginShape(). + * + * @method beginShape + * @param {Number/Constant} kind either POINTS, LINES, TRIANGLES, + * TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, + * or QUAD_STRIP + * @return {Object} the p5 object + * @example + * <div> + * <code> + * beginShape(); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(CLOSE); + * </code> + * </div> + * + * <div> + * <code> + * // currently not working + * beginShape(POINTS); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(LINES); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * beginShape(); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * beginShape(); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(CLOSE); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(TRIANGLES); + * vertex(30, 75); + * vertex(40, 20); + * vertex(50, 75); + * vertex(60, 20); + * vertex(70, 75); + * vertex(80, 20); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(TRIANGLE_STRIP); + * vertex(30, 75); + * vertex(40, 20); + * vertex(50, 75); + * vertex(60, 20); + * vertex(70, 75); + * vertex(80, 20); + * vertex(90, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(TRIANGLE_FAN); + * vertex(57.5, 50); + * vertex(57.5, 15); + * vertex(92, 50); + * vertex(57.5, 85); + * vertex(22, 50); + * vertex(57.5, 15); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(QUADS); + * vertex(30, 20); + * vertex(30, 75); + * vertex(50, 75); + * vertex(50, 20); + * vertex(65, 20); + * vertex(65, 75); + * vertex(85, 75); + * vertex(85, 20); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(QUAD_STRIP); + * vertex(30, 20); + * vertex(30, 75); + * vertex(50, 20); + * vertex(50, 75); + * vertex(65, 20); + * vertex(65, 75); + * vertex(85, 20); + * vertex(85, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(); + * vertex(20, 20); + * vertex(40, 20); + * vertex(40, 40); + * vertex(60, 40); + * vertex(60, 60); + * vertex(20, 60); + * endShape(CLOSE); + * </code> + * </div> + */ +p5.prototype.beginShape = function(kind) { + if (kind === constants.POINTS || + kind === constants.LINES || + kind === constants.TRIANGLES || + kind === constants.TRIANGLE_FAN || + kind === constants.TRIANGLE_STRIP || + kind === constants.QUADS || + kind === constants.QUAD_STRIP) { + shapeKind = kind; + } else { + shapeKind = null; + } + if(this._renderer.isP3D){ + this._renderer.beginShape(kind); + } else { + vertices = []; + contourVertices = []; + } + return this; +}; + +/** + * Specifies vertex coordinates for Bezier curves. Each call to + * bezierVertex() defines the position of two control points and + * one anchor point of a Bezier curve, adding a new segment to a + * line or shape. + * <br><br> + * The first time bezierVertex() is used within a + * beginShape() call, it must be prefaced with a call to vertex() + * to set the first anchor point. This function must be used between + * beginShape() and endShape() and only when there is no MODE + * parameter specified to beginShape(). + * + * @method bezierVertex + * @param {Number} x2 x-coordinate for the first control point + * @param {Number} y2 y-coordinate for the first control point + * @param {Number} x3 x-coordinate for the second control point + * @param {Number} y3 y-coordinate for the second control point + * @param {Number} x4 x-coordinate for the anchor point + * @param {Number} y4 y-coordinate for the anchor point + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * beginShape(); + * vertex(30, 20); + * bezierVertex(80, 0, 80, 75, 30, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(); + * vertex(30, 20); + * bezierVertex(80, 0, 80, 75, 30, 75); + * bezierVertex(50, 80, 60, 25, 30, 20); + * endShape(); + * </code> + * </div> + */ +p5.prototype.bezierVertex = function(x2, y2, x3, y3, x4, y4) { + if (vertices.length === 0) { + throw 'vertex() must be used once before calling bezierVertex()'; + } else { + isBezier = true; + var vert = []; + for (var i = 0; i < arguments.length; i++) { + vert[i] = arguments[i]; + } + vert.isVert = false; + if (isContour) { + contourVertices.push(vert); + } else { + vertices.push(vert); + } + } + return this; +}; + +/** + * Specifies vertex coordinates for curves. This function may only + * be used between beginShape() and endShape() and only when there + * is no MODE parameter specified to beginShape(). + * <br><br> + * The first and last points in a series of curveVertex() lines will be used to + * guide the beginning and end of a the curve. A minimum of four + * points is required to draw a tiny curve between the second and + * third points. Adding a fifth point with curveVertex() will draw + * the curve between the second, third, and fourth points. The + * curveVertex() function is an implementation of Catmull-Rom + * splines. + * + * @method curveVertex + * @param {Number} x x-coordinate of the vertex + * @param {Number} y y-coordinate of the vertex + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * beginShape(); + * curveVertex(84, 91); + * curveVertex(84, 91); + * curveVertex(68, 19); + * curveVertex(21, 17); + * curveVertex(32, 100); + * curveVertex(32, 100); + * endShape(); + * </code> + * </div> + */ +p5.prototype.curveVertex = function(x,y) { + isCurve = true; + this.vertex(x, y); + return this; +}; + +/** + * Use the beginContour() and endContour() functions to create negative + * shapes within shapes such as the center of the letter 'O'. beginContour() + * begins recording vertices for the shape and endContour() stops recording. + * The vertices that define a negative shape must "wind" in the opposite + * direction from the exterior shape. First draw vertices for the exterior + * clockwise order, then for internal shapes, draw vertices + * shape in counter-clockwise. + * <br><br> + * These functions can only be used within a beginShape()/endShape() pair and + * transformations such as translate(), rotate(), and scale() do not work + * within a beginContour()/endContour() pair. It is also not possible to use + * other shapes, such as ellipse() or rect() within. + * + * @method endContour + * @return {Object} the p5 object + * @example + * <div> + * <code> + * translate(50, 50); + * stroke(255, 0, 0); + * beginShape(); + * // Exterior part of shape, clockwise winding + * vertex(-40, -40); + * vertex(40, -40); + * vertex(40, 40); + * vertex(-40, 40); + * // Interior part of shape, counter-clockwise winding + * beginContour(); + * vertex(-20, -20); + * vertex(-20, 20); + * vertex(20, 20); + * vertex(20, -20); + * endContour(); + * endShape(CLOSE); + * </code> + * </div> + */ +p5.prototype.endContour = function() { + var vert = contourVertices[0].slice(); // copy all data + vert.isVert = contourVertices[0].isVert; + vert.moveTo = false; + contourVertices.push(vert); + + vertices.push(vertices[0]); + for (var i = 0; i < contourVertices.length; i++) { + vertices.push(contourVertices[i]); + } + return this; +}; + +/** + * The endShape() function is the companion to beginShape() and may only be + * called after beginShape(). When endshape() is called, all of image data + * defined since the previous call to beginShape() is written into the image + * buffer. The constant CLOSE as the value for the MODE parameter to close + * the shape (to connect the beginning and the end). + * + * @method endShape + * @param {Number/Constant} mode use CLOSE to close the shape + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * + * beginShape(); + * vertex(20, 20); + * vertex(45, 20); + * vertex(45, 80); + * endShape(CLOSE); + * + * beginShape(); + * vertex(50, 20); + * vertex(75, 20); + * vertex(75, 80); + * endShape(); + * </code> + * </div> + */ +p5.prototype.endShape = function(mode) { + if(this._renderer.isP3D){ + this._renderer.endShape(); + }else{ + if (vertices.length === 0) { return this; } + if (!this._renderer._doStroke && !this._renderer._doFill) { return this; } + + var closeShape = mode === constants.CLOSE; + + // if the shape is closed, the first element is also the last element + if (closeShape && !isContour) { + vertices.push(vertices[0]); + } + + this._renderer.endShape(mode, vertices, isCurve, isBezier, + isQuadratic, isContour, shapeKind); + + // Reset some settings + isCurve = false; + isBezier = false; + isQuadratic = false; + isContour = false; + + // If the shape is closed, the first element was added as last element. + // We must remove it again to prevent the list of vertices from growing + // over successive calls to endShape(CLOSE) + if (closeShape) { + vertices.pop(); + } + } + return this; +}; + +/** + * Specifies vertex coordinates for quadratic Bezier curves. Each call to + * quadraticVertex() defines the position of one control points and one + * anchor point of a Bezier curve, adding a new segment to a line or shape. + * The first time quadraticVertex() is used within a beginShape() call, it + * must be prefaced with a call to vertex() to set the first anchor point. + * This function must be used between beginShape() and endShape() and only + * when there is no MODE parameter specified to beginShape(). + * + * @method quadraticVertex + * @param {Number} cx x-coordinate for the control point + * @param {Number} cy y-coordinate for the control point + * @param {Number} x3 x-coordinate for the anchor point + * @param {Number} y3 y-coordinate for the anchor point + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * strokeWeight(4); + * beginShape(); + * vertex(20, 20); + * quadraticVertex(80, 20, 50, 50); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * strokeWeight(4); + * beginShape(); + * vertex(20, 20); + * quadraticVertex(80, 20, 50, 50); + * quadraticVertex(20, 80, 80, 80); + * vertex(80, 60); + * endShape(); + * </code> + * </div> + */ +p5.prototype.quadraticVertex = function(cx, cy, x3, y3) { + //if we're drawing a contour, put the points into an + // array for inside drawing + if(this._contourInited) { + var pt = {}; + pt.x = cx; + pt.y = cy; + pt.x3 = x3; + pt.y3 = y3; + pt.type = constants.QUADRATIC; + this._contourVertices.push(pt); + + return this; + } + if (vertices.length > 0) { + isQuadratic = true; + var vert = []; + for (var i = 0; i < arguments.length; i++) { + vert[i] = arguments[i]; + } + vert.isVert = false; + if (isContour) { + contourVertices.push(vert); + } else { + vertices.push(vert); + } + } else { + throw 'vertex() must be used once before calling quadraticVertex()'; + } + return this; +}; + +/** + * All shapes are constructed by connecting a series of vertices. vertex() + * is used to specify the vertex coordinates for points, lines, triangles, + * quads, and polygons. It is used exclusively within the beginShape() and + * endShape() functions. + * + * @method vertex + * @param {Number} x x-coordinate of the vertex + * @param {Number} y y-coordinate of the vertex + * @return {Object} the p5 object + * @example + * <div> + * <code> + * beginShape(POINTS); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(); + * </code> + * </div> + */ +p5.prototype.vertex = function(x, y, moveTo) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + if(this._renderer.isP3D){ + this._validateParameters( + 'vertex', + args, + [ + ['Number', 'Number', 'Number'] + ] + ); + this._renderer.vertex + (arguments[0], arguments[1], arguments[2]); + }else{ + this._validateParameters( + 'vertex', + args, + [ + ['Number', 'Number'], + ['Number', 'Number', 'Number'] + ] + ); + var vert = []; + vert.isVert = true; + vert[0] = x; + vert[1] = y; + vert[2] = 0; + vert[3] = 0; + vert[4] = 0; + vert[5] = this._renderer._getFill(); + vert[6] = this._renderer._getStroke(); + + if (moveTo) { + vert.moveTo = moveTo; + } + if (isContour) { + if (contourVertices.length === 0) { + vert.moveTo = true; + } + contourVertices.push(vert); + } else { + vertices.push(vert); + } + } + return this; +}; + +module.exports = p5; + +},{"./constants":47,"./core":48}],61:[function(_dereq_,module,exports){ +/** + * @module Events + * @submodule Acceleration + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * The system variable deviceOrientation always contains the orientation of + * the device. The value of this variable will either be set 'landscape' + * or 'portrait'. If no data is available it will be set to 'undefined'. + * + * @property deviceOrientation + */ +p5.prototype.deviceOrientation = undefined; + +/** + * The system variable accelerationX always contains the acceleration of the + * device along the x axis. Value is represented as meters per second squared. + * + * @property accelerationX + */ +p5.prototype.accelerationX = 0; + +/** + * The system variable accelerationY always contains the acceleration of the + * device along the y axis. Value is represented as meters per second squared. + * + * @property accelerationY + */ +p5.prototype.accelerationY = 0; + +/** + * The system variable accelerationZ always contains the acceleration of the + * device along the z axis. Value is represented as meters per second squared. + * + * @property accelerationZ + */ +p5.prototype.accelerationZ = 0; + +/** + * The system variable pAccelerationX always contains the acceleration of the + * device along the x axis in the frame previous to the current frame. Value + * is represented as meters per second squared. + * + * @property pAccelerationX + */ +p5.prototype.pAccelerationX = 0; + +/** + * The system variable pAccelerationY always contains the acceleration of the + * device along the y axis in the frame previous to the current frame. Value + * is represented as meters per second squared. + * + * @property pAccelerationY + */ +p5.prototype.pAccelerationY = 0; + +/** + * The system variable pAccelerationZ always contains the acceleration of the + * device along the z axis in the frame previous to the current frame. Value + * is represented as meters per second squared. + * + * @property pAccelerationZ + */ +p5.prototype.pAccelerationZ = 0; + +/** + * _updatePAccelerations updates the pAcceleration values + * + * @private + */ +p5.prototype._updatePAccelerations = function(){ + this._setProperty('pAccelerationX', this.accelerationX); + this._setProperty('pAccelerationY', this.accelerationY); + this._setProperty('pAccelerationZ', this.accelerationZ); +}; + +/** + * The system variable rotationX always contains the rotation of the + * device along the x axis. Value is represented as 0 to +/-180 degrees. + * <br><br> + * Note: The order the rotations are called is important, ie. if used + * together, it must be called in the order Z-X-Y or there might be + * unexpected behaviour. + * + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * //rotateZ(radians(rotationZ)); + * rotateX(radians(rotationX)); + * //rotateY(radians(rotationY)); + * box(200, 200, 200); + * } + * </code> + * </div> + * + * @property rotationX + */ +p5.prototype.rotationX = 0; + +/** + * The system variable rotationY always contains the rotation of the + * device along the y axis. Value is represented as 0 to +/-90 degrees. + * <br><br> + * Note: The order the rotations are called is important, ie. if used + * together, it must be called in the order Z-X-Y or there might be + * unexpected behaviour. + * + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * //rotateZ(radians(rotationZ)); + * //rotateX(radians(rotationX)); + * rotateY(radians(rotationY)); + * box(200, 200, 200); + * } + * </code> + * </div> + * + * @property rotationY + */ +p5.prototype.rotationY = 0; + +/** + * The system variable rotationZ always contains the rotation of the + * device along the z axis. Value is represented as 0 to 359 degrees. + * <br><br> + * Unlike rotationX and rotationY, this variable is available for devices + * with a built-in compass only. + * <br><br> + * Note: The order the rotations are called is important, ie. if used + * together, it must be called in the order Z-X-Y or there might be + * unexpected behaviour. + * + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * rotateZ(radians(rotationZ)); + * //rotateX(radians(rotationX)); + * //rotateY(radians(rotationY)); + * box(200, 200, 200); + * } + * </code> + * </div> + * + * @property rotationZ + */ +p5.prototype.rotationZ = 0; + +/** + * The system variable pRotationX always contains the rotation of the + * device along the x axis in the frame previous to the current frame. Value + * is represented as 0 to +/-180 degrees. + * <br><br> + * pRotationX can also be used with rotationX to determine the rotate + * direction of the device along the X-axis. + * @example + * <div class='norender'> + * <code> + * // A simple if statement looking at whether + * // rotationX - pRotationX < 0 is true or not will be + * // sufficient for determining the rotate direction + * // in most cases. + * + * // Some extra logic is needed to account for cases where + * // the angles wrap around. + * var rotateDirection = 'clockwise'; + * + * // Simple range conversion to make things simpler. + * // This is not absolutely neccessary but the logic + * // will be different in that case. + * + * var rX = rotationX + 180; + * var pRX = pRotationX + 180; + * + * if ((rX - pRX > 0 && rX - pRX < 270)|| rX - pRX < -270){ + * rotateDirection = 'clockwise'; + * } else if (rX - pRX < 0 || rX - pRX > 270){ + * rotateDirection = 'counter-clockwise'; + * } + * </code> + * </div> + * + * @property pRotationX + */ +p5.prototype.pRotationX = 0; + +/** + * The system variable pRotationY always contains the rotation of the + * device along the y axis in the frame previous to the current frame. Value + * is represented as 0 to +/-90 degrees. + * <br><br> + * pRotationY can also be used with rotationY to determine the rotate + * direction of the device along the Y-axis. + * @example + * <div class='norender'> + * <code> + * // A simple if statement looking at whether + * // rotationY - pRotationY < 0 is true or not will be + * // sufficient for determining the rotate direction + * // in most cases. + * + * // Some extra logic is needed to account for cases where + * // the angles wrap around. + * var rotateDirection = 'clockwise'; + * + * // Simple range conversion to make things simpler. + * // This is not absolutely neccessary but the logic + * // will be different in that case. + * + * var rY = rotationY + 180; + * var pRY = pRotationY + 180; + * + * if ((rY - pRY > 0 && rY - pRY < 270)|| rY - pRY < -270){ + * rotateDirection = 'clockwise'; + * } else if (rY - pRY < 0 || rY - pRY > 270){ + * rotateDirection = 'counter-clockwise'; + * } + * </code> + * </div> + * + * @property pRotationY + */ +p5.prototype.pRotationY = 0; + +/** + * The system variable pRotationZ always contains the rotation of the + * device along the z axis in the frame previous to the current frame. Value + * is represented as 0 to 359 degrees. + * <br><br> + * pRotationZ can also be used with rotationZ to determine the rotate + * direction of the device along the Z-axis. + * @example + * <div class='norender'> + * <code> + * // A simple if statement looking at whether + * // rotationZ - pRotationZ < 0 is true or not will be + * // sufficient for determining the rotate direction + * // in most cases. + * + * // Some extra logic is needed to account for cases where + * // the angles wrap around. + * var rotateDirection = 'clockwise'; + * + * if ((rotationZ - pRotationZ > 0 && + * rotationZ - pRotationZ < 270)|| + * rotationZ - pRotationZ < -270){ + * + * rotateDirection = 'clockwise'; + * + * } else if (rotationZ - pRotationZ < 0 || + * rotationZ - pRotationZ > 270){ + * + * rotateDirection = 'counter-clockwise'; + * + * } + * </code> + * </div> + * + * @property pRotationZ + */ +p5.prototype.pRotationZ = 0; + +var startAngleX = 0; +var startAngleY = 0; +var startAngleZ = 0; + +var rotateDirectionX = 'clockwise'; +var rotateDirectionY = 'clockwise'; +var rotateDirectionZ = 'clockwise'; + +var pRotateDirectionX; +var pRotateDirectionY; +var pRotateDirectionZ; + +p5.prototype._updatePRotations = function(){ + this._setProperty('pRotationX', this.rotationX); + this._setProperty('pRotationY', this.rotationY); + this._setProperty('pRotationZ', this.rotationZ); +}; + +p5.prototype.turnAxis = undefined; + +var move_threshold = 0.5; +var shake_threshold = 30; + +/** + * The setMoveThreshold() function is used to set the movement threshold for + * the deviceMoved() function. The default threshold is set to 0.5. + * + * @method setMoveThreshold + * @param {number} value The threshold value + */ +p5.prototype.setMoveThreshold = function(val){ + if(typeof val === 'number'){ + move_threshold = val; + } +}; + +/** + * The setShakeThreshold() function is used to set the movement threshold for + * the deviceShaken() function. The default threshold is set to 30. + * + * @method setShakeThreshold + * @param {number} value The threshold value + */ +p5.prototype.setShakeThreshold = function(val){ + if(typeof val === 'number'){ + shake_threshold = val; + } +}; + +/** + * The deviceMoved() function is called when the device is moved by more than + * the threshold value along X, Y or Z axis. The default threshold is set to + * 0.5. + * @method deviceMoved + * @example + * <div> + * <code> + * // Run this example on a mobile device + * // Move the device around + * // to change the value. + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function deviceMoved() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * </code> + * </div> + */ + +/** + * The deviceTurned() function is called when the device rotates by + * more than 90 degrees continuously. + * <br><br> + * The axis that triggers the deviceTurned() method is stored in the turnAxis + * variable. The deviceTurned() method can be locked to trigger on any axis: + * X, Y or Z by comparing the turnAxis variable to 'X', 'Y' or 'Z'. + * + * @method deviceTurned + * @example + * <div> + * <code> + * // Run this example on a mobile device + * // Rotate the device by 90 degrees + * // to change the value. + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function deviceTurned() { + * if (value == 0){ + * value = 255 + * } else if (value == 255) { + * value = 0; + * } + * } + * </code> + * </div> + * <div> + * <code> + * // Run this example on a mobile device + * // Rotate the device by 90 degrees in the + * // X-axis to change the value. + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function deviceTurned() { + * if (turnAxis == 'X'){ + * if (value == 0){ + * value = 255 + * } else if (value == 255) { + * value = 0; + * } + * } + * } + * </code> + * </div> + */ + +/** + * The deviceShaken() function is called when the device total acceleration + * changes of accelerationX and accelerationY values is more than + * the threshold value. The default threshold is set to 30. + * @method deviceShaken + * @example + * <div> + * <code> + * // Run this example on a mobile device + * // Shake the device to change the value. + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function deviceShaken() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * </code> + * </div> + */ + +p5.prototype._ondeviceorientation = function (e) { + this._updatePRotations(); + this._setProperty('rotationX', e.beta); + this._setProperty('rotationY', e.gamma); + this._setProperty('rotationZ', e.alpha); + this._handleMotion(); +}; +p5.prototype._ondevicemotion = function (e) { + this._updatePAccelerations(); + this._setProperty('accelerationX', e.acceleration.x * 2); + this._setProperty('accelerationY', e.acceleration.y * 2); + this._setProperty('accelerationZ', e.acceleration.z * 2); + this._handleMotion(); +}; +p5.prototype._handleMotion = function() { + if (window.orientation === 90 || window.orientation === -90) { + this._setProperty('deviceOrientation', 'landscape'); + } else if (window.orientation === 0) { + this._setProperty('deviceOrientation', 'portrait'); + } else if (window.orientation === undefined) { + this._setProperty('deviceOrientation', 'undefined'); + } + var deviceMoved = this.deviceMoved || window.deviceMoved; + if (typeof deviceMoved === 'function') { + if (Math.abs(this.accelerationX - this.pAccelerationX) > move_threshold || + Math.abs(this.accelerationY - this.pAccelerationY) > move_threshold || + Math.abs(this.accelerationZ - this.pAccelerationZ) > move_threshold) { + deviceMoved(); + } + } + var deviceTurned = this.deviceTurned || window.deviceTurned; + if (typeof deviceTurned === 'function') { + // The angles given by rotationX etc is from range -180 to 180. + // The following will convert them to 0 to 360 for ease of calculation + // of cases when the angles wrapped around. + // _startAngleX will be converted back at the end and updated. + var wRX = this.rotationX + 180; + var wPRX = this.pRotationX + 180; + var wSAX = startAngleX + 180; + if ((wRX - wPRX > 0 && wRX - wPRX < 270)|| wRX - wPRX < -270){ + rotateDirectionX = 'clockwise'; + } else if (wRX - wPRX < 0 || wRX - wPRX > 270){ + rotateDirectionX = 'counter-clockwise'; + } + if (rotateDirectionX !== pRotateDirectionX){ + wSAX = wRX; + } + if (Math.abs(wRX - wSAX) > 90 && Math.abs(wRX - wSAX) < 270){ + wSAX = wRX; + this._setProperty('turnAxis', 'X'); + deviceTurned(); + } + pRotateDirectionX = rotateDirectionX; + startAngleX = wSAX - 180; + + // Y-axis is identical to X-axis except for changing some names. + var wRY = this.rotationY + 180; + var wPRY = this.pRotationY + 180; + var wSAY = startAngleY + 180; + if ((wRY - wPRY > 0 && wRY - wPRY < 270)|| wRY - wPRY < -270){ + rotateDirectionY = 'clockwise'; + } else if (wRY - wPRY < 0 || wRY - this.pRotationY > 270){ + rotateDirectionY = 'counter-clockwise'; + } + if (rotateDirectionY !== pRotateDirectionY){ + wSAY = wRY; + } + if (Math.abs(wRY - wSAY) > 90 && Math.abs(wRY - wSAY) < 270){ + wSAY = wRY; + this._setProperty('turnAxis', 'Y'); + deviceTurned(); + } + pRotateDirectionY = rotateDirectionY; + startAngleY = wSAY - 180; + + // Z-axis is already in the range 0 to 360 + // so no conversion is needed. + if ((this.rotationZ - this.pRotationZ > 0 && + this.rotationZ - this.pRotationZ < 270)|| + this.rotationZ - this.pRotationZ < -270){ + rotateDirectionZ = 'clockwise'; + } else if (this.rotationZ - this.pRotationZ < 0 || + this.rotationZ - this.pRotationZ > 270){ + rotateDirectionZ = 'counter-clockwise'; + } + if (rotateDirectionZ !== pRotateDirectionZ){ + startAngleZ = this.rotationZ; + } + if (Math.abs(this.rotationZ - startAngleZ) > 90 && + Math.abs(this.rotationZ - startAngleZ) < 270){ + startAngleZ = this.rotationZ; + this._setProperty('turnAxis', 'Z'); + deviceTurned(); + } + pRotateDirectionZ = rotateDirectionZ; + this._setProperty('turnAxis', undefined); + } + var deviceShaken = this.deviceShaken || window.deviceShaken; + if (typeof deviceShaken === 'function') { + var accelerationChangeX; + var accelerationChangeY; + // Add accelerationChangeZ if acceleration change on Z is needed + if (this.pAccelerationX !== null) { + accelerationChangeX = Math.abs(this.accelerationX - this.pAccelerationX); + accelerationChangeY = Math.abs(this.accelerationY - this.pAccelerationY); + } + if (accelerationChangeX + accelerationChangeY > shake_threshold) { + deviceShaken(); + } + } +}; + + +module.exports = p5; + +},{"../core/core":48}],62:[function(_dereq_,module,exports){ +/** + * @module Events + * @submodule Keyboard + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Holds the key codes of currently pressed keys. + * @private + */ +var downKeys = {}; + +/** + * The boolean system variable keyIsPressed is true if any key is pressed + * and false if no keys are pressed. + * + * @property keyIsPressed + * @example + * <div> + * <code> + * var value = 0; + * function draw() { + * if (keyIsPressed === true) { + * fill(0); + * } else { + * fill(255); + * } + * rect(25, 25, 50, 50); + * } + * </code> + * </div> + */ +p5.prototype.isKeyPressed = false; +p5.prototype.keyIsPressed = false; // khan + +/** + * The system variable key always contains the value of the most recent + * key on the keyboard that was typed. To get the proper capitalization, it + * is best to use it within keyTyped(). For non-ASCII keys, use the keyCode + * variable. + * + * @property key + * @example + * <div><code> + * // Click any key to display it! + * // (Not Guaranteed to be Case Sensitive) + * function setup() { + * fill(245, 123, 158); + * textSize(50); + * } + * + * function draw() { + * background(200); + * text(key, 33,65); // Display last key pressed. + * } + * </div></code> + */ +p5.prototype.key = ''; + +/** + * The variable keyCode is used to detect special keys such as BACKSPACE, + * DELETE, ENTER, RETURN, TAB, ESCAPE, SHIFT, CONTROL, OPTION, ALT, UP_ARROW, + * DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW. + * + * @property keyCode + * @example + * <div><code> + * var fillVal = 126; + * function draw() { + * fill(fillVal); + * rect(25, 25, 50, 50); + * } + * + * function keyPressed() { + * if (keyCode == UP_ARROW) { + * fillVal = 255; + * } else if (keyCode == DOWN_ARROW) { + * fillVal = 0; + * } + * return false; // prevent default + * } + * </code></div> + */ +p5.prototype.keyCode = 0; + +/** + * The keyPressed() function is called once every time a key is pressed. The + * keyCode for the key that was pressed is stored in the keyCode variable. + * <br><br> + * For non-ASCII keys, use the keyCode variable. You can check if the keyCode + * equals BACKSPACE, DELETE, ENTER, RETURN, TAB, ESCAPE, SHIFT, CONTROL, + * OPTION, ALT, UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW. + * <br><br> + * For ASCII keys that was pressed is stored in the key variable. However, it + * does not distinguish between uppercase and lowercase. For this reason, it + * is recommended to use keyTyped() to read the key variable, in which the + * case of the variable will be distinguished. + * <br><br> + * Because of how operating systems handle key repeats, holding down a key + * may cause multiple calls to keyTyped() (and keyReleased() as well). The + * rate of repeat is set by the operating system and how each computer is + * configured.<br><br> + * Browsers may have different default + * behaviors attached to various key events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method keyPressed + * @example + * <div> + * <code> + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function keyPressed() { + * if (value === 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * <div> + * <code> + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function keyPressed() { + * if (keyCode === LEFT_ARROW) { + * value = 255; + * } else if (keyCode === RIGHT_ARROW) { + * value = 0; + * } + * } + * </code> + * </div> + * <div class="norender"> + * <code> + * function keyPressed(){ + * // Do something + * return false; // prevent any default behaviour + * } + * </code> + * </div> + */ +p5.prototype._onkeydown = function (e) { + if (downKeys[e.which]) { // prevent multiple firings + return; + } + this._setProperty('isKeyPressed', true); + this._setProperty('keyIsPressed', true); + this._setProperty('keyCode', e.which); + downKeys[e.which] = true; + var key = String.fromCharCode(e.which); + if (!key) { + key = e.which; + } + this._setProperty('key', key); + var keyPressed = this.keyPressed || window.keyPressed; + if (typeof keyPressed === 'function' && !e.charCode) { + var executeDefault = keyPressed(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; +/** + * The keyReleased() function is called once every time a key is released. + * See key and keyCode for more information.<br><br> + * Browsers may have different default + * behaviors attached to various key events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method keyReleased + * @example + * <div> + * <code> + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function keyReleased() { + * if (value === 0) { + * value = 255; + * } else { + * value = 0; + * } + * return false; // prevent any default behavior + * } + * </code> + * </div> + */ +p5.prototype._onkeyup = function (e) { + var keyReleased = this.keyReleased || window.keyReleased; + this._setProperty('isKeyPressed', false); + this._setProperty('keyIsPressed', false); + this._setProperty('_lastKeyCodeTyped', null); + downKeys[e.which] = false; + //delete this._downKeys[e.which]; + var key = String.fromCharCode(e.which); + if (!key) { + key = e.which; + } + this._setProperty('key', key); + this._setProperty('keyCode', e.which); + if (typeof keyReleased === 'function') { + var executeDefault = keyReleased(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +/** + * The keyTyped() function is called once every time a key is pressed, but + * action keys such as Ctrl, Shift, and Alt are ignored. The most recent + * key pressed will be stored in the key variable. + * <br><br> + * Because of how operating systems handle key repeats, holding down a key + * will cause multiple calls to keyTyped() (and keyReleased() as well). The + * rate of repeat is set by the operating system and how each computer is + * configured.<br><br> + * Browsers may have different default behaviors attached to various key + * events. To prevent any default behavior for this event, add "return false" + * to the end of the method. + * + * @method keyTyped + * @example + * <div> + * <code> + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function keyTyped() { + * if (key === 'a') { + * value = 255; + * } else if (key === 'b') { + * value = 0; + * } + * return false; // prevent any default behavior + * } + * </code> + * </div> + */ +p5.prototype._onkeypress = function (e) { + if (e.which === this._lastKeyCodeTyped) { // prevent multiple firings + return; + } + this._setProperty('keyCode', e.which); + this._setProperty('_lastKeyCodeTyped', e.which); // track last keyCode + this._setProperty('key', String.fromCharCode(e.which)); + var keyTyped = this.keyTyped || window.keyTyped; + if (typeof keyTyped === 'function') { + var executeDefault = keyTyped(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; +/** + * The onblur function is called when the user is no longer focused + * on the p5 element. Because the keyup events will not fire if the user is + * not focused on the element we must assume all keys currently down have + * been released. + */ +p5.prototype._onblur = function (e) { + downKeys = {}; +}; + +/** + * The keyIsDown() function checks if the key is currently down, i.e. pressed. + * It can be used if you have an object that moves, and you want several keys + * to be able to affect its behaviour simultaneously, such as moving a + * sprite diagonally. You can put in any number representing the keyCode of + * the key, or use any of the variable keyCode names listed + * <a href="http://p5js.org/reference/#p5/keyCode">here</a>. + * + * @method keyIsDown + * @param {Number} code The key to check for. + * @return {Boolean} whether key is down or not + * @example + * <div><code> + * var x = 100; + * var y = 100; + * + * function setup() { + * createCanvas(512, 512); + * } + * + * function draw() { + * if (keyIsDown(LEFT_ARROW)) + * x-=5; + * + * if (keyIsDown(RIGHT_ARROW)) + * x+=5; + * + * if (keyIsDown(UP_ARROW)) + * y-=5; + * + * if (keyIsDown(DOWN_ARROW)) + * y+=5; + * + * clear(); + * fill(255, 0, 0); + * ellipse(x, y, 50, 50); + * } + * </code></div> + */ +p5.prototype.keyIsDown = function(code) { + return downKeys[code]; +}; + +module.exports = p5; + +},{"../core/core":48}],63:[function(_dereq_,module,exports){ +/** + * @module Events + * @submodule Mouse + * @for p5 + * @requires core + * @requires constants + */ + + +'use strict'; + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); + +/* + * These are helper vars that store the mouseX and mouseY vals + * between the time that a mouse event happens and the next frame + * of draw. This is done to deal with the asynchronicity of event + * calls interacting with the draw loop. When a mouse event occurs + * the _nextMouseX/Y vars are updated, then on each call of draw, mouseX/Y + * and pmouseX/Y are updated using the _nextMouseX/Y vals. + */ +p5.prototype._nextMouseX = 0; +p5.prototype._nextMouseY = 0; + +/** + * The system variable mouseX always contains the current horizontal + * position of the mouse, relative to (0, 0) of the canvas. + * + * @property mouseX + * + * @example + * <div> + * <code> + * // Move the mouse across the canvas + * function draw() { + * background(244, 248, 252); + * line(mouseX, 0, mouseX, 100); + * } + * </code> + * </div> + */ +p5.prototype.mouseX = 0; + +/** + * The system variable mouseY always contains the current vertical position + * of the mouse, relative to (0, 0) of the canvas. + * + * @property mouseY + * + * @example + * <div> + * <code> + * // Move the mouse across the canvas + * function draw() { + * background(244, 248, 252); + * line(0, mouseY, 100, mouseY); + *} + * </code> + * </div> + */ +p5.prototype.mouseY = 0; + +/** + * The system variable pmouseX always contains the horizontal position of + * the mouse in the frame previous to the current frame, relative to (0, 0) + * of the canvas. + * + * @property pmouseX + * + * @example + * <div> + * <code> + * // Move the mouse across the canvas to leave a trail + * function setup() { + * //slow down the frameRate to make it more visible + * frameRate(10); + * } + * + * function draw() { + * background(244, 248, 252); + * line(mouseX, mouseY, pmouseX, pmouseY); + * print(pmouseX + " -> " + mouseX); + * } + * + * </code> + * </div> + */ +p5.prototype.pmouseX = 0; + +/** + * The system variable pmouseY always contains the vertical position of the + * mouse in the frame previous to the current frame, relative to (0, 0) of + * the canvas. + * + * @property pmouseY + * + * @example + * <div> + * <code> + * function draw() { + * background(237, 34, 93); + * fill(0); + * //draw a square only if the mouse is not moving + * if(mouseY == pmouseY && mouseX == pmouseX) + * rect(20,20,60,60); + * + * print(pmouseY + " -> " + mouseY); + * } + * + * </code> + * </div> + */ +p5.prototype.pmouseY = 0; + +/** + * The system variable winMouseX always contains the current horizontal + * position of the mouse, relative to (0, 0) of the window. + * + * @property winMouseX + * + * @example + * <div> + * <code> + * var myCanvas; + * + * function setup() { + * //use a variable to store a pointer to the canvas + * myCanvas = createCanvas(100, 100); + * } + * + * function draw() { + * background(237, 34, 93); + * fill(0); + * + * //move the canvas to the horizontal mouse position + * //relative to the window + * myCanvas.position(winMouseX+1, windowHeight/2); + * + * //the y of the square is relative to the canvas + * rect(20,mouseY,60,60); + * } + * + * </code> + * </div> + */ +p5.prototype.winMouseX = 0; + +/** + * The system variable winMouseY always contains the current vertical + * position of the mouse, relative to (0, 0) of the window. + * + * @property winMouseY + * + * @example + * <div> + * <code> + *var myCanvas; + * + * function setup() { + * //use a variable to store a pointer to the canvas + * myCanvas = createCanvas(100, 100); + * } + * + * function draw() { + * background(237, 34, 93); + * fill(0); + * + * //move the canvas to the vertical mouse position + * //relative to the window + * myCanvas.position(windowWidth/2, winMouseY+1); + * + * //the x of the square is relative to the canvas + * rect(mouseX,20,60,60); + * } + * + * </code> + * </div> + */ +p5.prototype.winMouseY = 0; + +/** + * The system variable pwinMouseX always contains the horizontal position + * of the mouse in the frame previous to the current frame, relative to + * (0, 0) of the window. + * + * @property pwinMouseX + * + * @example + * <div> + * <code> + * + * var myCanvas; + * + * function setup() { + * //use a variable to store a pointer to the canvas + * myCanvas = createCanvas(100, 100); + * noStroke(); + * fill(237, 34, 93); + * } + * + * function draw() { + * clear(); + * //the difference between previous and + * //current x position is the horizontal mouse speed + * var speed = abs(winMouseX-pwinMouseX); + * //change the size of the circle + * //according to the horizontal speed + * ellipse(50, 50, 10+speed*5, 10+speed*5); + * //move the canvas to the mouse position + * myCanvas.position( winMouseX+1, winMouseY+1); + * } + * + * </code> + * </div> + */ +p5.prototype.pwinMouseX = 0; + +/** + * The system variable pwinMouseY always contains the vertical position of + * the mouse in the frame previous to the current frame, relative to (0, 0) + * of the window. + * + * @property pwinMouseY + * + * + * @example + * <div> + * <code> + * + * var myCanvas; + * + * function setup() { + * //use a variable to store a pointer to the canvas + * myCanvas = createCanvas(100, 100); + * noStroke(); + * fill(237, 34, 93); + * } + * + * function draw() { + * clear(); + * //the difference between previous and + * //current y position is the vertical mouse speed + * var speed = abs(winMouseY-pwinMouseY); + * //change the size of the circle + * //according to the vertical speed + * ellipse(50, 50, 10+speed*5, 10+speed*5); + * //move the canvas to the mouse position + * myCanvas.position( winMouseX+1, winMouseY+1); + * } + * + * </code> + * </div> + */ +p5.prototype.pwinMouseY = 0; + +/** + * Processing automatically tracks if the mouse button is pressed and which + * button is pressed. The value of the system variable mouseButton is either + * LEFT, RIGHT, or CENTER depending on which button was pressed last. + * Warning: different browsers may track mouseButton differently. + * + * @property mouseButton + * + * @example + * <div> + * <code> + * function draw() { + * background(237, 34, 93); + * fill(0); + * + * if (mouseIsPressed) { + * if (mouseButton == LEFT) + * ellipse(50, 50, 50, 50); + * if (mouseButton == RIGHT) + * rect(25, 25, 50, 50); + * if (mouseButton == CENTER) + * triangle(23, 75, 50, 20, 78, 75); + * } + * + * print(mouseButton); + * } + * </code> + * </div> + */ +p5.prototype.mouseButton = 0; + +/** + * The boolean system variable mouseIsPressed is true if the mouse is pressed + * and false if not. + * + * @property mouseIsPressed + * + * @example + * <div> + * <code> + * function draw() { + * background(237, 34, 93); + * fill(0); + * + * if (mouseIsPressed) + * ellipse(50, 50, 50, 50); + * else + * rect(25, 25, 50, 50); + * + * print(mouseIsPressed); + * } + * </code> + * </div> + */ +p5.prototype.mouseIsPressed = false; +p5.prototype.isMousePressed = false; // both are supported + +p5.prototype._updateNextMouseCoords = function(e) { + if(e.type === 'touchstart' || + e.type === 'touchmove' || + e.type === 'touchend') { + this._setProperty('_nextMouseX', this._nextTouchX); + this._setProperty('_nextMouseY', this._nextTouchY); + } else { + if(this._curElement !== null) { + var mousePos = getMousePos(this._curElement.elt, e); + this._setProperty('_nextMouseX', mousePos.x); + this._setProperty('_nextMouseY', mousePos.y); + } + } + this._setProperty('winMouseX', e.pageX); + this._setProperty('winMouseY', e.pageY); +}; + +p5.prototype._updateMouseCoords = function() { + this._setProperty('pmouseX', this.mouseX); + this._setProperty('pmouseY', this.mouseY); + this._setProperty('mouseX', this._nextMouseX); + this._setProperty('mouseY', this._nextMouseY); + this._setProperty('pwinMouseX', this.winMouseX); + this._setProperty('pwinMouseY', this.winMouseY); +}; + +function getMousePos(canvas, evt) { + var rect = canvas.getBoundingClientRect(); + return { + x: evt.clientX - rect.left, + y: evt.clientY - rect.top + }; +} + +p5.prototype._setMouseButton = function(e) { + if (e.button === 1) { + this._setProperty('mouseButton', constants.CENTER); + } else if (e.button === 2) { + this._setProperty('mouseButton', constants.RIGHT); + } else { + this._setProperty('mouseButton', constants.LEFT); + } +}; + +/** + * The mouseMoved() function is called every time the mouse moves and a mouse + * button is not pressed.<br><br> + * Browsers may have different default + * behaviors attached to various mouse events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method mouseMoved + * @example + * <div> + * <code> + * // Move the mouse across the page + * // to change its value + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function mouseMoved() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function mouseMoved() { + * ellipse(mouseX, mouseY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ + +/** + * The mouseDragged() function is called once every time the mouse moves and + * a mouse button is pressed. If no mouseDragged() function is defined, the + * touchMoved() function will be called instead if it is defined.<br><br> + * Browsers may have different default + * behaviors attached to various mouse events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method mouseDragged + * @example + * <div> + * <code> + * // Drag the mouse across the page + * // to change its value + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function mouseDragged() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function mouseDragged() { + * ellipse(mouseX, mouseY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._onmousemove = function(e){ + var context = this._isGlobal ? window : this; + var executeDefault; + this._updateNextMouseCoords(e); + this._updateNextTouchCoords(e); + if (!this.isMousePressed) { + if (typeof context.mouseMoved === 'function') { + executeDefault = context.mouseMoved(e); + if(executeDefault === false) { + e.preventDefault(); + } + } + } + else { + if (typeof context.mouseDragged === 'function') { + executeDefault = context.mouseDragged(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.touchMoved === 'function') { + executeDefault = context.touchMoved(e); + if(executeDefault === false) { + e.preventDefault(); + } + } + } +}; + +/** + * The mousePressed() function is called once after every time a mouse button + * is pressed. The mouseButton variable (see the related reference entry) + * can be used to determine which button has been pressed. If no + * mousePressed() function is defined, the touchStarted() function will be + * called instead if it is defined.<br><br> + * Browsers may have different default + * behaviors attached to various mouse events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method mousePressed + * @example + * <div> + * <code> + * // Click within the image to change + * // the value of the rectangle + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function mousePressed() { + * if (value == 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function mousePressed() { + * ellipse(mouseX, mouseY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._onmousedown = function(e) { + var context = this._isGlobal ? window : this; + var executeDefault; + this._setProperty('isMousePressed', true); + this._setProperty('mouseIsPressed', true); + this._setMouseButton(e); + this._updateNextMouseCoords(e); + this._updateNextTouchCoords(e); + if (typeof context.mousePressed === 'function') { + executeDefault = context.mousePressed(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.touchStarted === 'function') { + executeDefault = context.touchStarted(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +/** + * The mouseReleased() function is called every time a mouse button is + * released. If no mouseReleased() function is defined, the touchEnded() + * function will be called instead if it is defined.<br><br> + * Browsers may have different default + * behaviors attached to various mouse events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * + * @method mouseReleased + * @example + * <div> + * <code> + * // Click within the image to change + * // the value of the rectangle + * // after the mouse has been clicked + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function mouseReleased() { + * if (value == 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function mouseReleased() { + * ellipse(mouseX, mouseY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._onmouseup = function(e) { + var context = this._isGlobal ? window : this; + var executeDefault; + this._setProperty('isMousePressed', false); + this._setProperty('mouseIsPressed', false); + if (typeof context.mouseReleased === 'function') { + executeDefault = context.mouseReleased(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.touchEnded === 'function') { + executeDefault = context.touchEnded(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +p5.prototype._ondragend = p5.prototype._onmouseup; +p5.prototype._ondragover = p5.prototype._onmousemove; + +/** + * The mouseClicked() function is called once after a mouse button has been + * pressed and then released.<br><br> + * Browsers may have different default + * behaviors attached to various mouse events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method mouseClicked + * @example + * <div> + * <code> + * // Click within the image to change + * // the value of the rectangle + * // after the mouse has been clicked + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function mouseClicked() { + * if (value == 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function mouseClicked() { + * ellipse(mouseX, mouseY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._onclick = function(e) { + var context = this._isGlobal ? window : this; + if (typeof context.mouseClicked === 'function') { + var executeDefault = context.mouseClicked(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +/** + * The function mouseWheel() is executed every time a vertical mouse wheel + * event is detected either triggered by an actual mouse wheel or by a + * touchpad.<br><br> + * The event.delta property returns the amount the mouse wheel + * have scrolled. The values can be positive or negative depending on the + * scroll direction (on OS X with "natural" scrolling enabled, the signs + * are inverted).<br><br> + * Browsers may have different default behaviors attached to various + * mouse events. To prevent any default behavior for this event, add + * "return false" to the end of the method.<br><br> + * Due to the current support of the "wheel" event on Safari, the function + * may only work as expected if "return false" is included while using Safari. + * + * @method mouseWheel + * + * @example + * <div> + * <code> + * var pos = 25; + * + * function draw() { + * background(237, 34, 93); + * fill(0); + * rect(25, pos, 50, 50); + * } + * + * function mouseWheel(event) { + * print(event.delta); + * //move the square according to the vertical scroll amount + * pos += event.delta; + * //uncomment to block page scrolling + * //return false; + * } + * </code> + * </div> + */ +p5.prototype._onwheel = function(e) { + var context = this._isGlobal ? window : this; + if (typeof context.mouseWheel === 'function') { + e.delta = e.deltaY; + var executeDefault = context.mouseWheel(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +module.exports = p5; + +},{"../core/constants":47,"../core/core":48}],64:[function(_dereq_,module,exports){ +/** + * @module Events + * @submodule Touch + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/* + * These are helper vars that store the touchX and touchY vals + * between the time that a mouse event happens and the next frame + * of draw. This is done to deal with the asynchronicity of event + * calls interacting with the draw loop. When a touch event occurs + * the _nextTouchX/Y vars are updated, then on each call of draw, touchX/Y + * and ptouchX/Y are updated using the _nextMouseX/Y vals. + */ +p5.prototype._nextTouchX = 0; +p5.prototype._nextTouchY = 0; + +/** + * The system variable touchX always contains the horizontal position of + * one finger, relative to (0, 0) of the canvas. This is best used for + * single touch interactions. For multi-touch interactions, use the + * touches[] array. + * + * @property touchX + */ +p5.prototype.touchX = 0; + +/** + * The system variable touchY always contains the vertical position of + * one finger, relative to (0, 0) of the canvas. This is best used for + * single touch interactions. For multi-touch interactions, use the + * touches[] array. + * + * @property touchY + */ +p5.prototype.touchY = 0; + +/** + * The system variable ptouchX always contains the horizontal position of + * one finger, relative to (0, 0) of the canvas, in the frame previous to the + * current frame. + * + * @property ptouchX + */ +p5.prototype.ptouchX = 0; + +/** + * The system variable ptouchY always contains the vertical position of + * one finger, relative to (0, 0) of the canvas, in the frame previous to the + * current frame. + * + * @property ptouchY + */ +p5.prototype.ptouchY = 0; + +/** + * The system variable touches[] contains an array of the positions of all + * current touch points, relative to (0, 0) of the canvas, and IDs identifying a + * unique touch as it moves. Each element in the array is an object with x, y, + * and id properties. + * + * @property touches[] + */ +p5.prototype.touches = []; + +/** + * The boolean system variable touchIsDown is true if the screen is + * touched and false if not. + * + * @property touchIsDown + */ +p5.prototype.touchIsDown = false; + +p5.prototype._updateNextTouchCoords = function(e) { + if(e.type === 'mousedown' || + e.type === 'mousemove' || + e.type === 'mouseup'){ + this._setProperty('_nextTouchX', this._nextMouseX); + this._setProperty('_nextTouchY', this._nextMouseY); + } else { + if(this._curElement !== null) { + var touchInfo = getTouchInfo(this._curElement.elt, e, 0); + this._setProperty('_nextTouchX', touchInfo.x); + this._setProperty('_nextTouchY', touchInfo.y); + + var touches = []; + for(var i = 0; i < e.touches.length; i++){ + touches[i] = getTouchInfo(this._curElement.elt, e, i); + } + this._setProperty('touches', touches); + } + } +}; + +p5.prototype._updateTouchCoords = function() { + this._setProperty('ptouchX', this.touchX); + this._setProperty('ptouchY', this.touchY); + this._setProperty('touchX', this._nextTouchX); + this._setProperty('touchY', this._nextTouchY); +}; + +function getTouchInfo(canvas, e, i) { + i = i || 0; + var rect = canvas.getBoundingClientRect(); + var touch = e.touches[i] || e.changedTouches[i]; + return { + x: touch.clientX - rect.left, + y: touch.clientY - rect.top, + id: touch.identifier + }; +} + +/** + * The touchStarted() function is called once after every time a touch is + * registered. If no touchStarted() function is defined, the mousePressed() + * function will be called instead if it is defined.<br><br> + * Browsers may have different default behaviors attached to various touch + * events. To prevent any default behavior for this event, add "return false" + * to the end of the method. + * + * @method touchStarted + * @example + * <div> + * <code> + * // Touch within the image to change + * // the value of the rectangle + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function touchStarted() { + * if (value == 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function touchStarted() { + * ellipse(touchX, touchY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._ontouchstart = function(e) { + var context = this._isGlobal ? window : this; + var executeDefault; + this._updateNextTouchCoords(e); + this._updateNextMouseCoords(e); + this._setProperty('touchIsDown', true); + if(typeof context.touchStarted === 'function') { + executeDefault = context.touchStarted(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.mousePressed === 'function') { + executeDefault = context.mousePressed(e); + if(executeDefault === false) { + e.preventDefault(); + } + //this._setMouseButton(e); + } +}; + +/** + * The touchMoved() function is called every time a touch move is registered. + * If no touchMoved() function is defined, the mouseDragged() function will + * be called instead if it is defined.<br><br> + * Browsers may have different default behaviors attached to various touch + * events. To prevent any default behavior for this event, add "return false" + * to the end of the method. + * + * @method touchMoved + * @example + * <div> + * <code> + * // Move your finger across the page + * // to change its value + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function touchMoved() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function touchMoved() { + * ellipse(touchX, touchY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._ontouchmove = function(e) { + var context = this._isGlobal ? window : this; + var executeDefault; + this._updateNextTouchCoords(e); + this._updateNextMouseCoords(e); + if (typeof context.touchMoved === 'function') { + executeDefault = context.touchMoved(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.mouseDragged === 'function') { + executeDefault = context.mouseDragged(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +/** + * The touchEnded() function is called every time a touch ends. If no + * touchEnded() function is defined, the mouseReleased() function will be + * called instead if it is defined.<br><br> + * Browsers may have different default behaviors attached to various touch + * events. To prevent any default behavior for this event, add "return false" + * to the end of the method. + * + * @method touchEnded + * @example + * <div> + * <code> + * // Release touch within the image to + * // change the value of the rectangle + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function touchEnded() { + * if (value == 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function touchEnded() { + * ellipse(touchX, touchY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._ontouchend = function(e) { + this._updateNextTouchCoords(e); + this._updateNextMouseCoords(e); + if (this.touches.length === 0) { + this._setProperty('touchIsDown', false); + } + var context = this._isGlobal ? window : this; + var executeDefault; + if (typeof context.touchEnded === 'function') { + executeDefault = context.touchEnded(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.mouseReleased === 'function') { + executeDefault = context.mouseReleased(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +module.exports = p5; + +},{"../core/core":48}],65:[function(_dereq_,module,exports){ +/*global ImageData:false */ + +/** + * This module defines the filters for use with image buffers. + * + * This module is basically a collection of functions stored in an object + * as opposed to modules. The functions are destructive, modifying + * the passed in canvas rather than creating a copy. + * + * Generally speaking users of this module will use the Filters.apply method + * on a canvas to create an effect. + * + * A number of functions are borrowed/adapted from + * http://www.html5rocks.com/en/tutorials/canvas/imagefilters/ + * or the java processing implementation. + */ + +'use strict'; + +var Filters = {}; + + +/* + * Helper functions + */ + + +/** + * Returns the pixel buffer for a canvas + * + * @private + * + * @param {Canvas|ImageData} canvas the canvas to get pixels from + * @return {Uint8ClampedArray} a one-dimensional array containing + * the data in thc RGBA order, with integer + * values between 0 and 255 + */ +Filters._toPixels = function (canvas) { + if (canvas instanceof ImageData) { + return canvas.data; + } else { + return canvas.getContext('2d').getImageData( + 0, + 0, + canvas.width, + canvas.height + ).data; + } +}; + +/** + * Returns a 32 bit number containing ARGB data at ith pixel in the + * 1D array containing pixels data. + * + * @private + * + * @param {Uint8ClampedArray} data array returned by _toPixels() + * @param {Integer} i index of a 1D Image Array + * @return {Integer} 32 bit integer value representing + * ARGB value. + */ +Filters._getARGB = function (data, i) { + var offset = i * 4; + return (data[offset+3] << 24) & 0xff000000 | + (data[offset] << 16) & 0x00ff0000 | + (data[offset+1] << 8) & 0x0000ff00 | + data[offset+2] & 0x000000ff; +}; + +/** + * Modifies pixels RGBA values to values contained in the data object. + * + * @private + * + * @param {Uint8ClampedArray} pixels array returned by _toPixels() + * @param {Int32Array} data source 1D array where each value + * represents ARGB values + */ +Filters._setPixels = function (pixels, data) { + var offset = 0; + for( var i = 0, al = pixels.length; i < al; i++) { + offset = i*4; + pixels[offset + 0] = (data[i] & 0x00ff0000)>>>16; + pixels[offset + 1] = (data[i] & 0x0000ff00)>>>8; + pixels[offset + 2] = (data[i] & 0x000000ff); + pixels[offset + 3] = (data[i] & 0xff000000)>>>24; + } +}; + +/** + * Returns the ImageData object for a canvas + * https://developer.mozilla.org/en-US/docs/Web/API/ImageData + * + * @private + * + * @param {Canvas|ImageData} canvas canvas to get image data from + * @return {ImageData} Holder of pixel data (and width and + * height) for a canvas + */ +Filters._toImageData = function (canvas) { + if (canvas instanceof ImageData) { + return canvas; + } else { + return canvas.getContext('2d').getImageData( + 0, + 0, + canvas.width, + canvas.height + ); + } +}; + +/** + * Returns a blank ImageData object. + * + * @private + * + * @param {Integer} width + * @param {Integer} height + * @return {ImageData} + */ +Filters._createImageData = function (width, height) { + Filters._tmpCanvas = document.createElement('canvas'); + Filters._tmpCtx = Filters._tmpCanvas.getContext('2d'); + return this._tmpCtx.createImageData(width, height); +}; + + +/** + * Applys a filter function to a canvas. + * + * The difference between this and the actual filter functions defined below + * is that the filter functions generally modify the pixel buffer but do + * not actually put that data back to the canvas (where it would actually + * update what is visible). By contrast this method does make the changes + * actually visible in the canvas. + * + * The apply method is the method that callers of this module would generally + * use. It has been separated from the actual filters to support an advanced + * use case of creating a filter chain that executes without actually updating + * the canvas in between everystep. + * + * @param {[type]} func [description] + * @param {[type]} canvas [description] + * @param {[type]} level [description] + * @return {[type]} [description] + */ +Filters.apply = function (canvas, func, filterParam) { + var ctx = canvas.getContext('2d'); + var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + //Filters can either return a new ImageData object, or just modify + //the one they received. + var newImageData = func(imageData, filterParam); + if (newImageData instanceof ImageData) { + ctx.putImageData(newImageData, 0, 0, 0, 0, canvas.width, canvas.height); + } else { + ctx.putImageData(imageData, 0, 0, 0, 0, canvas.width, canvas.height); + } +}; + + +/* + * Filters + */ + + +/** + * Converts the image to black and white pixels depending if they are above or + * below the threshold defined by the level parameter. The parameter must be + * between 0.0 (black) and 1.0 (white). If no level is specified, 0.5 is used. + * + * Borrowed from http://www.html5rocks.com/en/tutorials/canvas/imagefilters/ + * + * @param {Canvas} canvas + * @param {Float} level + */ +Filters.threshold = function (canvas, level) { + var pixels = Filters._toPixels(canvas); + + if (level === undefined) { + level = 0.5; + } + var thresh = Math.floor(level * 255); + + for (var i = 0; i < pixels.length; i += 4) { + var r = pixels[i]; + var g = pixels[i + 1]; + var b = pixels[i + 2]; + var gray = (0.2126 * r + 0.7152 * g + 0.0722 * b); + var val; + if (gray >= thresh) { + val = 255; + } else { + val = 0; + } + pixels[i] = pixels[i + 1] = pixels[i + 2] = val; + } + +}; + + +/** + * Converts any colors in the image to grayscale equivalents. + * No parameter is used. + * + * Borrowed from http://www.html5rocks.com/en/tutorials/canvas/imagefilters/ + * + * @param {Canvas} canvas + */ +Filters.gray = function (canvas) { + var pixels = Filters._toPixels(canvas); + + for (var i = 0; i < pixels.length; i += 4) { + var r = pixels[i]; + var g = pixels[i + 1]; + var b = pixels[i + 2]; + + // CIE luminance for RGB + var gray = (0.2126 * r + 0.7152 * g + 0.0722 * b); + pixels[i] = pixels[i + 1] = pixels[i + 2] = gray; + } +}; + +/** + * Sets the alpha channel to entirely opaque. No parameter is used. + * + * @param {Canvas} canvas + */ +Filters.opaque = function (canvas) { + var pixels = Filters._toPixels(canvas); + + for (var i = 0; i < pixels.length; i += 4) { + pixels[i + 3] = 255; + } + + return pixels; +}; + +/** + * Sets each pixel to its inverse value. No parameter is used. + * @param {Invert} + */ +Filters.invert = function (canvas) { + var pixels = Filters._toPixels(canvas); + + for (var i = 0; i < pixels.length; i += 4) { + pixels[i] = 255 - pixels[i]; + pixels[i + 1] = 255 - pixels[i + 1]; + pixels[i + 2] = 255 - pixels[i + 2]; + } + +}; + + +/** + * Limits each channel of the image to the number of colors specified as + * the parameter. The parameter can be set to values between 2 and 255, but + * results are most noticeable in the lower ranges. + * + * Adapted from java based processing implementation + * + * @param {Canvas} canvas + * @param {Integer} level + */ +Filters.posterize = function (canvas, level) { + var pixels = Filters._toPixels(canvas); + + if ((level < 2) || (level > 255)) { + throw new Error( + 'Level must be greater than 2 and less than 255 for posterize' + ); + } + + var levels1 = level - 1; + for (var i = 0; i < pixels.length; i+=4) { + var rlevel = pixels[i]; + var glevel = pixels[i + 1]; + var blevel = pixels[i + 2]; + + pixels[i] = (((rlevel * level) >> 8) * 255) / levels1; + pixels[i + 1] = (((glevel * level) >> 8) * 255) / levels1; + pixels[i + 2] = (((blevel * level) >> 8) * 255) / levels1; + } +}; + +/** + * reduces the bright areas in an image + * @param {Canvas} canvas + * + */ +Filters.dilate = function (canvas) { + var pixels = Filters._toPixels(canvas); + var currIdx = 0; + var maxIdx = pixels.length ? pixels.length/4 : 0; + var out = new Int32Array(maxIdx); + var currRowIdx, maxRowIdx, colOrig, colOut, currLum; + var idxRight, idxLeft, idxUp, idxDown, + colRight, colLeft, colUp, colDown, + lumRight, lumLeft, lumUp, lumDown; + + while(currIdx < maxIdx) { + currRowIdx = currIdx; + maxRowIdx = currIdx + canvas.width; + while (currIdx < maxRowIdx) { + colOrig = colOut = Filters._getARGB(pixels, currIdx); + idxLeft = currIdx - 1; + idxRight = currIdx + 1; + idxUp = currIdx - canvas.width; + idxDown = currIdx + canvas.width; + + if (idxLeft < currRowIdx) { + idxLeft = currIdx; + } + if (idxRight >= maxRowIdx) { + idxRight = currIdx; + } + if (idxUp < 0){ + idxUp = 0; + } + if (idxDown >= maxIdx) { + idxDown = currIdx; + } + colUp = Filters._getARGB(pixels, idxUp); + colLeft = Filters._getARGB(pixels, idxLeft); + colDown = Filters._getARGB(pixels, idxDown); + colRight = Filters._getARGB(pixels, idxRight); + + //compute luminance + currLum = 77*(colOrig>>16&0xff) + + 151*(colOrig>>8&0xff) + + 28*(colOrig&0xff); + lumLeft = 77*(colLeft>>16&0xff) + + 151*(colLeft>>8&0xff) + + 28*(colLeft&0xff); + lumRight = 77*(colRight>>16&0xff) + + 151*(colRight>>8&0xff) + + 28*(colRight&0xff); + lumUp = 77*(colUp>>16&0xff) + + 151*(colUp>>8&0xff) + + 28*(colUp&0xff); + lumDown = 77*(colDown>>16&0xff) + + 151*(colDown>>8&0xff) + + 28*(colDown&0xff); + + if (lumLeft > currLum) { + colOut = colLeft; + currLum = lumLeft; + } + if (lumRight > currLum) { + colOut = colRight; + currLum = lumRight; + } + if (lumUp > currLum) { + colOut = colUp; + currLum = lumUp; + } + if (lumDown > currLum) { + colOut = colDown; + currLum = lumDown; + } + out[currIdx++]=colOut; + } + } + Filters._setPixels(pixels, out); +}; + +/** + * increases the bright areas in an image + * @param {Canvas} canvas + * + */ +Filters.erode = function(canvas) { + var pixels = Filters._toPixels(canvas); + var currIdx = 0; + var maxIdx = pixels.length ? pixels.length/4 : 0; + var out = new Int32Array(maxIdx); + var currRowIdx, maxRowIdx, colOrig, colOut, currLum; + var idxRight, idxLeft, idxUp, idxDown, + colRight, colLeft, colUp, colDown, + lumRight, lumLeft, lumUp, lumDown; + + while(currIdx < maxIdx) { + currRowIdx = currIdx; + maxRowIdx = currIdx + canvas.width; + while (currIdx < maxRowIdx) { + colOrig = colOut = Filters._getARGB(pixels, currIdx); + idxLeft = currIdx - 1; + idxRight = currIdx + 1; + idxUp = currIdx - canvas.width; + idxDown = currIdx + canvas.width; + + if (idxLeft < currRowIdx) { + idxLeft = currIdx; + } + if (idxRight >= maxRowIdx) { + idxRight = currIdx; + } + if (idxUp < 0) { + idxUp = 0; + } + if (idxDown >= maxIdx) { + idxDown = currIdx; + } + colUp = Filters._getARGB(pixels, idxUp); + colLeft = Filters._getARGB(pixels, idxLeft); + colDown = Filters._getARGB(pixels, idxDown); + colRight = Filters._getARGB(pixels, idxRight); + + //compute luminance + currLum = 77*(colOrig>>16&0xff) + + 151*(colOrig>>8&0xff) + + 28*(colOrig&0xff); + lumLeft = 77*(colLeft>>16&0xff) + + 151*(colLeft>>8&0xff) + + 28*(colLeft&0xff); + lumRight = 77*(colRight>>16&0xff) + + 151*(colRight>>8&0xff) + + 28*(colRight&0xff); + lumUp = 77*(colUp>>16&0xff) + + 151*(colUp>>8&0xff) + + 28*(colUp&0xff); + lumDown = 77*(colDown>>16&0xff) + + 151*(colDown>>8&0xff) + + 28*(colDown&0xff); + + if (lumLeft < currLum) { + colOut = colLeft; + currLum = lumLeft; + } + if (lumRight < currLum) { + colOut = colRight; + currLum = lumRight; + } + if (lumUp < currLum) { + colOut = colUp; + currLum = lumUp; + } + if (lumDown < currLum) { + colOut = colDown; + currLum = lumDown; + } + + out[currIdx++]=colOut; + } + } + Filters._setPixels(pixels, out); +}; + +// BLUR + +// internal kernel stuff for the gaussian blur filter +var blurRadius; +var blurKernelSize; +var blurKernel; +var blurMult; + +/* + * Port of https://github.com/processing/processing/blob/ + * master/core/src/processing/core/PImage.java#L1250 + * + * Optimized code for building the blur kernel. + * further optimized blur code (approx. 15% for radius=20) + * bigger speed gains for larger radii (~30%) + * added support for various image types (ALPHA, RGB, ARGB) + * [toxi 050728] + */ +function buildBlurKernel(r) { + var radius = (r * 3.5)|0; + radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248); + + if (blurRadius !== radius) { + blurRadius = radius; + blurKernelSize = 1 + blurRadius<<1; + blurKernel = new Int32Array(blurKernelSize); + blurMult = new Array(blurKernelSize); + for(var l = 0; l < blurKernelSize; l++){ + blurMult[l] = new Int32Array(256); + } + + var bk,bki; + var bm,bmi; + + for (var i = 1, radiusi = radius - 1; i < radius; i++) { + blurKernel[radius+i] = blurKernel[radiusi] = bki = radiusi * radiusi; + bm = blurMult[radius+i]; + bmi = blurMult[radiusi--]; + for (var j = 0; j < 256; j++){ + bm[j] = bmi[j] = bki * j; + } + } + bk = blurKernel[radius] = radius * radius; + bm = blurMult[radius]; + + for (var k = 0; k < 256; k++){ + bm[k] = bk * k; + } + } + +} + +// Port of https://github.com/processing/processing/blob/ +// master/core/src/processing/core/PImage.java#L1433 +function blurARGB(canvas, radius) { + var pixels = Filters._toPixels(canvas); + var width = canvas.width; + var height = canvas.height; + var numPackedPixels = width * height; + var argb = new Int32Array(numPackedPixels); + for (var j = 0; j < numPackedPixels; j++) { + argb[j] = Filters._getARGB(pixels, j); + } + var sum, cr, cg, cb, ca; + var read, ri, ym, ymi, bk0; + var a2 = new Int32Array(numPackedPixels); + var r2 = new Int32Array(numPackedPixels); + var g2 = new Int32Array(numPackedPixels); + var b2 = new Int32Array(numPackedPixels); + var yi = 0; + buildBlurKernel(radius); + var x, y, i; + var bm; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + cb = cg = cr = ca = sum = 0; + read = x - blurRadius; + if (read < 0) { + bk0 = -read; + read = 0; + } else { + if (read >= width) { + break; + } + bk0 = 0; + } + for (i = bk0; i < blurKernelSize; i++) { + if (read >= width) { + break; + } + var c = argb[read + yi]; + bm = blurMult[i]; + ca += bm[(c & -16777216) >>> 24]; + cr += bm[(c & 16711680) >> 16]; + cg += bm[(c & 65280) >> 8]; + cb += bm[c & 255]; + sum += blurKernel[i]; + read++; + } + ri = yi + x; + a2[ri] = ca / sum; + r2[ri] = cr / sum; + g2[ri] = cg / sum; + b2[ri] = cb / sum; + } + yi += width; + } + yi = 0; + ym = -blurRadius; + ymi = ym * width; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + cb = cg = cr = ca = sum = 0; + if (ym < 0) { + bk0 = ri = -ym; + read = x; + } else { + if (ym >= height) { + break; + } + bk0 = 0; + ri = ym; + read = x + ymi; + } + for (i = bk0; i < blurKernelSize; i++) { + if (ri >= height) { + break; + } + bm = blurMult[i]; + ca += bm[a2[read]]; + cr += bm[r2[read]]; + cg += bm[g2[read]]; + cb += bm[b2[read]]; + sum += blurKernel[i]; + ri++; + read += width; + } + argb[x + yi] = (ca/sum)<<24 | (cr/sum)<<16 | (cg/sum)<<8 | (cb/sum); + } + yi += width; + ymi += width; + ym++; + } + Filters._setPixels(pixels, argb); +} + +Filters.blur = function(canvas, radius){ + blurARGB(canvas, radius); +}; + + +module.exports = Filters; + +},{}],66:[function(_dereq_,module,exports){ +/** + * @module Image + * @submodule Image + * @for p5 + * @requires core + */ + +/** + * This module defines the p5 methods for the p5.Image class + * for drawing images to the main display canvas. + */ +'use strict'; + + +var p5 = _dereq_('../core/core'); + +/* global frames:true */// This is not global, but JSHint is not aware that +// this module is implicitly enclosed with Browserify: this overrides the +// redefined-global error and permits using the name "frames" for the array +// of saved animation frames. +var frames = []; + + +/** + * Creates a new p5.Image (the datatype for storing images). This provides a + * fresh buffer of pixels to play with. Set the size of the buffer with the + * width and height parameters. + * <br><br> + * .pixels gives access to an array containing the values for all the pixels + * in the display window. + * These values are numbers. This array is the size (including an appropriate + * factor for the pixelDensity) of the display window x4, + * representing the R, G, B, A values in order for each pixel, moving from + * left to right across each row, then down each column. See .pixels for + * more info. It may also be simpler to use set() or get(). + * <br><br> + * Before accessing the pixels of an image, the data must loaded with the + * loadPixels() function. After the array data has been modified, the + * updatePixels() function must be run to update the changes. + * + * @method createImage + * @param {Integer} width width in pixels + * @param {Integer} height height in pixels + * @return {p5.Image} the p5.Image object + * @example + * <div> + * <code> + * img = createImage(66, 66); + * img.loadPixels(); + * for (i = 0; i < img.width; i++) { + * for (j = 0; j < img.height; j++) { + * img.set(i, j, color(0, 90, 102)); + * } + * } + * img.updatePixels(); + * image(img, 17, 17); + * </code> + * </div> + * + * <div> + * <code> + * img = createImage(66, 66); + * img.loadPixels(); + * for (i = 0; i < img.width; i++) { + * for (j = 0; j < img.height; j++) { + * img.set(i, j, color(0, 90, 102, i % img.width * 2)); + * } + * } + * img.updatePixels(); + * image(img, 17, 17); + * image(img, 34, 34); + * </code> + * </div> + * + * <div> + * <code> + * var pink = color(255, 102, 204); + * img = createImage(66, 66); + * img.loadPixels(); + * var d = pixelDensity; + * var halfImage = 4 * (width * d) * (height/2 * d); + * for (var i = 0; i < halfImage; i+=4) { + * img.pixels[i] = red(pink); + * img.pixels[i+1] = green(pink); + * img.pixels[i+2] = blue(pink); + * img.pixels[i+3] = alpha(pink); + * } + * img.updatePixels(); + * image(img, 17, 17); + * </code> + * </div> + */ +p5.prototype.createImage = function(width, height) { + return new p5.Image(width, height); +}; + +/** + * Save the current canvas as an image. In Safari, this will open the + * image in the window and the user must provide their own + * filename on save-as. Other browsers will either save the + * file immediately, or prompt the user with a dialogue window. + * + * @method saveCanvas + * @param {[selectedCanvas]} canvas a variable representing a + * specific html5 canvas (optional) + * @param {[String]} filename + * @param {[String]} extension 'jpg' or 'png' + * @example + * <div class='norender'><code> + * function setup() { + * var c = createCanvas(100, 100); + * background(255, 0, 0); + * saveCanvas(c, 'myCanvas', 'jpg'); + * } + * </code></div> + * <div class='norender'><code> + * // note that this example has the same result as above + * // if no canvas is specified, defaults to main canvas + * function setup() { + * createCanvas(100, 100); + * background(255, 0, 0); + * saveCanvas('myCanvas', 'jpg'); + * } + * </code></div> + * <div class='norender'><code> + * // all of the following are valid + * saveCanvas(c, 'myCanvas', 'jpg'); + * saveCanvas(c, 'myCanvas'); + * saveCanvas(c); + * saveCanvas('myCanvas', 'png'); + * saveCanvas('myCanvas'); + * saveCanvas(); + * </code></div> + */ +p5.prototype.saveCanvas = function() { + + var cnv, filename, extension; + if (arguments.length === 3) { + cnv = arguments[0]; + filename = arguments[1]; + extension = arguments[2]; + } else if (arguments.length === 2) { + if (typeof arguments[0] === 'object') { + cnv = arguments[0]; + filename = arguments[1]; + } else { + filename = arguments[0]; + extension = arguments[1]; + } + } else if (arguments.length === 1) { + if (typeof arguments[0] === 'object') { + cnv = arguments[0]; + } else { + filename = arguments[0]; + } + } + + if (cnv instanceof p5.Element) { + cnv = cnv.elt; + } + if (!(cnv instanceof HTMLCanvasElement)) { + cnv = null; + } + + if (!extension) { + extension = p5.prototype._checkFileExtension(filename, extension)[1]; + if (extension === '') { + extension = 'png'; + } + } + + if (!cnv) { + if (this._curElement && this._curElement.elt) { + cnv = this._curElement.elt; + } + } + + if ( p5.prototype._isSafari() ) { + var aText = 'Hello, Safari user!\n'; + aText += 'Now capturing a screenshot...\n'; + aText += 'To save this image,\n'; + aText += 'go to File --> Save As.\n'; + alert(aText); + window.location.href = cnv.toDataURL(); + } else { + var mimeType; + if (typeof(extension) === 'undefined') { + extension = 'png'; + mimeType = 'image/png'; + } + else { + switch(extension){ + case 'png': + mimeType = 'image/png'; + break; + case 'jpeg': + mimeType = 'image/jpeg'; + break; + case 'jpg': + mimeType = 'image/jpeg'; + break; + default: + mimeType = 'image/png'; + break; + } + } + var downloadMime = 'image/octet-stream'; + var imageData = cnv.toDataURL(mimeType); + imageData = imageData.replace(mimeType, downloadMime); + + p5.prototype.downloadFile(imageData, filename, extension); + } +}; + +/** + * Capture a sequence of frames that can be used to create a movie. + * Accepts a callback. For example, you may wish to send the frames + * to a server where they can be stored or converted into a movie. + * If no callback is provided, the browser will attempt to download + * all of the images that have just been created. + * + * @method saveFrames + * @param {String} filename + * @param {String} extension 'jpg' or 'png' + * @param {Number} duration Duration in seconds to save the frames for. + * @param {Number} framerate Framerate to save the frames in. + * @param {Function} [callback] A callback function that will be executed + to handle the image data. This function + should accept an array as argument. The + array will contain the spcecified number of + frames of objects. Each object have three + properties: imageData - an + image/octet-stream, filename and extension. + */ +p5.prototype.saveFrames = function(fName, ext, _duration, _fps, callback) { + var duration = _duration || 3; + duration = p5.prototype.constrain(duration, 0, 15); + duration = duration * 1000; + var fps = _fps || 15; + fps = p5.prototype.constrain(fps, 0, 22); + var count = 0; + + var makeFrame = p5.prototype._makeFrame; + var cnv = this._curElement.elt; + var frameFactory = setInterval(function(){ + makeFrame(fName + count, ext, cnv); + count++; + },1000/fps); + + setTimeout(function(){ + clearInterval(frameFactory); + if (callback) { + callback(frames); + } + else { + for (var i = 0; i < frames.length; i++) { + var f = frames[i]; + p5.prototype.downloadFile(f.imageData, f.filename, f.ext); + } + } + frames = []; // clear frames + }, duration + 0.01); +}; + +p5.prototype._makeFrame = function(filename, extension, _cnv) { + var cnv; + if (this) { + cnv = this._curElement.elt; + } else { + cnv = _cnv; + } + var mimeType; + if (!extension) { + extension = 'png'; + mimeType = 'image/png'; + } + else { + switch(extension.toLowerCase()){ + case 'png': + mimeType = 'image/png'; + break; + case 'jpeg': + mimeType = 'image/jpeg'; + break; + case 'jpg': + mimeType = 'image/jpeg'; + break; + default: + mimeType = 'image/png'; + break; + } + } + var downloadMime = 'image/octet-stream'; + var imageData = cnv.toDataURL(mimeType); + imageData = imageData.replace(mimeType, downloadMime); + + var thisFrame = {}; + thisFrame.imageData = imageData; + thisFrame.filename = filename; + thisFrame.ext = extension; + frames.push(thisFrame); +}; + +module.exports = p5; + +},{"../core/core":48}],67:[function(_dereq_,module,exports){ +/** + * @module Image + * @submodule Loading & Displaying + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var Filters = _dereq_('./filters'); +var canvas = _dereq_('../core/canvas'); +var constants = _dereq_('../core/constants'); + +_dereq_('../core/error_helpers'); + +/** + * Loads an image from a path and creates a p5.Image from it. + * <br><br> + * The image may not be immediately available for rendering + * If you want to ensure that the image is ready before doing + * anything with it, place the loadImage() call in preload(). + * You may also supply a callback function to handle the image when it's ready. + * <br><br> + * The path to the image should be relative to the HTML file + * that links in your sketch. Loading an from a URL or other + * remote location may be blocked due to your browser's built-in + * security. + * + * @method loadImage + * @param {String} path Path of the image to be loaded + * @param {Function(p5.Image)} [successCallback] Function to be called once + * the image is loaded. Will be passed the + * p5.Image. + * @param {Function(Event)} [failureCallback] called with event error if + * the image fails to load. + * @return {p5.Image} the p5.Image object + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/laDefense.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * } + * </code> + * </div> + * <div> + * <code> + * function setup() { + * // here we use a callback to display the image after loading + * loadImage("assets/laDefense.jpg", function(img) { + * image(img, 0, 0); + * }); + * } + * </code> + * </div> + */ +p5.prototype.loadImage = function(path, successCallback, failureCallback) { + var img = new Image(); + var pImg = new p5.Image(1, 1, this); + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + img.onload = function() { + pImg.width = pImg.canvas.width = img.width; + pImg.height = pImg.canvas.height = img.height; + + // Draw the image into the backing canvas of the p5.Image + pImg.drawingContext.drawImage(img, 0, 0); + + if (typeof successCallback === 'function') { + successCallback(pImg); + } + if (decrementPreload && (successCallback !== decrementPreload)) { + decrementPreload(); + } + }; + img.onerror = function(e) { + p5._friendlyFileLoadError(0,img.src); + // don't get failure callback mixed up with decrementPreload + if ((typeof failureCallback === 'function') && + (failureCallback !== decrementPreload)) { + failureCallback(e); + } + }; + + //set crossOrigin in case image is served which CORS headers + //this will let us draw to canvas without tainting it. + //see https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image + // When using data-uris the file will be loaded locally + // so we don't need to worry about crossOrigin with base64 file types + if(path.indexOf('data:image/') !== 0) { + img.crossOrigin = 'Anonymous'; + } + + //start loading the image + img.src = path; + + return pImg; +}; + +/** + * Validates clipping params. Per drawImage spec sWidth and sHight cannot be + * negative or greater than image intrinsic width and height + * @private + * @param {Number} sVal + * @param {Number} iVal + * @returns {Number} + * @private + */ +function _sAssign(sVal, iVal) { + if (sVal > 0 && sVal < iVal) { + return sVal; + } + else { + return iVal; + } +} + +/** + * Draw an image to the main canvas of the p5js sketch + * + * @method image + * @param {p5.Image} img the image to display + * @param {Number} [sx=0] The X coordinate of the top left corner of the + * sub-rectangle of the source image to draw into + * the destination canvas. + * @param {Number} [sy=0] The Y coordinate of the top left corner of the + * sub-rectangle of the source image to draw into + * the destination canvas. + * @param {Number} [sWidth=img.width] The width of the sub-rectangle of the + * source image to draw into the destination + * canvas. + * @param {Number} [sHeight=img.height] The height of the sub-rectangle of the + * source image to draw into the + * destination context. + * @param {Number} [dx=0] The X coordinate in the destination canvas at + * which to place the top-left corner of the + * source image. + * @param {Number} [dy=0] The Y coordinate in the destination canvas at + * which to place the top-left corner of the + * source image. + * @param {Number} [dWidth] The width to draw the image in the destination + * canvas. This allows scaling of the drawn image. + * @param {Number} [dHeight] The height to draw the image in the destination + * canvas. This allows scaling of the drawn image. + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/laDefense.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * image(img, 0, 0, 100, 100); + * image(img, 0, 0, 100, 100, 0, 0, 100, 100); + * } + * </code> + * </div> + * <div> + * <code> + * function setup() { + * // here we use a callback to display the image after loading + * loadImage("assets/laDefense.jpg", function(img) { + * image(img, 0, 0); + * }); + * } + * </code> + * </div> + */ +p5.prototype.image = + function(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) { + // Temporarily disabling until options for p5.Graphics are added. + // var args = new Array(arguments.length); + // for (var i = 0; i < args.length; ++i) { + // args[i] = arguments[i]; + // } + // this._validateParameters( + // 'image', + // args, + // [ + // ['p5.Image', 'Number', 'Number'], + // ['p5.Image', 'Number', 'Number', 'Number', 'Number'] + // ] + // ); + + // set defaults per spec: https://goo.gl/3ykfOq + if (arguments.length <= 5) { + dx = sx || 0; + dy = sy || 0; + sx = 0; + sy = 0; + if (img.elt && img.elt.videoWidth && !img.canvas) { // video no canvas + var actualW = img.elt.videoWidth; + var actualH = img.elt.videoHeight; + dWidth = sWidth || img.elt.width; + dHeight = sHeight || img.elt.width*actualH/actualW; + sWidth = actualW; + sHeight = actualH; + } else { + dWidth = sWidth || img.width; + dHeight = sHeight || img.height; + sWidth = img.width; + sHeight = img.height; + } + } else if (arguments.length === 9) { + sx = sx || 0; + sy = sy || 0; + sWidth = _sAssign(sWidth, img.width); + sHeight = _sAssign(sHeight, img.height); + + dx = dx || 0; + dy = dy || 0; + dWidth = dWidth || img.width; + dHeight = dHeight || img.height; + } else { + throw 'Wrong number of arguments to image()'; + } + + var vals = canvas.modeAdjust(dx, dy, dWidth, dHeight, + this._renderer._imageMode); + + // tint the image if there is a tint + this._renderer.image(img, sx, sy, sWidth, sHeight, vals.x, vals.y, vals.w, + vals.h); +}; + +/** + * Sets the fill value for displaying images. Images can be tinted to + * specified colors or made transparent by including an alpha value. + * <br><br> + * To apply transparency to an image without affecting its color, use + * white as the tint color and specify an alpha value. For instance, + * tint(255, 128) will make an image 50% transparent (assuming the default + * alpha range of 0-255, which can be changed with colorMode()). + * <br><br> + * The value for the gray parameter must be less than or equal to the current + * maximum value as specified by colorMode(). The default maximum value is + * 255. + * + * @method tint + * @param {Number|Array} v1 gray value, red or hue value (depending on the + * current color mode), or color Array + * @param {Number|Array} [v2] green or saturation value (depending on the + * current color mode) + * @param {Number|Array} [v3] blue or brightness value (depending on the + * current color mode) + * @param {Number|Array} [a] opacity of the background + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/laDefense.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * tint(0, 153, 204); // Tint blue + * image(img, 50, 0); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/laDefense.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * tint(0, 153, 204, 126); // Tint blue and set transparency + * image(img, 50, 0); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/laDefense.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * tint(255, 126); // Apply transparency without changing color + * image(img, 50, 0); + * } + * </code> + * </div> + */ +p5.prototype.tint = function () { + var c = this.color.apply(this, arguments); + this._renderer._tint = c.levels; +}; + +/** + * Removes the current fill value for displaying images and reverts to + * displaying images with their original hues. + * + * @method noTint + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * tint(0, 153, 204); // Tint blue + * image(img, 0, 0); + * noTint(); // Disable tint + * image(img, 50, 0); + * } + * </code> + * </div> + */ +p5.prototype.noTint = function() { + this._renderer._tint = null; +}; + +/** + * Apply the current tint color to the input image, return the resulting + * canvas. + * + * @param {p5.Image} The image to be tinted + * @return {canvas} The resulting tinted canvas + * + */ +p5.prototype._getTintedImageCanvas = function(img) { + if (!img.canvas) { + return img; + } + var pixels = Filters._toPixels(img.canvas); + var tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = img.canvas.width; + tmpCanvas.height = img.canvas.height; + var tmpCtx = tmpCanvas.getContext('2d'); + var id = tmpCtx.createImageData(img.canvas.width, img.canvas.height); + var newPixels = id.data; + + for(var i = 0; i < pixels.length; i += 4) { + var r = pixels[i]; + var g = pixels[i+1]; + var b = pixels[i+2]; + var a = pixels[i+3]; + + newPixels[i] = r*this._renderer._tint[0]/255; + newPixels[i+1] = g*this._renderer._tint[1]/255; + newPixels[i+2] = b*this._renderer._tint[2]/255; + newPixels[i+3] = a*this._renderer._tint[3]/255; + } + + tmpCtx.putImageData(id, 0, 0); + return tmpCanvas; +}; + +/** + * Set image mode. Modifies the location from which images are drawn by + * changing the way in which parameters given to image() are interpreted. + * The default mode is imageMode(CORNER), which interprets the second and + * third parameters of image() as the upper-left corner of the image. If + * two additional parameters are specified, they are used to set the image's + * width and height. + * <br><br> + * imageMode(CORNERS) interprets the second and third parameters of image() + * as the location of one corner, and the fourth and fifth parameters as the + * opposite corner. + * <br><br> + * imageMode(CENTER) interprets the second and third parameters of image() + * as the image's center point. If two additional parameters are specified, + * they are used to set the image's width and height. + * + * @method imageMode + * @param {String} m The mode: either CORNER, CORNERS, or CENTER. + * @example + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * imageMode(CORNER); + * image(img, 10, 10, 50, 50); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * imageMode(CORNERS); + * image(img, 10, 10, 90, 40); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * imageMode(CENTER); + * image(img, 50, 50, 80, 80); + * } + * </code> + * </div> + */ +p5.prototype.imageMode = function(m) { + if (m === constants.CORNER || + m === constants.CORNERS || + m === constants.CENTER) { + this._renderer._imageMode = m; + } +}; + + +module.exports = p5; + +},{"../core/canvas":46,"../core/constants":47,"../core/core":48,"../core/error_helpers":51,"./filters":65}],68:[function(_dereq_,module,exports){ +/** + * @module Image + * @submodule Image + * @requires core + * @requires constants + * @requires filters + */ + +/** + * This module defines the p5.Image class and P5 methods for + * drawing images to the main display canvas. + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var Filters = _dereq_('./filters'); + +/* + * Class methods + */ + +/** + * Creates a new p5.Image. A p5.Image is a canvas backed representation of an + * image. + * <br><br> + * p5 can display .gif, .jpg and .png images. Images may be displayed + * in 2D and 3D space. Before an image is used, it must be loaded with the + * loadImage() function. The p5.Image class contains fields for the width and + * height of the image, as well as an array called pixels[] that contains the + * values for every pixel in the image. + * <br><br> + * The methods described below allow easy access to the image's pixels and + * alpha channel and simplify the process of compositing. + * <br><br> + * Before using the pixels[] array, be sure to use the loadPixels() method on + * the image to make sure that the pixel data is properly loaded. + * + * @class p5.Image + * @constructor + * @param {Number} width + * @param {Number} height + * @param {Object} pInst An instance of a p5 sketch. + */ +p5.Image = function(width, height){ + /** + * Image width. + * @property width + * @example + * <div><code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * createCanvas(100, 100); + * image(img, 0, 0); + * for (var i=0; i < img.width; i++) { + * var c = img.get(i, img.height/2); + * stroke(c); + * line(i, height/2, i, height); + * } + * } + * </code></div> + */ + this.width = width; + /** + * Image height. + * @property height + * @example + * <div><code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * createCanvas(100, 100); + * image(img, 0, 0); + * for (var i=0; i < img.height; i++) { + * var c = img.get(img.width/2, i); + * stroke(c); + * line(0, i, width/2, i); + * } + * } + * </code></div> + */ + this.height = height; + this.canvas = document.createElement('canvas'); + this.canvas.width = this.width; + this.canvas.height = this.height; + this.drawingContext = this.canvas.getContext('2d'); + this._pixelDensity = 1; + //used for webgl texturing only + this.isTexture = false; + /** + * Array containing the values for all the pixels in the display window. + * These values are numbers. This array is the size (include an appropriate + * factor for pixelDensity) of the display window x4, + * representing the R, G, B, A values in order for each pixel, moving from + * left to right across each row, then down each column. Retina and other + * high denisty displays may have more pixels[] (by a factor of + * pixelDensity^2). + * For example, if the image is 100x100 pixels, there will be 40,000. With + * pixelDensity = 2, there will be 160,000. The first four values + * (indices 0-3) in the array will be the R, G, B, A values of the pixel at + * (0, 0). The second four values (indices 4-7) will contain the R, G, B, A + * values of the pixel at (1, 0). More generally, to set values for a pixel + * at (x, y): + * <code><pre>var d = pixelDensity; + * for (var i = 0; i < d; i++) { + * for (var j = 0; j < d; j++) { + * // loop over + * idx = 4*((y * d + j) * width * d + (x * d + i)); + * pixels[idx] = r; + * pixels[idx+1] = g; + * pixels[idx+2] = b; + * pixels[idx+3] = a; + * } + * } + * </pre></code> + * <br><br> + * Before accessing this array, the data must loaded with the loadPixels() + * function. After the array data has been modified, the updatePixels() + * function must be run to update the changes. + * @property pixels[] + * @example + * <div> + * <code> + * img = createImage(66, 66); + * img.loadPixels(); + * for (i = 0; i < img.width; i++) { + * for (j = 0; j < img.height; j++) { + * img.set(i, j, color(0, 90, 102)); + * } + * } + * img.updatePixels(); + * image(img, 17, 17); + * </code> + * </div> + * <div> + * <code> + * var pink = color(255, 102, 204); + * img = createImage(66, 66); + * img.loadPixels(); + * for (var i = 0; i < 4*(width*height/2); i+=4) { + * img.pixels[i] = red(pink); + * img.pixels[i+1] = green(pink); + * img.pixels[i+2] = blue(pink); + * img.pixels[i+3] = alpha(pink); + * } + * img.updatePixels(); + * image(img, 17, 17); + * </code> + * </div> + */ + this.pixels = []; +}; + +/** + * Helper fxn for sharing pixel methods + * + */ +p5.Image.prototype._setProperty = function (prop, value) { + this[prop] = value; +}; + +/** + * Loads the pixels data for this image into the [pixels] attribute. + * + * @method loadPixels + * @example + * <div><code> + * var myImage; + * var halfImage; + * + * function preload() { + * myImage = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * myImage.loadPixels(); + * halfImage = 4 * width * height/2; + * for(var i = 0; i < halfImage; i++){ + * myImage.pixels[i+halfImage] = myImage.pixels[i]; + * } + * myImage.updatePixels(); + * } + * + * function draw() { + * image(myImage, 0, 0); + * } + * </code></div> + */ +p5.Image.prototype.loadPixels = function(){ + p5.Renderer2D.prototype.loadPixels.call(this); +}; + +/** + * Updates the backing canvas for this image with the contents of + * the [pixels] array. + * + * @method updatePixels + * @param {Integer|undefined} x x-offset of the target update area for the + * underlying canvas + * @param {Integer|undefined} y y-offset of the target update area for the + * underlying canvas + * @param {Integer|undefined} w height of the target update area for the + * underlying canvas + * @param {Integer|undefined} h height of the target update area for the + * underlying canvas + * @example + * <div><code> + * var myImage; + * var halfImage; + * + * function preload() { + * myImage = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * myImage.loadPixels(); + * halfImage = 4 * width * height/2; + * for(var i = 0; i < halfImage; i++){ + * myImage.pixels[i+halfImage] = myImage.pixels[i]; + * } + * myImage.updatePixels(); + * } + * + * function draw() { + * image(myImage, 0, 0); + * } + * </code></div> + */ +p5.Image.prototype.updatePixels = function(x, y, w, h){ + p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h); +}; + +/** + * Get a region of pixels from an image. + * + * If no params are passed, those whole image is returned, + * if x and y are the only params passed a single pixel is extracted + * if all params are passed a rectangle region is extracted and a p5.Image + * is returned. + * + * Returns undefined if the region is outside the bounds of the image + * + * @method get + * @param {Number} [x] x-coordinate of the pixel + * @param {Number} [y] y-coordinate of the pixel + * @param {Number} [w] width + * @param {Number} [h] height + * @return {Array/Color | p5.Image} color of pixel at x,y in array format + * [R, G, B, A] or p5.Image + * @example + * <div><code> + * var myImage; + * var c; + * + * function preload() { + * myImage = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * background(myImage); + * noStroke(); + * c = myImage.get(60, 90); + * fill(c); + * rect(25, 25, 50, 50); + * } + * + * //get() returns color here + * </code></div> + */ +p5.Image.prototype.get = function(x, y, w, h){ + return p5.Renderer2D.prototype.get.call(this, x, y, w, h); +}; + +/** + * Set the color of a single pixel or write an image into + * this p5.Image. + * + * Note that for a large number of pixels this will + * be slower than directly manipulating the pixels array + * and then calling updatePixels(). + * + * @method set + * @param {Number} x x-coordinate of the pixel + * @param {Number} y y-coordinate of the pixel + * @param {Number|Array|Object} a grayscale value | pixel array | + * a p5.Color | image to copy + * @example + * <div> + * <code> + * img = createImage(66, 66); + * img.loadPixels(); + * for (i = 0; i < img.width; i++) { + * for (j = 0; j < img.height; j++) { + * img.set(i, j, color(0, 90, 102, i % img.width * 2)); + * } + * } + * img.updatePixels(); + * image(img, 17, 17); + * image(img, 34, 34); + * </code> + * </div> + */ +p5.Image.prototype.set = function(x, y, imgOrCol){ + p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol); +}; + +/** + * Resize the image to a new width and height. To make the image scale + * proportionally, use 0 as the value for the wide or high parameter. + * For instance, to make the width of an image 150 pixels, and change + * the height using the same proportion, use resize(150, 0). + * + * @method resize + * @param {Number} width the resized image width + * @param {Number} height the resized image height + * @example + * <div><code> + * var img; + * + * function setup() { + * img = loadImage("assets/rockies.jpg"); + * } + + * function draw() { + * image(img, 0, 0); + * } + * + * function mousePressed() { + * img.resize(50, 100); + * } + * </code></div> + */ +p5.Image.prototype.resize = function(width, height){ + + // Copy contents to a temporary canvas, resize the original + // and then copy back. + // + // There is a faster approach that involves just one copy and swapping the + // this.canvas reference. We could switch to that approach if (as i think + // is the case) there an expectation that the user would not hold a + // reference to the backing canvas of a p5.Image. But since we do not + // enforce that at the moment, I am leaving in the slower, but safer + // implementation. + + // auto-resize + if (width === 0 && height === 0) { + width = this.canvas.width; + height = this.canvas.height; + } else if (width === 0) { + width = this.canvas.width * height / this.canvas.height; + } else if (height === 0) { + height = this.canvas.height * width / this.canvas.width; + } + + var tempCanvas = document.createElement('canvas'); + tempCanvas.width = width; + tempCanvas.height = height; + tempCanvas.getContext('2d').drawImage(this.canvas, + 0, 0, this.canvas.width, this.canvas.height, + 0, 0, tempCanvas.width, tempCanvas.height + ); + + + // Resize the original canvas, which will clear its contents + this.canvas.width = this.width = width; + this.canvas.height = this.height = height; + + //Copy the image back + + this.drawingContext.drawImage(tempCanvas, + 0, 0, width, height, + 0, 0, width, height + ); + + if(this.pixels.length > 0){ + this.loadPixels(); + } +}; + +/** + * Copies a region of pixels from one image to another. If no + * srcImage is specified this is used as the source. If the source + * and destination regions aren't the same size, it will + * automatically resize source pixels to fit the specified + * target region. + * + * @method copy + * @param {p5.Image|undefined} srcImage source image + * @param {Integer} sx X coordinate of the source's upper left corner + * @param {Integer} sy Y coordinate of the source's upper left corner + * @param {Integer} sw source image width + * @param {Integer} sh source image height + * @param {Integer} dx X coordinate of the destination's upper left corner + * @param {Integer} dy Y coordinate of the destination's upper left corner + * @param {Integer} dw destination image width + * @param {Integer} dh destination image height + * @example + * <div><code> + * var photo; + * var bricks; + * var x; + * var y; + * + * function preload() { + * photo = loadImage("assets/rockies.jpg"); + * bricks = loadImage("assets/bricks.jpg"); + * } + * + * function setup() { + * x = bricks.width/2; + * y = bricks.height/2; + * photo.copy(bricks, 0, 0, x, y, 0, 0, x, y); + * image(photo, 0, 0); + * } + * </code></div> + */ +p5.Image.prototype.copy = function () { + p5.prototype.copy.apply(this, arguments); +}; + +/** + * Masks part of an image from displaying by loading another + * image and using it's blue channel as an alpha channel for + * this image. + * + * @method mask + * @param {p5.Image} srcImage source image + * @example + * <div><code> + * var photo, maskImage; + * function preload() { + * photo = loadImage("assets/rockies.jpg"); + * maskImage = loadImage("assets/mask2.png"); + * } + * + * function setup() { + * createCanvas(100, 100); + * photo.mask(maskImage); + * image(photo, 0, 0); + * } + * </code></div> + * + * http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/ + * + */ +// TODO: - Accept an array of alpha values. +// - Use other channels of an image. p5 uses the +// blue channel (which feels kind of arbitrary). Note: at the +// moment this method does not match native processings original +// functionality exactly. +p5.Image.prototype.mask = function(p5Image) { + if(p5Image === undefined){ + p5Image = this; + } + var currBlend = this.drawingContext.globalCompositeOperation; + + var scaleFactor = 1; + if (p5Image instanceof p5.Renderer) { + scaleFactor = p5Image._pInst._pixelDensity; + } + + var copyArgs = [ + p5Image, + 0, + 0, + scaleFactor*p5Image.width, + scaleFactor*p5Image.height, + 0, + 0, + this.width, + this.height + ]; + + this.drawingContext.globalCompositeOperation = 'destination-in'; + this.copy.apply(this, copyArgs); + this.drawingContext.globalCompositeOperation = currBlend; +}; + +/** + * Applies an image filter to a p5.Image + * + * @method filter + * @param {String} operation one of threshold, gray, invert, posterize and + * opaque see Filters.js for docs on each available + * filter + * @param {Number|undefined} value + * @example + * <div><code> + * var photo1; + * var photo2; + * + * function preload() { + * photo1 = loadImage("assets/rockies.jpg"); + * photo2 = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * photo2.filter("gray"); + * image(photo1, 0, 0); + * image(photo2, width/2, 0); + * } + * </code></div> + */ +p5.Image.prototype.filter = function(operation, value) { + Filters.apply(this.canvas, Filters[operation.toLowerCase()], value); +}; + +/** + * Copies a region of pixels from one image to another, using a specified + * blend mode to do the operation. + * + * @method blend + * @param {p5.Image|undefined} srcImage source image + * @param {Integer} sx X coordinate of the source's upper left corner + * @param {Integer} sy Y coordinate of the source's upper left corner + * @param {Integer} sw source image width + * @param {Integer} sh source image height + * @param {Integer} dx X coordinate of the destination's upper left corner + * @param {Integer} dy Y coordinate of the destination's upper left corner + * @param {Integer} dw destination image width + * @param {Integer} dh destination image height + * @param {Integer} blendMode the blend mode + * + * Available blend modes are: normal | multiply | screen | overlay | + * darken | lighten | color-dodge | color-burn | hard-light | + * soft-light | difference | exclusion | hue | saturation | + * color | luminosity + * + * + * http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/ + * + */ +p5.Image.prototype.blend = function() { + p5.prototype.blend.apply(this, arguments); +}; + +/** + * Saves the image to a file and force the browser to download it. + * Accepts two strings for filename and file extension + * Supports png (default) and jpg. + * + * @method save + * @param {String} filename give your file a name + * @param {String} extension 'png' or 'jpg' + * @example + * <div><code> + * var photo; + * + * function preload() { + * photo = loadImage("assets/rockies.jpg"); + * } + * + * function draw() { + * image(photo, 0, 0); + * } + * + * function keyTyped() { + * if (key == 's') { + * photo.save("photo", "png"); + * } + * } + * </code></div> + */ +p5.Image.prototype.save = function(filename, extension) { + var mimeType; + if (!extension) { + extension = 'png'; + mimeType = 'image/png'; + } + else { + // en.wikipedia.org/wiki/Comparison_of_web_browsers#Image_format_support + switch(extension.toLowerCase()){ + case 'png': + mimeType = 'image/png'; + break; + case 'jpeg': + mimeType = 'image/jpeg'; + break; + case 'jpg': + mimeType = 'image/jpeg'; + break; + default: + mimeType = 'image/png'; + break; + } + } + var downloadMime = 'image/octet-stream'; + var imageData = this.canvas.toDataURL(mimeType); + imageData = imageData.replace(mimeType, downloadMime); + + //Make the browser download the file + p5.prototype.downloadFile(imageData, filename, extension); +}; + +/** + * creates a gl texture + * used in WEBGL mode only + * @param {[type]} tex [description] + * @return {[type]} [description] + */ +p5.Image.prototype.createTexture = function(tex){ + //this.texture = tex; + return this; +}; + +module.exports = p5.Image; + +},{"../core/core":48,"./filters":65}],69:[function(_dereq_,module,exports){ +/** + * @module Image + * @submodule Pixels + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var Filters = _dereq_('./filters'); +_dereq_('../color/p5.Color'); + +/** + * <a href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference + * /Global_Objects/Uint8ClampedArray' target='_blank'>Uint8ClampedArray</a> + * containing the values for all the pixels in the display window. + * These values are numbers. This array is the size (include an appropriate + * factor for pixelDensity) of the display window x4, + * representing the R, G, B, A values in order for each pixel, moving from + * left to right across each row, then down each column. Retina and other + * high denisty displays will have more pixels[] (by a factor of + * pixelDensity^2). + * For example, if the image is 100x100 pixels, there will be 40,000. On a + * retina display, there will be 160,000. + * <br><br> + * The first four values (indices 0-3) in the array will be the R, G, B, A + * values of the pixel at (0, 0). The second four values (indices 4-7) will + * contain the R, G, B, A values of the pixel at (1, 0). More generally, to + * set values for a pixel at (x, y): + * <code><pre> + * var d = pixelDensity; + * for (var i = 0; i < d; i++) { + * for (var j = 0; j < d; j++) { + * // loop over + * idx = 4 * ((y * d + j) * width * d + (x * d + i)); + * pixels[idx] = r; + * pixels[idx+1] = g; + * pixels[idx+2] = b; + * pixels[idx+3] = a; + * } + * } + * </pre></code> + * + * <p>While the above method is complex, it is flexible enough to work with + * any pixelDensity. Note that set() will automatically take care of + * setting all the appropriate values in pixels[] for a given (x, y) at + * any pixelDensity, but the performance may not be as fast when lots of + * modifications are made to the pixel array. + * <br><br> + * Before accessing this array, the data must loaded with the loadPixels() + * function. After the array data has been modified, the updatePixels() + * function must be run to update the changes. + * <br><br> + * Note that this is not a standard javascript array. This means that + * standard javascript functions such as <code>slice()</code> or + * <code>arrayCopy()</code> do not + * work.</p> + * + * @property pixels[] + * @example + * <div> + * <code> + * var pink = color(255, 102, 204); + * loadPixels(); + * var d = pixelDensity(); + * var halfImage = 4 * (width * d) * (height/2 * d); + * for (var i = 0; i < halfImage; i+=4) { + * pixels[i] = red(pink); + * pixels[i+1] = green(pink); + * pixels[i+2] = blue(pink); + * pixels[i+3] = alpha(pink); + * } + * updatePixels(); + * </code> + * </div> + */ +p5.prototype.pixels = []; + +/** + * Copies a region of pixels from one image to another, using a specified + * blend mode to do the operation.<br><br> + * Available blend modes are: BLEND | DARKEST | LIGHTEST | DIFFERENCE | + * MULTIPLY| EXCLUSION | SCREEN | REPLACE | OVERLAY | HARD_LIGHT | + * SOFT_LIGHT | DODGE | BURN | ADD | NORMAL + * + * + * @method blend + * @param {p5.Image|undefined} srcImage source image + * @param {Integer} sx X coordinate of the source's upper left corner + * @param {Integer} sy Y coordinate of the source's upper left corner + * @param {Integer} sw source image width + * @param {Integer} sh source image height + * @param {Integer} dx X coordinate of the destination's upper left corner + * @param {Integer} dy Y coordinate of the destination's upper left corner + * @param {Integer} dw destination image width + * @param {Integer} dh destination image height + * @param {Integer} blendMode the blend mode + * + * @example + * <div><code> + * var img0; + * var img1; + * + * function preload() { + * img0 = loadImage("assets/rockies.jpg"); + * img1 = loadImage("assets/bricks_third.jpg"); + * } + * + * function setup() { + * background(img0); + * image(img1, 0, 0); + * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST); + * } + * </code></div> + * <div><code> + * var img0; + * var img1; + * + * function preload() { + * img0 = loadImage("assets/rockies.jpg"); + * img1 = loadImage("assets/bricks_third.jpg"); + * } + * + * function setup() { + * background(img0); + * image(img1, 0, 0); + * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST); + * } + * </code></div> + * <div><code> + * var img0; + * var img1; + * + * function preload() { + * img0 = loadImage("assets/rockies.jpg"); + * img1 = loadImage("assets/bricks_third.jpg"); + * } + * + * function setup() { + * background(img0); + * image(img1, 0, 0); + * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, ADD); + * } + * </code></div> + */ +p5.prototype.blend = function() { + this._renderer.blend.apply(this._renderer, arguments); +}; + +/** + * Copies a region of the canvas to another region of the canvas + * and copies a region of pixels from an image used as the srcImg parameter + * into the canvas srcImage is specified this is used as the source. If + * the source and destination regions aren't the same size, it will + * automatically resize source pixels to fit the specified + * target region. + * + * @method copy + * @param {p5.Image|undefined} srcImage source image + * @param {Integer} sx X coordinate of the source's upper left corner + * @param {Integer} sy Y coordinate of the source's upper left corner + * @param {Integer} sw source image width + * @param {Integer} sh source image height + * @param {Integer} dx X coordinate of the destination's upper left corner + * @param {Integer} dy Y coordinate of the destination's upper left corner + * @param {Integer} dw destination image width + * @param {Integer} dh destination image height + * + * @example + * <div><code> + * var img; + * + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * background(img); + * copy(img, 7, 22, 10, 10, 35, 25, 50, 50); + * stroke(255); + * noFill(); + * // Rectangle shows area being copied + * rect(7, 22, 10, 10); + * } + * </code></div> + */ +p5.prototype.copy = function () { + p5.Renderer2D._copyHelper.apply(this, arguments); +}; + +/** + * Applies a filter to the canvas. + * <br><br> + * + * The presets options are: + * <br><br> + * + * THRESHOLD + * Converts the image to black and white pixels depending if they are above or + * below the threshold defined by the level parameter. The parameter must be + * between 0.0 (black) and 1.0 (white). If no level is specified, 0.5 is used. + * <br><br> + * + * GRAY + * Converts any colors in the image to grayscale equivalents. No parameter + * is used. + * <br><br> + * + * OPAQUE + * Sets the alpha channel to entirely opaque. No parameter is used. + * <br><br> + * + * INVERT + * Sets each pixel to its inverse value. No parameter is used. + * <br><br> + * + * POSTERIZE + * Limits each channel of the image to the number of colors specified as the + * parameter. The parameter can be set to values between 2 and 255, but + * results are most noticeable in the lower ranges. + * <br><br> + * + * BLUR + * Executes a Guassian blur with the level parameter specifying the extent + * of the blurring. If no parameter is used, the blur is equivalent to + * Guassian blur of radius 1. Larger values increase the blur. + * <br><br> + * + * ERODE + * Reduces the light areas. No parameter is used. + * <br><br> + * + * DILATE + * Increases the light areas. No parameter is used. + * + * @method filter + * @param {String} filterType + * @param {Number} filterParam an optional parameter unique + * to each filter, see above + * + * + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(THRESHOLD); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(GRAY); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(OPAQUE); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(INVERT); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(POSTERIZE,3); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(DILATE); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(BLUR,3); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(ERODE); + * } + * </code> + * </div> + */ +p5.prototype.filter = function(operation, value) { + Filters.apply(this.canvas, Filters[operation.toLowerCase()], value); +}; + +/** + * Returns an array of [R,G,B,A] values for any pixel or grabs a section of + * an image. If no parameters are specified, the entire image is returned. + * Use the x and y parameters to get the value of one pixel. Get a section of + * the display window by specifying additional w and h parameters. When + * getting an image, the x and y parameters define the coordinates for the + * upper-left corner of the image, regardless of the current imageMode(). + * <br><br> + * If the pixel requested is outside of the image window, [0,0,0,255] is + * returned. To get the numbers scaled according to the current color ranges + * and taking into account colorMode, use getColor instead of get. + * <br><br> + * Getting the color of a single pixel with get(x, y) is easy, but not as fast + * as grabbing the data directly from pixels[]. The equivalent statement to + * get(x, y) using pixels[] with pixel density d is + * <code>[pixels[(y*width*d+x)*d], + * pixels[(y*width*d+x)*d+1], + * pixels[(y*width*d+x)*d+2], + * pixels[(y*width*d+x)*d+3]]</code>. + * <br><br> + * See the reference for pixels[] for more information. + * + * @method get + * @param {Number} [x] x-coordinate of the pixel + * @param {Number} [y] y-coordinate of the pixel + * @param {Number} [w] width + * @param {Number} [h] height + * @return {Array|p5.Image} values of pixel at x,y in array format + * [R, G, B, A] or p5.Image + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * var c = get(); + * image(c, width/2, 0); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * var c = get(50, 90); + * fill(c); + * noStroke(); + * rect(25, 25, 50, 50); + * } + * </code> + * </div> + */ +p5.prototype.get = function(x, y, w, h){ + return this._renderer.get(x, y, w, h); +}; + +/** + * Loads the pixel data for the display window into the pixels[] array. This + * function must always be called before reading from or writing to pixels[]. + * + * @method loadPixels + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * image(img, 0, 0); + * var d = pixelDensity(); + * var halfImage = 4 * (img.width * d) * + (img.height/2 * d); + * loadPixels(); + * for (var i = 0; i < halfImage; i++) { + * pixels[i+halfImage] = pixels[i]; + * } + * updatePixels(); + * } + * </code> + * </div> + */ +p5.prototype.loadPixels = function() { + this._renderer.loadPixels(); +}; + +/** + * <p>Changes the color of any pixel, or writes an image directly to the + * display window.</p> + * <p>The x and y parameters specify the pixel to change and the c parameter + * specifies the color value. This can be a p5.Color object, or [R, G, B, A] + * pixel array. It can also be a single grayscale value. + * When setting an image, the x and y parameters define the coordinates for + * the upper-left corner of the image, regardless of the current imageMode(). + * </p> + * <p> + * After using set(), you must call updatePixels() for your changes to + * appear. This should be called once all pixels have been set. + * </p> + * <p>Setting the color of a single pixel with set(x, y) is easy, but not as + * fast as putting the data directly into pixels[]. Setting the pixels[] + * values directly may be complicated when working with a retina display, + * but will perform better when lots of pixels need to be set directly on + * every loop.</p> + * <p>See the reference for pixels[] for more information.</p> + * + * @method set + * @param {Number} x x-coordinate of the pixel + * @param {Number} y y-coordinate of the pixel + * @param {Number|Array|Object} c insert a grayscale value | a pixel array | + * a p5.Color object | a p5.Image to copy + * @example + * <div> + * <code> + * var black = color(0); + * set(30, 20, black); + * set(85, 20, black); + * set(85, 75, black); + * set(30, 75, black); + * updatePixels(); + * </code> + * </div> + * + * <div> + * <code> + * for (var i = 30; i < width-15; i++) { + * for (var j = 20; j < height-25; j++) { + * var c = color(204-j, 153-i, 0); + * set(i, j, c); + * } + * } + * updatePixels(); + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * set(0, 0, img); + * updatePixels(); + * line(0, 0, width, height); + * line(0, height, width, 0); + * } + * </code> + * </div> + */ +p5.prototype.set = function (x, y, imgOrCol) { + this._renderer.set(x, y, imgOrCol); +}; +/** + * Updates the display window with the data in the pixels[] array. + * Use in conjunction with loadPixels(). If you're only reading pixels from + * the array, there's no need to call updatePixels() — updating is only + * necessary to apply changes. updatePixels() should be called anytime the + * pixels array is manipulated or set() is called. + * + * @method updatePixels + * @param {Number} [x] x-coordinate of the upper-left corner of region + * to update + * @param {Number} [y] y-coordinate of the upper-left corner of region + * to update + * @param {Number} [w] width of region to update + * @param {Number} [w] height of region to update + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * image(img, 0, 0); + * var halfImage = 4 * (img.width * pixelDensity()) * + * (img.height * pixelDensity()/2); + * loadPixels(); + * for (var i = 0; i < halfImage; i++) { + * pixels[i+halfImage] = pixels[i]; + * } + * updatePixels(); + * } + * </code> + * </div> + */ +p5.prototype.updatePixels = function (x, y, w, h) { + // graceful fail - if loadPixels() or set() has not been called, pixel + // array will be empty, ignore call to updatePixels() + if (this.pixels.length === 0) { + return; + } + this._renderer.updatePixels(x, y, w, h); +}; + +module.exports = p5; + +},{"../color/p5.Color":42,"../core/core":48,"./filters":65}],70:[function(_dereq_,module,exports){ +/** + * @module IO + * @submodule Input + * @for p5 + * @requires core + * @requires reqwest + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var reqwest = _dereq_('reqwest'); +var opentype = _dereq_('opentype.js'); +_dereq_('../core/error_helpers'); + +/** + * Checks if we are in preload and returns the last arg which will be the + * _decrementPreload function if called from a loadX() function. Should + * only be used in loadX() functions. + * @private + */ +p5._getDecrementPreload = function () { + var decrementPreload = arguments[arguments.length - 1]; + + // when in preload decrementPreload will always be the last arg as it is set + // with args.push() before invocation in _wrapPreload + if ((window.preload || (this && this.preload)) && + typeof decrementPreload === 'function') { + return decrementPreload; + } else { + return null; + } +}; + +/** + * Loads an opentype font file (.otf, .ttf) from a file or a URL, + * and returns a PFont Object. This method is asynchronous, + * meaning it may not finish before the next line in your sketch + * is executed. + * <br><br> + * The path to the font should be relative to the HTML file + * that links in your sketch. Loading an from a URL or other + * remote location may be blocked due to your browser's built-in + * security. + * + * @method loadFont + * @param {String} path name of the file or url to load + * @param {Function} [callback] function to be executed after + * loadFont() + * completes + * @return {Object} p5.Font object + * @example + * + * <p>Calling loadFont() inside preload() guarantees that the load + * operation will have completed before setup() and draw() are called.</p> + * + * <div><code> + * var myFont; + * function preload() { + * myFont = loadFont('assets/AvenirNextLTPro-Demi.otf'); + * } + * + * function setup() { + * fill('#ED225D'); + * textFont(myFont); + * textSize(36); + * text('p5*js', 10, 50); + * } + * </code></div> + * + * <p>Outside of preload(), you may supply a callback function to handle the + * object:</p> + * + * <div><code> + * function setup() { + * loadFont('assets/AvenirNextLTPro-Demi.otf', drawText); + * } + * + * function drawText(font) { + * fill('#ED225D'); + * textFont(font, 36); + * text('p5*js', 10, 50); + * } + * + * </code></div> + * + * <p>You can also use the string name of the font to style other HTML + * elements.</p> + * + * <div><code> + * var myFont; + * + * function preload() { + * myFont = loadFont('assets/Avenir.otf'); + * } + * + * function setup() { + * var myDiv = createDiv('hello there'); + * myDiv.style('font-family', 'Avenir'); + * } + * </code></div> + */ +p5.prototype.loadFont = function (path, onSuccess, onError) { + + var p5Font = new p5.Font(this); + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + opentype.load(path, function (err, font) { + + if (err) { + + if ((typeof onError !== 'undefined') && (onError !== decrementPreload)) { + return onError(err); + } + throw err; + } + + p5Font.font = font; + + if (typeof onSuccess !== 'undefined') { + onSuccess(p5Font); + } + + if (decrementPreload && (onSuccess !== decrementPreload)) { + decrementPreload(); + } + + // check that we have an acceptable font type + var validFontTypes = [ 'ttf', 'otf', 'woff', 'woff2' ], + fileNoPath = path.split('\\').pop().split('/').pop(), + lastDotIdx = fileNoPath.lastIndexOf('.'), fontFamily, newStyle, + fileExt = lastDotIdx < 1 ? null : fileNoPath.substr(lastDotIdx + 1); + + // if so, add it to the DOM (name-only) for use with p5.dom + if (validFontTypes.indexOf(fileExt) > -1) { + + fontFamily = fileNoPath.substr(0, lastDotIdx); + newStyle = document.createElement('style'); + newStyle.appendChild(document.createTextNode('\n@font-face {' + + '\nfont-family: ' + fontFamily + ';\nsrc: url(' + path + ');\n}\n')); + document.head.appendChild(newStyle); + } + + }); + + return p5Font; +}; + +//BufferedReader +p5.prototype.createInput = function () { + // TODO + throw 'not yet implemented'; +}; + +p5.prototype.createReader = function () { + // TODO + throw 'not yet implemented'; +}; + +p5.prototype.loadBytes = function () { + // TODO + throw 'not yet implemented'; +}; + +/** + * Loads a JSON file from a file or a URL, and returns an Object or Array. + * This method is asynchronous, meaning it may not finish before the next + * line in your sketch is executed. + * + * @method loadJSON + * @param {String} path name of the file or url to load + * @param {Function} [callback] function to be executed after + * loadJSON() completes, data is passed + * in as first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + * @param {String} [datatype] "json" or "jsonp" + * @return {Object|Array} JSON data + * @example + * + * <p>Calling loadJSON() inside preload() guarantees to complete the + * operation before setup() and draw() are called.</p> + * + * <div><code> + * var weather; + * function preload() { + * var url = 'http://api.openweathermap.org/data/2.5/weather?q=London,UK'+ + * '&APPID=7bbbb47522848e8b9c26ba35c226c734'; + * weather = loadJSON(url); + * } + * + * function setup() { + * noLoop(); + * } + * + * function draw() { + * background(200); + * // get the humidity value out of the loaded JSON + * var humidity = weather.main.humidity; + * fill(0, humidity); // use the humidity value to set the alpha + * ellipse(width/2, height/2, 50, 50); + * } + * </code></div> + * + * + * <p>Outside of preload(), you may supply a callback function to handle the + * object:</p> + * <div><code> + * function setup() { + * noLoop(); + * var url = 'http://api.openweathermap.org/data/2.5/weather?q=NewYork'+ + * '&APPID=7bbbb47522848e8b9c26ba35c226c734'; + * loadJSON(url, drawWeather); + * } + * + * function draw() { + * background(200); + * } + * + * function drawWeather(weather) { + * // get the humidity value out of the loaded JSON + * var humidity = weather.main.humidity; + * fill(0, humidity); // use the humidity value to set the alpha + * ellipse(width/2, height/2, 50, 50); + * } + * </code></div> + * + */ +p5.prototype.loadJSON = function () { + var path = arguments[0]; + var callback = arguments[1]; + var errorCallback; + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + var ret = []; // array needed for preload + // assume jsonp for URLs + var t = 'json'; //= path.indexOf('http') === -1 ? 'json' : 'jsonp'; + + // check for explicit data type argument + for (var i = 2; i < arguments.length; i++) { + var arg = arguments[i]; + if (typeof arg === 'string') { + if (arg === 'jsonp' || arg === 'json') { + t = arg; + } + } else if (typeof arg === 'function') { + errorCallback = arg; + } + } + + reqwest({ + url: path, + type: t, + crossOrigin: true, + error: function (resp) { + // pass to error callback if defined + if (errorCallback) { + errorCallback(resp); + } else { // otherwise log error msg + console.log(resp.statusText); + } + }, + success: function (resp) { + for (var k in resp) { + ret[k] = resp[k]; + } + if (typeof callback !== 'undefined') { + callback(resp); + } + if (decrementPreload && (callback !== decrementPreload)) { + decrementPreload(); + } + } + }); + + return ret; +}; + +/** + * Reads the contents of a file and creates a String array of its individual + * lines. If the name of the file is used as the parameter, as in the above + * example, the file must be located in the sketch directory/folder. + * <br><br> + * Alternatively, the file maybe be loaded from anywhere on the local + * computer using an absolute path (something that starts with / on Unix and + * Linux, or a drive letter on Windows), or the filename parameter can be a + * URL for a file found on a network. + * <br><br> + * This method is asynchronous, meaning it may not finish before the next + * line in your sketch is executed. + * + * @method loadStrings + * @param {String} filename name of the file or url to load + * @param {Function} [callback] function to be executed after loadStrings() + * completes, Array is passed in as first + * argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + * @return {Array} Array of Strings + * @example + * + * <p>Calling loadStrings() inside preload() guarantees to complete the + * operation before setup() and draw() are called.</p> + * + * <div><code> + * var result; + * function preload() { + * result = loadStrings('assets/test.txt'); + * } + + * function setup() { + * background(200); + * var ind = floor(random(result.length)); + * text(result[ind], 10, 10, 80, 80); + * } + * </code></div> + * + * <p>Outside of preload(), you may supply a callback function to handle the + * object:</p> + * + * <div><code> + * function setup() { + * loadStrings('assets/test.txt', pickString); + * } + * + * function pickString(result) { + * background(200); + * var ind = floor(random(result.length)); + * text(result[ind], 10, 10, 80, 80); + * } + * </code></div> + */ +p5.prototype.loadStrings = function (path, callback, errorCallback) { + var ret = []; + var req = new XMLHttpRequest(); + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + req.addEventListener('error', function (resp) { + if (errorCallback) { + errorCallback(resp); + } else { + console.log(resp.responseText); + } + }); + + req.open('GET', path, true); + req.onreadystatechange = function () { + if (req.readyState === 4) { + if (req.status === 200) { + var arr = req.responseText.match(/[^\r\n]+/g); + for (var k in arr) { + ret[k] = arr[k]; + } + if (typeof callback !== 'undefined') { + callback(ret); + } + if (decrementPreload && (callback !== decrementPreload)) { + decrementPreload(); + } + } else { + if (errorCallback) { + errorCallback(req); + } else { + console.log(req.statusText); + } + //p5._friendlyFileLoadError(3, path); + } + } + }; + req.send(null); + return ret; +}; + +/** + * <p>Reads the contents of a file or URL and creates a p5.Table object with + * its values. If a file is specified, it must be located in the sketch's + * "data" folder. The filename parameter can also be a URL to a file found + * online. By default, the file is assumed to be comma-separated (in CSV + * format). Table only looks for a header row if the 'header' option is + * included.</p> + * + * <p>Possible options include: + * <ul> + * <li>csv - parse the table as comma-separated values</li> + * <li>tsv - parse the table as tab-separated values</li> + * <li>header - this table has a header (title) row</li> + * </ul> + * </p> + * + * <p>When passing in multiple options, pass them in as separate parameters, + * seperated by commas. For example: + * <br><br> + * <code> + * loadTable("my_csv_file.csv", "csv", "header") + * </code> + * </p> + * + * <p> All files loaded and saved use UTF-8 encoding.</p> + * + * <p>This method is asynchronous, meaning it may not finish before the next + * line in your sketch is executed. Calling loadTable() inside preload() + * guarantees to complete the operation before setup() and draw() are called. + * <p>Outside of preload(), you may supply a callback function to handle the + * object:</p> + * </p> + * + * @method loadTable + * @param {String} filename name of the file or URL to load + * @param {String|Strings} [options] "header" "csv" "tsv" + * @param {Function} [callback] function to be executed after + * loadTable() completes, Table object is + * passed in as first argument + * @return {Object} Table object containing data + * + * @example + * <div class="norender"> + * <code> + * // Given the following CSV file called "mammals.csv" + * // located in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * //the file can be remote + * //table = loadTable("http://p5js.org/reference/assets/mammals.csv", + * // "csv", "header"); + * } + * + * function setup() { + * //count the columns + * print(table.getRowCount() + " total rows in table"); + * print(table.getColumnCount() + " total columns in table"); + * + * print(table.getColumn("name")); + * //["Goat", "Leopard", "Zebra"] + * + * //cycle through the table + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) { + * print(table.getString(r, c)); + * } + * } + * </code> + * </div> + */ +p5.prototype.loadTable = function (path) { + var callback = null; + var options = []; + var header = false; + var sep = ','; + var separatorSet = false; + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + for (var i = 1; i < arguments.length; i++) { + if ((typeof (arguments[i]) === 'function') && + (arguments[i] !== decrementPreload)) { + callback = arguments[i]; + } else if (typeof (arguments[i]) === 'string') { + options.push(arguments[i]); + if (arguments[i] === 'header') { + header = true; + } + if (arguments[i] === 'csv') { + if (separatorSet) { + throw new Error('Cannot set multiple separator types.'); + } else { + sep = ','; + separatorSet = true; + } + } else if (arguments[i] === 'tsv') { + if (separatorSet) { + throw new Error('Cannot set multiple separator types.'); + } else { + sep = '\t'; + separatorSet = true; + } + } + } + } + + var t = new p5.Table(); + reqwest({ + url: path, + crossOrigin: true, + type: 'csv' + }) + .then(function (resp) { + resp = resp.responseText; + + var state = {}; + + // define constants + var PRE_TOKEN = 0, + MID_TOKEN = 1, + POST_TOKEN = 2, + POST_RECORD = 4; + + var QUOTE = '\"', + CR = '\r', + LF = '\n'; + + var records = []; + var offset = 0; + var currentRecord = null; + var currentChar; + + var recordBegin = function () { + state.escaped = false; + currentRecord = []; + tokenBegin(); + }; + + var recordEnd = function () { + state.currentState = POST_RECORD; + records.push(currentRecord); + currentRecord = null; + }; + + var tokenBegin = function () { + state.currentState = PRE_TOKEN; + state.token = ''; + }; + + var tokenEnd = function () { + currentRecord.push(state.token); + tokenBegin(); + }; + + while (true) { + currentChar = resp[offset++]; + + // EOF + if (currentChar == null) { + if (state.escaped) { + throw new Error('Unclosed quote in file.'); + } + if (currentRecord) { + tokenEnd(); + recordEnd(); + break; + } + } + if (currentRecord === null) { + recordBegin(); + } + + // Handle opening quote + if (state.currentState === PRE_TOKEN) { + if (currentChar === QUOTE) { + state.escaped = true; + state.currentState = MID_TOKEN; + continue; + } + state.currentState = MID_TOKEN; + } + + // mid-token and escaped, look for sequences and end quote + if (state.currentState === MID_TOKEN && state.escaped) { + if (currentChar === QUOTE) { + if (resp[offset] === QUOTE) { + state.token += QUOTE; + offset++; + } else { + state.escaped = false; + state.currentState = POST_TOKEN; + } + } else { + state.token += currentChar; + } + continue; + } + + // fall-through: mid-token or post-token, not escaped + if (currentChar === CR) { + if (resp[offset] === LF) { + offset++; + } + tokenEnd(); + recordEnd(); + } else if (currentChar === LF) { + tokenEnd(); + recordEnd(); + } else if (currentChar === sep) { + tokenEnd(); + } else if (state.currentState === MID_TOKEN) { + state.token += currentChar; + } + } + + // set up column names + if (header) { + t.columns = records.shift(); + } else { + for (i = 0; i < records[0].length; i++) { + t.columns[i] = 'null'; + } + } + var row; + for (i = 0; i < records.length; i++) { + //Handles row of 'undefined' at end of some CSVs + if (i === records.length - 1 && records[i].length === 1) { + if (records[i][0] === 'undefined') { + break; + } + } + row = new p5.TableRow(); + row.arr = records[i]; + row.obj = makeObject(records[i], t.columns); + t.addRow(row); + } + if (callback !== null) { + callback(t); + } + if (decrementPreload && (callback !== decrementPreload)) { + decrementPreload(); + } + }) + .fail(function (err, msg) { + p5._friendlyFileLoadError(2, path); + // don't get error callback mixed up with decrementPreload + if ((typeof callback !== 'undefined') && + (callback !== decrementPreload)) { + callback(false); + } + }); + + return t; +}; + +// helper function to turn a row into a JSON object +function makeObject(row, headers) { + var ret = {}; + headers = headers || []; + if (typeof (headers) === 'undefined') { + for (var j = 0; j < row.length; j++) { + headers[j.toString()] = j; + } + } + for (var i = 0; i < headers.length; i++) { + var key = headers[i]; + var val = row[i]; + ret[key] = val; + } + return ret; +} + +/** + * Reads the contents of a file and creates an XML object with its values. + * If the name of the file is used as the parameter, as in the above example, + * the file must be located in the sketch directory/folder. + * <br><br> + * Alternatively, the file maybe be loaded from anywhere on the local + * computer using an absolute path (something that starts with / on Unix and + * Linux, or a drive letter on Windows), or the filename parameter can be a + * URL for a file found on a network. + * <br><br> + * This method is asynchronous, meaning it may not finish before the next + * line in your sketch is executed. Calling loadXML() inside preload() + * guarantees to complete the operation before setup() and draw() are called. + * <br><br> + * Outside of preload(), you may supply a callback function to handle the + * object: + * + * @method loadXML + * @param {String} filename name of the file or URL to load + * @param {Function} [callback] function to be executed after loadXML() + * completes, XML object is passed in as + * first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + * @return {Object} XML object containing data + */ +p5.prototype.loadXML = function (path, callback, errorCallback) { + var ret = document.implementation.createDocument(null, null); + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + reqwest({ + url: path, + type: 'xml', + crossOrigin: true, + error: function (resp) { + // pass to error callback if defined + if (errorCallback) { + errorCallback(resp); + } else { // otherwise log error msg + console.log(resp.statusText); + } + //p5._friendlyFileLoadError(1,path); + } + }) + .then(function (resp) { + var x = resp.documentElement; + ret.appendChild(x); + if (typeof callback !== 'undefined') { + callback(ret); + } + if (decrementPreload && (callback !== decrementPreload)) { + decrementPreload(); + } + }); + return ret; +}; + +// name clash with window.open +// p5.prototype.open = function() { +// // TODO + +// }; + +p5.prototype.parseXML = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.prototype.selectFolder = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.prototype.selectInput = function () { + // TODO + throw 'not yet implemented'; + +}; + +/** + * Method for executing an HTTP GET request. If data type is not specified, + * p5 will try to guess based on the URL, defaulting to text. + * + * @method httpGet + * @param {String} path name of the file or url to load + * @param {Object} [data] param data passed sent with request + * @param {String} [datatype] "json", "jsonp", "xml", or "text" + * @param {Function} [callback] function to be executed after + * httpGet() completes, data is passed in + * as first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + */ +p5.prototype.httpGet = function () { + var args = Array.prototype.slice.call(arguments); + args.push('GET'); + p5.prototype.httpDo.apply(this, args); +}; + +/** + * Method for executing an HTTP POST request. If data type is not specified, + * p5 will try to guess based on the URL, defaulting to text. + * + * @method httpPost + * @param {String} path name of the file or url to load + * @param {Object} [data] param data passed sent with request + * @param {String} [datatype] "json", "jsonp", "xml", or "text" + * @param {Function} [callback] function to be executed after + * httpGet() completes, data is passed in + * as first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + */ +p5.prototype.httpPost = function () { + var args = Array.prototype.slice.call(arguments); + args.push('POST'); + p5.prototype.httpDo.apply(this, args); +}; + +/** + * Method for executing an HTTP request. If data type is not specified, + * p5 will try to guess based on the URL, defaulting to text.<br><br> + * You may also pass a single object specifying all parameters for the + * request following the examples inside the reqwest() calls here: + * <a href='https://github.com/ded/reqwest#api' + * >https://github.com/ded/reqwest#api</a> + * + * @method httpDo + * @param {String} path name of the file or url to load + * @param {String} [method] either "GET", "POST", or "PUT", + * defaults to "GET" + * @param {Object} [data] param data passed sent with request + * @param {String} [datatype] "json", "jsonp", "xml", or "text" + * @param {Function} [callback] function to be executed after + * httpGet() completes, data is passed in + * as first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + */ +p5.prototype.httpDo = function () { + if (typeof arguments[0] === 'object') { + reqwest(arguments[0]); + } else { + var method = 'GET'; + var path = arguments[0]; + var data = {}; + var type = ''; + var callback; + var errorCallback; + + for (var i = 1; i < arguments.length; i++) { + var a = arguments[i]; + if (typeof a === 'string') { + if (a === 'GET' || a === 'POST' || a === 'PUT') { + method = a; + } else { + type = a; + } + } else if (typeof a === 'object') { + data = a; + } else if (typeof a === 'function') { + if (!callback) { + callback = a; + } else { + errorCallback = a; + } + } + } + + // do some sort of smart type checking + if (type === '') { + if (path.indexOf('json') !== -1) { + type = 'json'; + } else if (path.indexOf('xml') !== -1) { + type = 'xml'; + } else { + type = 'text'; + } + } + + reqwest({ + url: path, + method: method, + data: data, + type: type, + crossOrigin: true, + success: function (resp) { + if (typeof callback !== 'undefined') { + if (type === 'text') { + callback(resp.response); + } else { + callback(resp); + } + } + }, + error: function (resp) { + if (errorCallback) { + errorCallback(resp); + } else { + console.log(resp.statusText); + } + } + }); + } +}; + +/** + * @module IO + * @submodule Output + * @for p5 + */ + +window.URL = window.URL || window.webkitURL; + +// private array of p5.PrintWriter objects +p5.prototype._pWriters = []; + +p5.prototype.beginRaw = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.prototype.beginRecord = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.prototype.createOutput = function () { + // TODO + + throw 'not yet implemented'; +}; + +p5.prototype.createWriter = function (name, extension) { + var newPW; + // check that it doesn't already exist + for (var i in p5.prototype._pWriters) { + if (p5.prototype._pWriters[i].name === name) { + // if a p5.PrintWriter w/ this name already exists... + // return p5.prototype._pWriters[i]; // return it w/ contents intact. + // or, could return a new, empty one with a unique name: + newPW = new p5.PrintWriter(name + window.millis(), extension); + p5.prototype._pWriters.push(newPW); + return newPW; + } + } + newPW = new p5.PrintWriter(name, extension); + p5.prototype._pWriters.push(newPW); + return newPW; +}; + +p5.prototype.endRaw = function () { + // TODO + + throw 'not yet implemented'; +}; + +p5.prototype.endRecord = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.PrintWriter = function (filename, extension) { + var self = this; + this.name = filename; + this.content = ''; + this.print = function (data) { + this.content += data; + }; + this.println = function (data) { + this.content += data + '\n'; + }; + this.flush = function () { + this.content = ''; + }; + this.close = function () { + // convert String to Array for the writeFile Blob + var arr = []; + arr.push(this.content); + p5.prototype.writeFile(arr, filename, extension); + // remove from _pWriters array and delete self + for (var i in p5.prototype._pWriters) { + if (p5.prototype._pWriters[i].name === this.name) { + // remove from _pWriters array + p5.prototype._pWriters.splice(i, 1); + } + } + self.flush(); + self = {}; + }; +}; + +p5.prototype.saveBytes = function () { + // TODO + throw 'not yet implemented'; + +}; + +// object, filename, options --> saveJSON, saveStrings, saveTable +// filename, [extension] [canvas] --> saveImage + +/** + * <p>Save an image, text, json, csv, wav, or html. Prompts download to + * the client's computer. <b>Note that it is not recommended to call save() + * within draw if it's looping, as the save() function will open a new save + * dialog every frame.</b></p> + * <p>The default behavior is to save the canvas as an image. You can + * optionally specify a filename. + * For example:</p> + * <pre class='language-javascript'><code> + * save(); + * save('myCanvas.jpg'); // save a specific canvas with a filename + * </code></pre> + * + * <p>Alternately, the first parameter can be a pointer to a canvas + * p5.Element, an Array of Strings, + * an Array of JSON, a JSON object, a p5.Table, a p5.Image, or a + * p5.SoundFile (requires p5.sound). The second parameter is a filename + * (including extension). The third parameter is for options specific + * to this type of object. This method will save a file that fits the + * given paramaters. For example:</p> + * + * <pre class='language-javascript'><code> + * + * save('myCanvas.jpg'); // Saves canvas as an image + * + * var cnv = createCanvas(100, 100); + * save(cnv, 'myCanvas.jpg'); // Saves canvas as an image + * + * var gb = createGraphics(100, 100); + * save(gb, 'myGraphics.jpg'); // Saves p5.Renderer object as an image + * + * save(myTable, 'myTable.html'); // Saves table as html file + * save(myTable, 'myTable.csv',); // Comma Separated Values + * save(myTable, 'myTable.tsv'); // Tab Separated Values + * + * save(myJSON, 'my.json'); // Saves pretty JSON + * save(myJSON, 'my.json', true); // Optimizes JSON filesize + * + * save(img, 'my.png'); // Saves pImage as a png image + * + * save(arrayOfStrings, 'my.txt'); // Saves strings to a text file with line + * // breaks after each item in the array + * </code></pre> + * + * @method save + * @param {[Object|String]} objectOrFilename If filename is provided, will + * save canvas as an image with + * either png or jpg extension + * depending on the filename. + * If object is provided, will + * save depending on the object + * and filename (see examples + * above). + * @param {[String]} filename If an object is provided as the first + * parameter, then the second parameter + * indicates the filename, + * and should include an appropriate + * file extension (see examples above). + * @param {[Boolean/String]} options Additional options depend on + * filetype. For example, when saving JSON, + * <code>true</code> indicates that the + * output will be optimized for filesize, + * rather than readability. + */ +p5.prototype.save = function (object, _filename, _options) { + // parse the arguments and figure out which things we are saving + var args = arguments; + // ================================================= + // OPTION 1: saveCanvas... + + // if no arguments are provided, save canvas + var cnv = this._curElement.elt; + if (args.length === 0) { + p5.prototype.saveCanvas(cnv); + return; + } + // otherwise, parse the arguments + + // if first param is a p5Graphics, then saveCanvas + else if (args[0] instanceof p5.Renderer || + args[0] instanceof p5.Graphics) { + p5.prototype.saveCanvas(args[0].elt, args[1], args[2]); + return; + } + + // if 1st param is String and only one arg, assume it is canvas filename + else if (args.length === 1 && typeof (args[0]) === 'string') { + p5.prototype.saveCanvas(cnv, args[0]); + } + + // ================================================= + // OPTION 2: extension clarifies saveStrings vs. saveJSON + else { + var extension = _checkFileExtension(args[1], args[2])[1]; + switch (extension) { + case 'json': + p5.prototype.saveJSON(args[0], args[1], args[2]); + return; + case 'txt': + p5.prototype.saveStrings(args[0], args[1], args[2]); + return; + // ================================================= + // OPTION 3: decide based on object... + default: + if (args[0] instanceof Array) { + p5.prototype.saveStrings(args[0], args[1], args[2]); + } else if (args[0] instanceof p5.Table) { + p5.prototype.saveTable(args[0], args[1], args[2], args[3]); + } else if (args[0] instanceof p5.Image) { + p5.prototype.saveCanvas(args[0].canvas, args[1]); + } else if (args[0] instanceof p5.SoundFile) { + p5.prototype.saveSound(args[0], args[1], args[2], args[3]); + } + } + } +}; + +/** + * Writes the contents of an Array or a JSON object to a .json file. + * The file saving process and location of the saved file will + * vary between web browsers. + * + * @method saveJSON + * @param {Array|Object} json + * @param {String} filename + * @param {Boolean} [optimize] If true, removes line breaks + * and spaces from the output + * file to optimize filesize + * (but not readability). + * @example + * <div><code> + * var json; + * + * function setup() { + * + * json = {}; // new JSON Object + * + * json.id = 0; + * json.species = 'Panthera leo'; + * json.name = 'Lion'; + * + * // To save, un-comment the line below, then click 'run' + * // saveJSONObject(json, 'lion.json'); + * } + * + * // Saves the following to a file called "lion.json": + * // { + * // "id": 0, + * // "species": "Panthera leo", + * // "name": "Lion" + * // } + * </div></code> + */ +p5.prototype.saveJSON = function (json, filename, opt) { + var stringify; + if (opt) { + stringify = JSON.stringify(json); + } else { + stringify = JSON.stringify(json, undefined, 2); + } + console.log(stringify); + this.saveStrings(stringify.split('\n'), filename, 'json'); +}; + +p5.prototype.saveJSONObject = p5.prototype.saveJSON; +p5.prototype.saveJSONArray = p5.prototype.saveJSON; + +p5.prototype.saveStream = function () { + // TODO + throw 'not yet implemented'; + +}; + +/** + * Writes an array of Strings to a text file, one line per String. + * The file saving process and location of the saved file will + * vary between web browsers. + * + * @method saveStrings + * @param {Array} list string array to be written + * @param {String} filename filename for output + * @example + * <div><code> + * var words = 'apple bear cat dog'; + * + * // .split() outputs an Array + * var list = split(words, ' '); + * + * // To save the file, un-comment next line and click 'run' + * // saveStrings(list, 'nouns.txt'); + * + * // Saves the following to a file called 'nouns.txt': + * // + * // apple + * // bear + * // cat + * // dog + * </code></div> + */ +p5.prototype.saveStrings = function (list, filename, extension) { + var ext = extension || 'txt'; + var pWriter = this.createWriter(filename, ext); + for (var i = 0; i < list.length; i++) { + if (i < list.length - 1) { + pWriter.println(list[i]); + } else { + pWriter.print(list[i]); + } + } + pWriter.close(); + pWriter.flush(); +}; + +p5.prototype.saveXML = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.prototype.selectOutput = function () { + // TODO + throw 'not yet implemented'; + +}; + +// ======= +// HELPERS +// ======= + +function escapeHelper(content) { + return content + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +/** + * Writes the contents of a Table object to a file. Defaults to a + * text file with comma-separated-values ('csv') but can also + * use tab separation ('tsv'), or generate an HTML table ('html'). + * The file saving process and location of the saved file will + * vary between web browsers. + * + * @method saveTable + * @param {p5.Table} Table the Table object to save to a file + * @param {String} filename the filename to which the Table should be saved + * @param {String} [options] can be one of "tsv", "csv", or "html" + * @example + * <div><code> + * var table; + * + * function setup() { + * table = new p5.Table(); + * + * table.addColumn('id'); + * table.addColumn('species'); + * table.addColumn('name'); + * + * var newRow = table.addRow(); + * newRow.setNum('id', table.getRowCount() - 1); + * newRow.setString('species', 'Panthera leo'); + * newRow.setString('name', 'Lion'); + * + * // To save, un-comment next line then click 'run' + * // saveTable(table, 'new.csv'); + * } + * + * // Saves the following to a file called 'new.csv': + * // id,species,name + * // 0,Panthera leo,Lion + * </code></div> + */ +p5.prototype.saveTable = function (table, filename, options) { + var pWriter = this.createWriter(filename, options); + + var header = table.columns; + + var sep = ','; // default to CSV + if (options === 'tsv') { + sep = '\t'; + } + if (options !== 'html') { + // make header if it has values + if (header[0] !== '0') { + for (var h = 0; h < header.length; h++) { + if (h < header.length - 1) { + pWriter.print(header[h] + sep); + } else { + pWriter.println(header[h]); + } + } + } + + // make rows + for (var i = 0; i < table.rows.length; i++) { + var j; + for (j = 0; j < table.rows[i].arr.length; j++) { + if (j < table.rows[i].arr.length - 1) { + pWriter.print(table.rows[i].arr[j] + sep); + } else if (i < table.rows.length - 1) { + pWriter.println(table.rows[i].arr[j]); + } else { + pWriter.print(table.rows[i].arr[j]); // no line break + } + } + } + } + + // otherwise, make HTML + else { + pWriter.println('<html>'); + pWriter.println('<head>'); + var str = ' <meta http-equiv=\"content-type\" content'; + str += '=\"text/html;charset=utf-8\" />'; + pWriter.println(str); + pWriter.println('</head>'); + + pWriter.println('<body>'); + pWriter.println(' <table>'); + + // make header if it has values + if (header[0] !== '0') { + pWriter.println(' <tr>'); + for (var k = 0; k < header.length; k++) { + var e = escapeHelper(header[k]); + pWriter.println(' <td>' + e); + pWriter.println(' </td>'); + } + pWriter.println(' </tr>'); + } + + // make rows + for (var row = 0; row < table.rows.length; row++) { + pWriter.println(' <tr>'); + for (var col = 0; col < table.columns.length; col++) { + var entry = table.rows[row].getString(col); + var htmlEntry = escapeHelper(entry); + pWriter.println(' <td>' + htmlEntry); + pWriter.println(' </td>'); + } + pWriter.println(' </tr>'); + } + pWriter.println(' </table>'); + pWriter.println('</body>'); + pWriter.print('</html>'); + } + // close and flush the pWriter + pWriter.close(); + pWriter.flush(); +}; // end saveTable() + +/** + * Generate a blob of file data as a url to prepare for download. + * Accepts an array of data, a filename, and an extension (optional). + * This is a private function because it does not do any formatting, + * but it is used by saveStrings, saveJSON, saveTable etc. + * + * @param {Array} dataToDownload + * @param {String} filename + * @param {[String]} extension + * @private + */ +p5.prototype.writeFile = function (dataToDownload, filename, extension) { + var type = 'application\/octet-stream'; + if (p5.prototype._isSafari()) { + type = 'text\/plain'; + } + var blob = new Blob(dataToDownload, { + 'type': type + }); + var href = window.URL.createObjectURL(blob); + p5.prototype.downloadFile(href, filename, extension); +}; + +/** + * Forces download. Accepts a url to filedata/blob, a filename, + * and an extension (optional). + * This is a private function because it does not do any formatting, + * but it is used by saveStrings, saveJSON, saveTable etc. + * + * @param {String} href i.e. an href generated by createObjectURL + * @param {[String]} filename + * @param {[String]} extension + */ +p5.prototype.downloadFile = function (href, fName, extension) { + var fx = _checkFileExtension(fName, extension); + var filename = fx[0]; + var ext = fx[1]; + + var a = document.createElement('a'); + a.href = href; + a.download = filename; + + // Firefox requires the link to be added to the DOM before click() + a.onclick = destroyClickedElement; + a.style.display = 'none'; + document.body.appendChild(a); + + // Safari will open this file in the same page as a confusing Blob. + if (p5.prototype._isSafari()) { + var aText = 'Hello, Safari user! To download this file...\n'; + aText += '1. Go to File --> Save As.\n'; + aText += '2. Choose "Page Source" as the Format.\n'; + aText += '3. Name it with this extension: .\"' + ext + '\"'; + alert(aText); + } + a.click(); + href = null; +}; + +/** + * Returns a file extension, or another string + * if the provided parameter has no extension. + * + * @param {String} filename + * @return {Array} [fileName, fileExtension] + * + * @private + */ +function _checkFileExtension(filename, extension) { + if (!extension || extension === true || extension === 'true') { + extension = ''; + } + if (!filename) { + filename = 'untitled'; + } + var ext = ''; + // make sure the file will have a name, see if filename needs extension + if (filename && filename.indexOf('.') > -1) { + ext = filename.split('.').pop(); + } + // append extension if it doesn't exist + if (extension) { + if (ext !== extension) { + ext = extension; + filename = filename + '.' + ext; + } + } + return [filename, ext]; +} +p5.prototype._checkFileExtension = _checkFileExtension; + +/** + * Returns true if the browser is Safari, false if not. + * Safari makes trouble for downloading files. + * + * @return {Boolean} [description] + * @private + */ +p5.prototype._isSafari = function () { + var x = Object.prototype.toString.call(window.HTMLElement); + return x.indexOf('Constructor') > 0; +}; + +/** + * Helper function, a callback for download that deletes + * an invisible anchor element from the DOM once the file + * has been automatically downloaded. + * + * @private + */ +function destroyClickedElement(event) { + document.body.removeChild(event.target); +} + +module.exports = p5; + +},{"../core/core":48,"../core/error_helpers":51,"opentype.js":8,"reqwest":27}],71:[function(_dereq_,module,exports){ +/** + * @module IO + * @submodule Table + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + + +/** + * Table Options + * <p>Generic class for handling tabular data, typically from a + * CSV, TSV, or other sort of spreadsheet file.</p> + * <p>CSV files are + * <a href="http://en.wikipedia.org/wiki/Comma-separated_values"> + * comma separated values</a>, often with the data in quotes. TSV + * files use tabs as separators, and usually don't bother with the + * quotes.</p> + * <p>File names should end with .csv if they're comma separated.</p> + * <p>A rough "spec" for CSV can be found + * <a href="http://tools.ietf.org/html/rfc4180">here</a>.</p> + * <p>To load files, use the loadTable method.</p> + * <p>To save tables to your computer, use the save method + * or the saveTable method.</p> + * + * Possible options include: + * <ul> + * <li>csv - parse the table as comma-separated values + * <li>tsv - parse the table as tab-separated values + * <li>header - this table has a header (title) row + * </ul> + */ + +/** + * Table objects store data with multiple rows and columns, much + * like in a traditional spreadsheet. Tables can be generated from + * scratch, dynamically, or using data from an existing file. + * + * @class p5.Table + * @constructor + * @param {Array} [rows] An array of p5.TableRow objects + * @return {p5.Table} p5.Table generated + */ +p5.Table = function (rows) { + /** + * @property columns + * @type {Array} + */ + this.columns = []; + + /** + * @property rows + * @type {Array} + */ + this.rows = []; +}; + +/** + * Use addRow() to add a new row of data to a p5.Table object. By default, + * an empty row is created. Typically, you would store a reference to + * the new row in a TableRow object (see newRow in the example above), + * and then set individual values using set(). + * + * If a p5.TableRow object is included as a parameter, then that row is + * duplicated and added to the table. + * + * @method addRow + * @param {p5.TableRow} [row] row to be added to the table + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * //add a row + * var newRow = table.addRow(); + * newRow.setString("id", table.getRowCount() - 1); + * newRow.setString("species", "Canis Lupus"); + * newRow.setString("name", "Wolf"); + * + * //print the results + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.addRow = function(row) { + // make sure it is a valid TableRow + var r = row || new p5.TableRow(); + + if (typeof(r.arr) === 'undefined' || typeof(r.obj) === 'undefined') { + //r = new p5.prototype.TableRow(r); + throw 'invalid TableRow: ' + r; + } + r.table = this; + this.rows.push(r); + return r; +}; + +/** + * Removes a row from the table object. + * + * @method removeRow + * @param {Number} id ID number of the row to remove + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * //remove the first row + * var r = table.removeRow(0); + * + * //print the results + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.removeRow = function(id) { + this.rows[id].table = null; // remove reference to table + var chunk = this.rows.splice(id+1, this.rows.length); + this.rows.pop(); + this.rows = this.rows.concat(chunk); +}; + + +/** + * Returns a reference to the specified p5.TableRow. The reference + * can then be used to get and set values of the selected row. + * + * @method getRow + * @param {Number} rowID ID number of the row to get + * @return {TableRow} p5.TableRow object + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * var row = table.getRow(1); + * //print it column by column + * //note: a row is an object, not an array + * for (var c = 0; c < table.getColumnCount(); c++) + * print(row.getString(c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.getRow = function(r) { + return this.rows[r]; +}; + +/** + * Gets all rows from the table. Returns an array of p5.TableRows. + * + * @method getRows + * @return {Array} Array of p5.TableRows + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * var rows = table.getRows(); + * + * //warning: rows is an array of objects + * for (var r = 0; r < rows.length; r++) + * rows[r].set("name", "Unicorn"); + * + * //print the results + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.getRows = function() { + return this.rows; +}; + +/** + * Finds the first row in the Table that contains the value + * provided, and returns a reference to that row. Even if + * multiple rows are possible matches, only the first matching + * row is returned. The column to search may be specified by + * either its ID or title. + * + * @method findRow + * @param {String} value The value to match + * @param {Number|String} column ID number or title of the + * column to search + * @return {TableRow} + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * //find the animal named zebra + * var row = table.findRow("Zebra", "name"); + * //find the corresponding species + * print(row.getString("species")); + * } + * </code> + * </div> + */ +p5.Table.prototype.findRow = function(value, column) { + // try the Object + if (typeof(column) === 'string') { + for (var i = 0; i < this.rows.length; i++){ + if (this.rows[i].obj[column] === value) { + return this.rows[i]; + } + } + } + // try the Array + else { + for (var j = 0; j < this.rows.length; j++){ + if (this.rows[j].arr[column] === value) { + return this.rows[j]; + } + } + } + // otherwise... + return null; +}; + +/** + * Finds the rows in the Table that contain the value + * provided, and returns references to those rows. Returns an + * Array, so for must be used to iterate through all the rows, + * as shown in the example above. The column to search may be + * specified by either its ID or title. + * + * @method findRows + * @param {String} value The value to match + * @param {Number|String} column ID number or title of the + * column to search + * @return {Array} An Array of TableRow objects + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * //add another goat + * var newRow = table.addRow(); + * newRow.setString("id", table.getRowCount() - 1); + * newRow.setString("species", "Scape Goat"); + * newRow.setString("name", "Goat"); + * + * //find the rows containing animals named Goat + * var rows = table.findRows("Goat", "name"); + * print(rows.length + " Goats found"); + * } + * </code> + * </div> + */ +p5.Table.prototype.findRows = function(value, column) { + var ret = []; + if (typeof(column) === 'string') { + for (var i = 0; i < this.rows.length; i++){ + if (this.rows[i].obj[column] === value) { + ret.push( this.rows[i] ); + } + } + } + // try the Array + else { + for (var j = 0; j < this.rows.length; j++){ + if (this.rows[j].arr[column] === value) { + ret.push( this.rows[j] ); + } + } + } + return ret; +}; + +/** + * Finds the first row in the Table that matches the regular + * expression provided, and returns a reference to that row. + * Even if multiple rows are possible matches, only the first + * matching row is returned. The column to search may be + * specified by either its ID or title. + * + * @method matchRow + * @param {String} regexp The regular expression to match + * @param {String|Number} column The column ID (number) or + * title (string) + * @return {TableRow} TableRow object + */ +p5.Table.prototype.matchRow = function(regexp, column) { + if (typeof(column) === 'number') { + for (var j = 0; j < this.rows.length; j++) { + if ( this.rows[j].arr[column].match(regexp) ) { + return this.rows[j]; + } + } + } + + else { + for (var i = 0; i < this.rows.length; i++) { + if ( this.rows[i].obj[column].match(regexp) ) { + return this.rows[i]; + } + } + } + return null; +}; + +/** + * Finds the first row in the Table that matches the regular + * expression provided, and returns a reference to that row. + * Even if multiple rows are possible matches, only the first + * matching row is returned. The column to search may be specified + * by either its ID or title. + * + * @method matchRows + * @param {String} regexp The regular expression to match + * @param {String|Number} [column] The column ID (number) or + * title (string) + * @return {Array} An Array of TableRow objects + */ +p5.Table.prototype.matchRows = function(regexp, column) { + var ret = []; + if (typeof(column) === 'number') { + for (var j = 0; j < this.rows.length; j++) { + if ( this.rows[j].arr[column].match(regexp) ) { + ret.push( this.rows[j] ); + } + } + } + + else { + for (var i = 0; i < this.rows.length; i++) { + if ( this.rows[i].obj[column].match(regexp) ) { + ret.push( this.rows[i] ); + } + } + } + return ret; +}; + + +/** + * Retrieves all values in the specified column, and returns them + * as an array. The column may be specified by either its ID or title. + * + * @method getColumn + * @param {String|Number} column String or Number of the column to return + * @return {Array} Array of column values + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * //getColumn returns an array that can be printed directly + * print(table.getColumn("species")); + * //outputs ["Capra hircus", "Panthera pardus", "Equus zebra"] + * } + * </code> + * </div> + */ +p5.Table.prototype.getColumn = function(value) { + var ret = []; + if (typeof(value) === 'string'){ + for (var i = 0; i < this.rows.length; i++){ + ret.push (this.rows[i].obj[value]); + } + } else { + for (var j = 0; j < this.rows.length; j++){ + ret.push (this.rows[j].arr[value]); + } + } + return ret; +}; + +/** + * Removes all rows from a Table. While all rows are removed, + * columns and column titles are maintained. + * + * @method clearRows + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * table.clearRows(); + * print(table.getRowCount() + " total rows in table"); + * print(table.getColumnCount() + " total columns in table"); + * } + * </code> + * </div> + */ +p5.Table.prototype.clearRows = function() { + delete this.rows; + this.rows = []; +}; + +/** + * Use addColumn() to add a new column to a Table object. + * Typically, you will want to specify a title, so the column + * may be easily referenced later by name. (If no title is + * specified, the new column's title will be null.) + * + * @method addColumn + * @param {String} [title] title of the given column + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * table.addColumn("carnivore"); + * table.set(0, "carnivore", "no"); + * table.set(1, "carnivore", "yes"); + * table.set(2, "carnivore", "no"); + * + * //print the results + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.addColumn = function(title) { + var t = title || null; + this.columns.push(t); +}; + +/** + * Returns the total number of columns in a Table. + * + * @return {Number} Number of columns in this table + */ +p5.Table.prototype.getColumnCount = function() { + return this.columns.length; +}; + +/** + * Returns the total number of rows in a Table. + * + * @method getRowCount + * @return {Number} Number of rows in this table + + */ +p5.Table.prototype.getRowCount = function() { + return this.rows.length; +}; + +/** + * <p>Removes any of the specified characters (or "tokens").</p> + * + * <p>If no column is specified, then the values in all columns and + * rows are processed. A specific column may be referenced by + * either its ID or title.</p> + * + * @method removeTokens + * @param {String} chars String listing characters to be removed + * @param {String|Number} [column] Column ID (number) + * or name (string) + */ +p5.Table.prototype.removeTokens = function(chars, column) { + var escape= function(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + }; + var charArray = []; + for (var i = 0; i < chars.length; i++) { + charArray.push( escape( chars.charAt(i) ) ); + } + var regex = new RegExp(charArray.join('|'), 'g'); + + if (typeof(column) === 'undefined'){ + for (var c = 0; c < this.columns.length; c++) { + for (var d = 0; d < this.rows.length; d++) { + var s = this.rows[d].arr[c]; + s = s.replace(regex, ''); + this.rows[d].arr[c] = s; + this.rows[d].obj[this.columns[c]] = s; + } + } + } + else if (typeof(column) === 'string'){ + for (var j = 0; j < this.rows.length; j++) { + var val = this.rows[j].obj[column]; + val = val.replace(regex, ''); + this.rows[j].obj[column] = val; + var pos = this.columns.indexOf(column); + this.rows[j].arr[pos] = val; + } + } + else { + for (var k = 0; k < this.rows.length; k++) { + var str = this.rows[k].arr[column]; + str = str.replace(regex, ''); + this.rows[k].arr[column] = str; + this.rows[k].obj[this.columns[column]] = str; + } + } +}; + +/** + * Trims leading and trailing whitespace, such as spaces and tabs, + * from String table values. If no column is specified, then the + * values in all columns and rows are trimmed. A specific column + * may be referenced by either its ID or title. + * + * @method trim + * @param {String|Number} column Column ID (number) + * or name (string) + */ +p5.Table.prototype.trim = function(column) { + var regex = new RegExp( (' '), 'g'); + + if (typeof(column) === 'undefined'){ + for (var c = 0; c < this.columns.length; c++) { + for (var d = 0; d < this.rows.length; d++) { + var s = this.rows[d].arr[c]; + s = s.replace(regex, ''); + this.rows[d].arr[c] = s; + this.rows[d].obj[this.columns[c]] = s; + } + } + } + else if (typeof(column) === 'string'){ + for (var j = 0; j < this.rows.length; j++) { + var val = this.rows[j].obj[column]; + val = val.replace(regex, ''); + this.rows[j].obj[column] = val; + var pos = this.columns.indexOf(column); + this.rows[j].arr[pos] = val; + } + } + else { + for (var k = 0; k < this.rows.length; k++) { + var str = this.rows[k].arr[column]; + str = str.replace(regex, ''); + this.rows[k].arr[column] = str; + this.rows[k].obj[this.columns[column]] = str; + } + } +}; + +/** + * Use removeColumn() to remove an existing column from a Table + * object. The column to be removed may be identified by either + * its title (a String) or its index value (an int). + * removeColumn(0) would remove the first column, removeColumn(1) + * would remove the second column, and so on. + * + * @method removeColumn + * @param {String|Number} column columnName (string) or ID (number) + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * table.removeColumn("id"); + * print(table.getColumnCount()); + * } + * </code> + * </div> + */ +p5.Table.prototype.removeColumn = function(c) { + var cString; + var cNumber; + if (typeof(c) === 'string') { + // find the position of c in the columns + cString = c; + cNumber = this.columns.indexOf(c); + console.log('string'); + } + else{ + cNumber = c; + cString = this.columns[c]; + } + + var chunk = this.columns.splice(cNumber+1, this.columns.length); + this.columns.pop(); + this.columns = this.columns.concat(chunk); + + for (var i = 0; i < this.rows.length; i++){ + var tempR = this.rows[i].arr; + var chip = tempR.splice(cNumber+1, tempR.length); + tempR.pop(); + this.rows[i].arr = tempR.concat(chip); + delete this.rows[i].obj[cString]; + } + +}; + + +/** + * Stores a value in the Table's specified row and column. + * The row is specified by its ID, while the column may be specified + * by either its ID or title. + * + * @method set + * @param {String|Number} column column ID (Number) + * or title (String) + * @param {String|Number} value value to assign + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * table.set(0, "species", "Canis Lupus"); + * table.set(0, "name", "Wolf"); + * + * //print the results + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.set = function(row, column, value) { + this.rows[row].set(column, value); +}; + +/** + * Stores a Float value in the Table's specified row and column. + * The row is specified by its ID, while the column may be specified + * by either its ID or title. + * + * @method setNum + * @param {Number} row row ID + * @param {String|Number} column column ID (Number) + * or title (String) + * @param {Number} value value to assign + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * table.setNum(1, "id", 1); + * + * print(table.getColumn(0)); + * //["0", 1, "2"] + * } + * </code> + * </div> + */ +p5.Table.prototype.setNum = function(row, column, value){ + this.rows[row].setNum(column, value); +}; + + +/** + * Stores a String value in the Table's specified row and column. + * The row is specified by its ID, while the column may be specified + * by either its ID or title. + * + * @method setString + * @param {Number} row row ID + * @param {String|Number} column column ID (Number) + * or title (String) + * @param {String} value value to assign + */ +p5.Table.prototype.setString = function(row, column, value){ + this.rows[row].setString(column, value); +}; + +/** + * Retrieves a value from the Table's specified row and column. + * The row is specified by its ID, while the column may be specified by + * either its ID or title. + * + * @method get + * @param {Number} row row ID + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {String|Number} + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * print(table.get(0, 1)); + * //Capra hircus + * print(table.get(0, "species")); + * //Capra hircus + * } + * </code> + * </div> + */ +p5.Table.prototype.get = function(row, column) { + return this.rows[row].get(column); +}; + +/** + * Retrieves a Float value from the Table's specified row and column. + * The row is specified by its ID, while the column may be specified by + * either its ID or title. + * + * @method getNum + * @param {Number} row row ID + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {Number} + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * print(table.getNum(1, 0) + 100); + * //id 1 + 100 = 101 + * } + * </code> + * </div> + */ +p5.Table.prototype.getNum = function(row, column) { + return this.rows[row].getNum(column); +}; + +/** + * Retrieves a String value from the Table's specified row and column. + * The row is specified by its ID, while the column may be specified by + * either its ID or title. + * + * @method getString + * @param {Number} row row ID + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {String} + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * var tableArray = table.getArray(); + * + * //output each row as array + * for (var i = 0; i < tableArray.length; i++) + * print(tableArray[i]); + * } + * </code> + * </div> + */ +p5.Table.prototype.getString = function(row, column) { + return this.rows[row].getString(column); +}; + +/** + * Retrieves all table data and returns as an object. If a column name is + * passed in, each row object will be stored with that attribute as its + * title. + * + * @method getObject + * @param {String} headerColumn Name of the column which should be used to + * title each row object (optional) + * @return {Object} + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * var tableObject = table.getObject(); + * + * print(tableObject); + * //outputs an object + * } + * </code> + * </div> + + */ +p5.Table.prototype.getObject = function (headerColumn) { + var tableObject = {}; + var obj, cPos, index; + + for(var i = 0; i < this.rows.length; i++) { + obj = this.rows[i].obj; + + if (typeof(headerColumn) === 'string'){ + cPos = this.columns.indexOf(headerColumn); // index of columnID + if (cPos >= 0) { + index = obj[headerColumn]; + tableObject[index] = obj; + } else { + throw 'This table has no column named "' + headerColumn +'"'; + } + } else { + tableObject[i] = this.rows[i].obj; + } + } + return tableObject; +}; + +/** + * Retrieves all table data and returns it as a multidimensional array. + * + * @method getArray + * @return {Array} + */ +p5.Table.prototype.getArray = function () { + var tableArray = []; + for(var i = 0; i < this.rows.length; i++) { + tableArray.push(this.rows[i].arr); + } + return tableArray; +}; + +module.exports = p5.Table; + +},{"../core/core":48}],72:[function(_dereq_,module,exports){ +/** + * @module IO + * @submodule Table + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * A TableRow object represents a single row of data values, + * stored in columns, from a table. + * + * A Table Row contains both an ordered array, and an unordered + * JSON object. + * + * @class p5.TableRow + * @constructor + * @param {String} [str] optional: populate the row with a + * string of values, separated by the + * separator + * @param {String} [separator] comma separated values (csv) by default + */ +p5.TableRow = function (str, separator) { + var arr = []; + var obj = {}; + if (str){ + separator = separator || ','; + arr = str.split(separator); + } + for (var i = 0; i < arr.length; i++){ + var key = i; + var val = arr[i]; + obj[key] = val; + } + this.arr = arr; + this.obj = obj; + this.table = null; +}; + +/** + * Stores a value in the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method set + * @param {String|Number} column Column ID (Number) + * or Title (String) + * @param {String|Number} value The value to be stored + */ +p5.TableRow.prototype.set = function(column, value) { + // if typeof column is string, use .obj + if (typeof(column) === 'string'){ + var cPos = this.table.columns.indexOf(column); // index of columnID + if (cPos >= 0) { + this.obj[column] = value; + this.arr[cPos] = value; + } + else { + throw 'This table has no column named "' + column +'"'; + } + } + + // if typeof column is number, use .arr + else { + if (column < this.table.columns.length) { + this.arr[column] = value; + var cTitle = this.table.columns[column]; + this.obj[cTitle] = value; + } + else { + throw 'Column #' + column + ' is out of the range of this table'; + } + } +}; + + +/** + * Stores a Float value in the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method setNum + * @param {String|Number} column Column ID (Number) + * or Title (String) + * @param {Number} value The value to be stored + * as a Float + */ +p5.TableRow.prototype.setNum = function(column, value){ + var floatVal = parseFloat(value, 10); + this.set(column, floatVal); +}; + + +/** + * Stores a String value in the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method setString + * @param {String|Number} column Column ID (Number) + * or Title (String) + * @param {String} value The value to be stored + * as a String + */ +p5.TableRow.prototype.setString = function(column, value){ + var stringVal = value.toString(); + this.set(column, stringVal); +}; + +/** + * Retrieves a value from the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method get + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {String|Number} + */ +p5.TableRow.prototype.get = function(column) { + if (typeof(column) === 'string'){ + return this.obj[column]; + } else { + return this.arr[column]; + } +}; + +/** + * Retrieves a Float value from the TableRow's specified + * column. The column may be specified by either its ID or + * title. + * + * @method getNum + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {Number} Float Floating point number + */ +p5.TableRow.prototype.getNum = function(column) { + var ret; + if (typeof(column) === 'string'){ + ret = parseFloat(this.obj[column], 10); + } else { + ret = parseFloat(this.arr[column], 10); + } + + if (ret.toString() === 'NaN') { + throw 'Error: ' + this.obj[column]+ ' is NaN (Not a Number)'; + } + return ret; +}; + +/** + * Retrieves an String value from the TableRow's specified + * column. The column may be specified by either its ID or + * title. + * + * @method getString + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {String} String + */ +p5.TableRow.prototype.getString = function(column) { + if (typeof(column) === 'string'){ + return this.obj[column].toString(); + } else { + return this.arr[column].toString(); + } +}; + +module.exports = p5.TableRow; + +},{"../core/core":48}],73:[function(_dereq_,module,exports){ +/** + * @module Math + * @submodule Calculation + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Calculates the absolute value (magnitude) of a number. Maps to Math.abs(). + * The absolute value of a number is always positive. + * + * @method abs + * @param {Number} n number to compute + * @return {Number} absolute value of given number + * @example + * <div class = "norender"><code> + * function setup() { + * var x = -3; + * var y = abs(x); + * + * print(x); // -3 + * print(y); // 3 + * } + * </code></div> + */ +p5.prototype.abs = Math.abs; + +/** + * Calculates the closest int value that is greater than or equal to the + * value of the parameter. Maps to Math.ceil(). For example, ceil(9.03) + * returns the value 10. + * + * @method ceil + * @param {Number} n number to round up + * @return {Number} rounded up number + * @example + * <div><code> + * function draw() { + * background(200); + * // map, mouseX between 0 and 5. + * var ax = map(mouseX, 0, 100, 0, 5); + * var ay = 66; + * + * //Get the ceiling of the mapped number. + * var bx = ceil(map(mouseX, 0, 100, 0,5)); + * var by = 33; + * + * // Multiply the mapped numbers by 20 to more easily + * // see the changes. + * stroke(0); + * fill(0); + * line(0, ay, ax * 20, ay); + * line(0, by, bx * 20, by); + * + * // Reformat the float returned by map and draw it. + * noStroke(); + * text(nfc(ax, 2,2), ax, ay - 5); + * text(nfc(bx,1,1), bx, by - 5); + * } + * </code></div> + */ +p5.prototype.ceil = Math.ceil; + +/** + * Constrains a value between a minimum and maximum value. + * + * @method constrain + * @param {Number} n number to constrain + * @param {Number} low minimum limit + * @param {Number} high maximum limit + * @return {Number} constrained number + * @example + * <div><code> + * function draw() { + * background(200); + * + * var leftWall = 25; + * var rightWall = 75; + * + * // xm is just the mouseX, while + * // xc is the mouseX, but constrained + * // between the leftWall and rightWall! + * var xm = mouseX; + * var xc = constrain(mouseX, leftWall, rightWall); + * + * // Draw the walls. + * stroke(150); + * line(leftWall, 0, leftWall, height); + * line(rightWall, 0, rightWall, height); + * + * // Draw xm and xc as circles. + * noStroke(); + * fill(150); + * ellipse(xm, 33, 9,9); // Not Constrained + * fill(0); + * ellipse(xc, 66, 9,9); // Constrained + * } + * </code></div> + */ +p5.prototype.constrain = function(n, low, high) { + return Math.max(Math.min(n, high), low); +}; + +/** + * Calculates the distance between two points. + * + * @method dist + * @param {Number} x1 x-coordinate of the first point + * @param {Number} y1 y-coordinate of the first point + * @param {Number} [z1] z-coordinate of the first point + * @param {Number} x2 x-coordinate of the second point + * @param {Number} y2 y-coordinate of the second point + * @param {Number} [z2] z-coordinate of the second point + * @return {Number} distance between the two points + * @example + * <div><code> + * // Move your mouse inside the canvas to see the + * // change in distance between two points! + * function draw() { + * background(200); + * fill(0); + * + * var x1 = 10; + * var y1 = 90; + * var x2 = mouseX; + * var y2 = mouseY; + * + * line(x1, y1, x2, y2); + * ellipse(x1, y1, 7, 7); + * ellipse(x2, y2, 7, 7); + * + * // d is the length of the line + * // the distance from point 1 to point 2. + * var d = int(dist(x1, y1, x2, y2)); + * + * // Let's write d along the line we are drawing! + * push(); + * translate( (x1+x2)/2, (y1+y2)/2 ); + * rotate( atan2(y2-y1,x2-x1) ); + * text(nfc(d,1,1), 0, -5); + * pop(); + * // Fancy! + * } + * </code></div> + */ +p5.prototype.dist = function(x1, y1, z1, x2, y2, z2) { + if (arguments.length === 4) { + // In the case of 2d: z1 means x2 and x2 means y2 + return Math.sqrt( (z1-x1)*(z1-x1) + (x2-y1)*(x2-y1) ); + } else if (arguments.length === 6) { + return Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1) ); + } +}; + +/** + * Returns Euler's number e (2.71828...) raised to the power of the n + * parameter. Maps to Math.exp(). + * + * @method exp + * @param {Number} n exponent to raise + * @return {Number} e^n + * @example + * <div><code> + * function draw() { + * background(200); + * + * // Compute the exp() function with a value between 0 and 2 + * var xValue = map(mouseX, 0, width, 0, 2); + * var yValue = exp(xValue); + * + * var y = map(yValue, 0, 8, height, 0); + * + * var legend = "exp (" + nfc(xValue, 3) +")\n= " + nf(yValue, 1, 4); + * stroke(150); + * line(mouseX, y, mouseX, height); + * fill(0); + * text(legend, 5, 15); + * noStroke(); + * ellipse (mouseX,y, 7, 7); + * + * // Draw the exp(x) curve, + * // over the domain of x from 0 to 2 + * noFill(); + * stroke(0); + * beginShape(); + * for (var x = 0; x < width; x++) { + * xValue = map(x, 0, width, 0, 2); + * yValue = exp(xValue); + * y = map(yValue, 0, 8, height, 0); + * vertex(x, y); + * } + * + * endShape(); + * line(0, 0, 0, height); + * line(0, height-1, width, height-1); + * } + * </code></div> + */ +p5.prototype.exp = Math.exp; + +/** + * Calculates the closest int value that is less than or equal to the + * value of the parameter. Maps to Math.floor(). + * + * @method floor + * @param {Number} n number to round down + * @return {Number} rounded down number + * @example + * <div><code> + * function draw() { + * background(200); + * //map, mouseX between 0 and 5. + * var ax = map(mouseX, 0, 100, 0, 5); + * var ay = 66; + * + * //Get the floor of the mapped number. + * var bx = floor(map(mouseX, 0, 100, 0,5)); + * var by = 33; + * + * // Multiply the mapped numbers by 20 to more easily + * // see the changes. + * stroke(0); + * fill(0); + * line(0, ay, ax * 20, ay); + * line(0, by, bx * 20, by); + * + * // Reformat the float returned by map and draw it. + * noStroke(); + * text(nfc(ax, 2,2), ax, ay - 5); + * text(nfc(bx,1,1), bx, by - 5); + * } + * </code></div> + */ +p5.prototype.floor = Math.floor; + +/** + * Calculates a number between two numbers at a specific increment. The amt + * parameter is the amount to interpolate between the two values where 0.0 + * equal to the first point, 0.1 is very near the first point, 0.5 is + * half-way in between, etc. The lerp function is convenient for creating + * motion along a straight path and for drawing dotted lines. + * + * @method lerp + * @param {Number} start first value + * @param {Number} stop second value + * @param {Number} amt number between 0.0 and 1.0 + * @return {Number} lerped value + * @example + * <div><code> + * function setup() { + * background(200); + * var a = 20; + * var b = 80; + * var c = lerp(a,b, .2); + * var d = lerp(a,b, .5); + * var e = lerp(a,b, .8); + * + * var y = 50 + * + * strokeWeight(5); + * stroke(0); // Draw the original points in black + * point(a, y); + * point(b, y); + * + * stroke(100); // Draw the lerp points in gray + * point(c, y); + * point(d, y); + * point(e, y); + * } + * </code></div> + */ +p5.prototype.lerp = function(start, stop, amt) { + return amt*(stop-start)+start; +}; + +/** + * Calculates the natural logarithm (the base-e logarithm) of a number. This + * function expects the n parameter to be a value greater than 0.0. Maps to + * Math.log(). + * + * @method log + * @param {Number} n number greater than 0 + * @return {Number} natural logarithm of n + * @example + * <div><code> + * function draw() { + * background(200); + * var maxX = 2.8; + * var maxY = 1.5; + * + * // Compute the natural log of a value between 0 and maxX + * var xValue = map(mouseX, 0, width, 0, maxX); + * if (xValue > 0) { // Cannot take the log of a negative number. + * var yValue = log(xValue); + * var y = map(yValue, -maxY, maxY, height, 0); + * + * // Display the calculation occurring. + * var legend = "log(" + nf(xValue, 1, 2) + ")\n= " + nf(yValue, 1, 3); + * stroke(150); + * line(mouseX, y, mouseX, height); + * fill(0); + * text (legend, 5, 15); + * noStroke(); + * ellipse (mouseX, y, 7, 7); + * } + * + * // Draw the log(x) curve, + * // over the domain of x from 0 to maxX + * noFill(); + * stroke(0); + * beginShape(); + * for(var x=0; x < width; x++) { + * xValue = map(x, 0, width, 0, maxX); + * yValue = log(xValue); + * y = map(yValue, -maxY, maxY, height, 0); + * vertex(x, y); + * } + * endShape(); + * line(0,0,0,height); + * line(0,height/2,width, height/2); + * } + * </code></div> + */ +p5.prototype.log = Math.log; + +/** + * Calculates the magnitude (or length) of a vector. A vector is a direction + * in space commonly used in computer graphics and linear algebra. Because it + * has no "start" position, the magnitude of a vector can be thought of as + * the distance from the coordinate 0,0 to its x,y value. Therefore, mag() is + * a shortcut for writing dist(0, 0, x, y). + * + * @method mag + * @param {Number} a first value + * @param {Number} b second value + * @return {Number} magnitude of vector from (0,0) to (a,b) + * @example + * <div><code> + * function setup() { + * var x1 = 20; + * var x2 = 80; + * var y1 = 30; + * var y2 = 70; + * + * line(0, 0, x1, y1); + * print(mag(x1, y1)); // Prints "36.05551" + * line(0, 0, x2, y1); + * print(mag(x2, y1)); // Prints "85.44004" + * line(0, 0, x1, y2); + * print(mag(x1, y2)); // Prints "72.8011" + * line(0, 0, x2, y2); + * print(mag(x2, y2)); // Prints "106.30146" + * } + * </code></div> + */ +p5.prototype.mag = function(x, y) { + return Math.sqrt(x*x+y*y); +}; + +/** + * Re-maps a number from one range to another. + * <br><br> + * In the first example above, the number 25 is converted from a value in the + * range of 0 to 100 into a value that ranges from the left edge of the + * window (0) to the right edge (width). + * + * @method map + * @param {Number} value the incoming value to be converted + * @param {Number} start1 lower bound of the value's current range + * @param {Number} stop1 upper bound of the value's current range + * @param {Number} start2 lower bound of the value's target range + * @param {Number} stop upper bound of the value's target range + * @return {Number} remapped number + * @example + * <div><code> + * var value = 25; + * var m = map(value, 0, 100, 0, width); + * ellipse(m, 50, 10, 10); + * </code></div> + * + * <div><code> + * function setup() { + * noStroke(); + * } + * + * function draw() { + * background(204); + * var x1 = map(mouseX, 0, width, 25, 75); + * ellipse(x1, 25, 25, 25); + * var x2 = map(mouseX, 0, width, 0, 100); + * ellipse(x2, 75, 25, 25); + * } + * </code></div> + */ +p5.prototype.map = function(n, start1, stop1, start2, stop2) { + return ((n-start1)/(stop1-start1))*(stop2-start2)+start2; +}; + +/** + * Determines the largest value in a sequence of numbers, and then returns + * that value. max() accepts any number of Number parameters, or an Array + * of any length. + * + * @method max + * @param {Number|Array} n0 Numbers to compare + * @return {Number} maximum Number + * @example + * <div><code> + * function setup() { + * // Change the elements in the array and run the sketch + * // to show how max() works! + * numArray = new Array(2,1,5,4,8,9); + * fill(0); + * noStroke(); + * text("Array Elements", 0, 10); + * // Draw all numbers in the array + * var spacing = 15; + * var elemsY = 25; + * for(var i = 0; i < numArray.length; i++) { + * text(numArray[i], i * spacing, elemsY); + * } + * maxX = 33; + * maxY = 80; + * // Draw the Maximum value in the array. + * textSize(32); + * text(max(numArray), maxX, maxY); + * } + * </code></div> + */ +p5.prototype.max = function() { + if (arguments[0] instanceof Array) { + return Math.max.apply(null,arguments[0]); + } else { + return Math.max.apply(null,arguments); + } +}; + +/** + * Determines the smallest value in a sequence of numbers, and then returns + * that value. min() accepts any number of Number parameters, or an Array + * of any length. + * + * @method min + * @param {Number|Array} n0 Numbers to compare + * @return {Number} minimum Number + * @example + * <div><code> + * function setup() { + * // Change the elements in the array and run the sketch + * // to show how min() works! + * numArray = new Array(2,1,5,4,8,9); + * fill(0); + * noStroke(); + * text("Array Elements", 0, 10); + * // Draw all numbers in the array + * var spacing = 15; + * var elemsY = 25; + * for(var i = 0; i < numArray.length; i++) { + * text(numArray[i], i * spacing, elemsY); + * } + * maxX = 33; + * maxY = 80; + * // Draw the Minimum value in the array. + * textSize(32); + * text(min(numArray), maxX, maxY); + * } + * </code></div> + */ +p5.prototype.min = function() { + if (arguments[0] instanceof Array) { + return Math.min.apply(null,arguments[0]); + } else { + return Math.min.apply(null,arguments); + } +}; + +/** + * Normalizes a number from another range into a value between 0 and 1. + * Identical to map(value, low, high, 0, 1). + * Numbers outside of the range are not clamped to 0 and 1, because + * out-of-range values are often intentional and useful. (See the second + * example above.) + * + * @method norm + * @param {Number} value incoming value to be normalized + * @param {Number} start lower bound of the value's current range + * @param {Number} stop upper bound of the value's current range + * @return {Number} normalized number + * @example + * <div><code> + * function draw() { + * background(200); + * currentNum = mouseX; + * lowerBound = 0; + * upperBound = width; //100; + * normalized = norm(currentNum, lowerBound, upperBound); + * lineY = 70 + * line(0, lineY, width, lineY); + * //Draw an ellipse mapped to the non-normalized value. + * noStroke(); + * fill(50) + * var s = 7; // ellipse size + * ellipse(currentNum, lineY, s, s); + * + * // Draw the guide + * guideY = lineY + 15; + * text("0", 0, guideY); + * textAlign(RIGHT); + * text("100", width, guideY); + * + * // Draw the normalized value + * textAlign(LEFT); + * fill(0); + * textSize(32); + * normalY = 40; + * normalX = 20; + * text(normalized, normalX, normalY); + * } + * </code></div> + */ +p5.prototype.norm = function(n, start, stop) { + return this.map(n, start, stop, 0, 1); +}; + +/** + * Facilitates exponential expressions. The pow() function is an efficient + * way of multiplying numbers by themselves (or their reciprocals) in large + * quantities. For example, pow(3, 5) is equivalent to the expression + * 3*3*3*3*3 and pow(3, -5) is equivalent to 1 / 3*3*3*3*3. Maps to + * Math.pow(). + * + * @method pow + * @param {Number} n base of the exponential expression + * @param {Number} e power by which to raise the base + * @return {Number} n^e + * @example + * <div><code> + * function setup() { + * //Exponentially increase the size of an ellipse. + * eSize = 3; // Original Size + * eLoc = 10; // Original Location + * + * ellipse(eLoc, eLoc, eSize, eSize); + * + * ellipse(eLoc*2, eLoc*2, pow(eSize, 2), pow(eSize, 2)); + * + * ellipse(eLoc*4, eLoc*4, pow(eSize, 3), pow(eSize, 3)); + * + * ellipse(eLoc*8, eLoc*8, pow(eSize, 4), pow(eSize, 4)); + * } + * </code></div> + */ +p5.prototype.pow = Math.pow; + +/** + * Calculates the integer closest to the n parameter. For example, + * round(133.8) returns the value 134. Maps to Math.round(). + * + * @method round + * @param {Number} n number to round + * @return {Number} rounded number + * @example + * <div><code> + * function draw() { + * background(200); + * //map, mouseX between 0 and 5. + * var ax = map(mouseX, 0, 100, 0, 5); + * var ay = 66; + * + * // Round the mapped number. + * var bx = round(map(mouseX, 0, 100, 0,5)); + * var by = 33; + * + * // Multiply the mapped numbers by 20 to more easily + * // see the changes. + * stroke(0); + * fill(0); + * line(0, ay, ax * 20, ay); + * line(0, by, bx * 20, by); + * + * // Reformat the float returned by map and draw it. + * noStroke(); + * text(nfc(ax, 2,2), ax, ay - 5); + * text(nfc(bx,1,1), bx, by - 5); + * } + * </code></div> + */ +p5.prototype.round = Math.round; + +/** + * Squares a number (multiplies a number by itself). The result is always a + * positive number, as multiplying two negative numbers always yields a + * positive result. For example, -1 * -1 = 1. + * + * @method sq + * @param {Number} n number to square + * @return {Number} squared number + * @example + * <div><code> + * function draw() { + * background(200); + * eSize = 7; + * x1 = map(mouseX, 0, width, 0, 10); + * y1 = 80; + * x2 = sq(x1); + * y2 = 20; + * + * // Draw the non-squared. + * line(0, y1, width, y1); + * ellipse(x1, y1, eSize, eSize); + * + * // Draw the squared. + * line(0, y2, width, y2); + * ellipse(x2, y2, eSize, eSize); + * + * // Draw dividing line. + * stroke(100) + * line(0, height/2, width, height/2); + * + * // Draw text. + * var spacing = 15; + * noStroke(); + * fill(0); + * text("x = " + x1, 0, y1 + spacing); + * text("sq(x) = " + x2, 0, y2 + spacing); + * } + * </code></div> + */ +p5.prototype.sq = function(n) { return n*n; }; + +/** + * Calculates the square root of a number. The square root of a number is + * always positive, even though there may be a valid negative root. The + * square root s of number a is such that s*s = a. It is the opposite of + * squaring. Maps to Math.sqrt(). + * + * @method sqrt + * @param {Number} n non-negative number to square root + * @return {Number} square root of number + * @example + * <div><code> + * function draw() { + * background(200); + * eSize = 7; + * x1 = mouseX; + * y1 = 80; + * x2 = sqrt(x1); + * y2 = 20; + * + * // Draw the non-squared. + * line(0, y1, width, y1); + * ellipse(x1, y1, eSize, eSize); + * + * // Draw the squared. + * line(0, y2, width, y2); + * ellipse(x2, y2, eSize, eSize); + * + * // Draw dividing line. + * stroke(100) + * line(0, height/2, width, height/2); + * + * // Draw text. + * noStroke(); + * fill(0); + * var spacing = 15; + * text("x = " + x1, 0, y1 + spacing); + * text("sqrt(x) = " + x2, 0, y2 + spacing); + * } + * </code></div> + */ +p5.prototype.sqrt = Math.sqrt; + +module.exports = p5; + +},{"../core/core":48}],74:[function(_dereq_,module,exports){ +/** + * @module Math + * @submodule Math + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + + +/** + * Creates a new p5.Vector (the datatype for storing vectors). This provides a + * two or three dimensional vector, specifically a Euclidean (also known as + * geometric) vector. A vector is an entity that has both magnitude and + * direction. + * + * @method createVector + * @param {Number} [x] x component of the vector + * @param {Number} [y] y component of the vector + * @param {Number} [z] z component of the vector + */ +p5.prototype.createVector = function (x, y, z) { + if (this instanceof p5) { + return new p5.Vector(this, arguments); + } else { + return new p5.Vector(x, y, z); + } +}; + +module.exports = p5; + +},{"../core/core":48}],75:[function(_dereq_,module,exports){ +////////////////////////////////////////////////////////////// + +// http://mrl.nyu.edu/~perlin/noise/ +// Adapting from PApplet.java +// which was adapted from toxi +// which was adapted from the german demo group farbrausch +// as used in their demo "art": http://www.farb-rausch.de/fr010src.zip + +// someday we might consider using "improved noise" +// http://mrl.nyu.edu/~perlin/paper445.pdf +// See: https://github.com/shiffman/The-Nature-of-Code-Examples-p5.js/ +// blob/master/introduction/Noise1D/noise.js + +/** + * @module Math + * @submodule Noise + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +var PERLIN_YWRAPB = 4; +var PERLIN_YWRAP = 1<<PERLIN_YWRAPB; +var PERLIN_ZWRAPB = 8; +var PERLIN_ZWRAP = 1<<PERLIN_ZWRAPB; +var PERLIN_SIZE = 4095; + +var perlin_octaves = 4; // default to medium smooth +var perlin_amp_falloff = 0.5; // 50% reduction/octave + +var scaled_cosine = function(i) { + return 0.5*(1.0-Math.cos(i*Math.PI)); +}; + +var perlin; // will be initialized lazily by noise() or noiseSeed() + + +/** + * Returns the Perlin noise value at specified coordinates. Perlin noise is + * a random sequence generator producing a more natural ordered, harmonic + * succession of numbers compared to the standard <b>random()</b> function. + * It was invented by Ken Perlin in the 1980s and been used since in + * graphical applications to produce procedural textures, natural motion, + * shapes, terrains etc.<br /><br /> The main difference to the + * <b>random()</b> function is that Perlin noise is defined in an infinite + * n-dimensional space where each pair of coordinates corresponds to a + * fixed semi-random value (fixed only for the lifespan of the program; see + * the noiseSeed() function). p5.js can compute 1D, 2D and 3D noise, + * depending on the number of coordinates given. The resulting value will + * always be between 0.0 and 1.0. The noise value can be animated by moving + * through the noise space as demonstrated in the example above. The 2nd + * and 3rd dimension can also be interpreted as time.<br /><br />The actual + * noise is structured similar to an audio signal, in respect to the + * function's use of frequencies. Similar to the concept of harmonics in + * physics, perlin noise is computed over several octaves which are added + * together for the final result. <br /><br />Another way to adjust the + * character of the resulting sequence is the scale of the input + * coordinates. As the function works within an infinite space the value of + * the coordinates doesn't matter as such, only the distance between + * successive coordinates does (eg. when using <b>noise()</b> within a + * loop). As a general rule the smaller the difference between coordinates, + * the smoother the resulting noise sequence will be. Steps of 0.005-0.03 + * work best for most applications, but this will differ depending on use. + * + * + * @method noise + * @param {Number} x x-coordinate in noise space + * @param {Number} y y-coordinate in noise space + * @param {Number} z z-coordinate in noise space + * @return {Number} Perlin noise value (between 0 and 1) at specified + * coordinates + * @example + * <div> + * <code>var xoff = 0.0; + * + * function draw() { + * background(204); + * xoff = xoff + .01; + * var n = noise(xoff) * width; + * line(n, 0, n, height); + * } + * </code> + * </div> + * <div> + * <code>var noiseScale=0.02; + * + * function draw() { + * background(0); + * for (var x=0; x < width; x++) { + * var noiseVal = noise((mouseX+x)*noiseScale, mouseY*noiseScale); + * stroke(noiseVal*255); + * line(x, mouseY+noiseVal*80, x, height); + * } + * } + * </code> + * </div> + */ +p5.prototype.noise = function(x,y,z) { + y = y || 0; + z = z || 0; + + if (perlin == null) { + perlin = new Array(PERLIN_SIZE + 1); + for (var i = 0; i < PERLIN_SIZE + 1; i++) { + perlin[i] = Math.random(); + } + } + + if (x<0) { x=-x; } + if (y<0) { y=-y; } + if (z<0) { z=-z; } + + var xi=Math.floor(x), yi=Math.floor(y), zi=Math.floor(z); + var xf = x - xi; + var yf = y - yi; + var zf = z - zi; + var rxf, ryf; + + var r=0; + var ampl=0.5; + + var n1,n2,n3; + + for (var o=0; o<perlin_octaves; o++) { + var of=xi+(yi<<PERLIN_YWRAPB)+(zi<<PERLIN_ZWRAPB); + + rxf = scaled_cosine(xf); + ryf = scaled_cosine(yf); + + n1 = perlin[of&PERLIN_SIZE]; + n1 += rxf*(perlin[(of+1)&PERLIN_SIZE]-n1); + n2 = perlin[(of+PERLIN_YWRAP)&PERLIN_SIZE]; + n2 += rxf*(perlin[(of+PERLIN_YWRAP+1)&PERLIN_SIZE]-n2); + n1 += ryf*(n2-n1); + + of += PERLIN_ZWRAP; + n2 = perlin[of&PERLIN_SIZE]; + n2 += rxf*(perlin[(of+1)&PERLIN_SIZE]-n2); + n3 = perlin[(of+PERLIN_YWRAP)&PERLIN_SIZE]; + n3 += rxf*(perlin[(of+PERLIN_YWRAP+1)&PERLIN_SIZE]-n3); + n2 += ryf*(n3-n2); + + n1 += scaled_cosine(zf)*(n2-n1); + + r += n1*ampl; + ampl *= perlin_amp_falloff; + xi<<=1; + xf*=2; + yi<<=1; + yf*=2; + zi<<=1; + zf*=2; + + if (xf>=1.0) { xi++; xf--; } + if (yf>=1.0) { yi++; yf--; } + if (zf>=1.0) { zi++; zf--; } + } + return r; +}; + + +/** + * + * Adjusts the character and level of detail produced by the Perlin noise + * function. Similar to harmonics in physics, noise is computed over + * several octaves. Lower octaves contribute more to the output signal and + * as such define the overall intensity of the noise, whereas higher octaves + * create finer grained details in the noise sequence. + * <br><br> + * By default, noise is computed over 4 octaves with each octave contributing + * exactly half than its predecessor, starting at 50% strength for the 1st + * octave. This falloff amount can be changed by adding an additional function + * parameter. Eg. a falloff factor of 0.75 means each octave will now have + * 75% impact (25% less) of the previous lower octave. Any value between + * 0.0 and 1.0 is valid, however note that values greater than 0.5 might + * result in greater than 1.0 values returned by <b>noise()</b>. + * <br><br> + * By changing these parameters, the signal created by the <b>noise()</b> + * function can be adapted to fit very specific needs and characteristics. + * + * @method noiseDetail + * @param {Number} lod number of octaves to be used by the noise + * @param {Number} falloff falloff factor for each octave + * @example + * <div> + * <code> + * + * var noiseVal; + * var noiseScale=0.02; + * + * function setup() { + * createCanvas(100,100); + * } + * + * function draw() { + * background(0); + * for (var y = 0; y < height; y++) { + * for (var x = 0; x < width/2; x++) { + * noiseDetail(2,0.2); + * noiseVal = noise((mouseX+x) * noiseScale, + * (mouseY+y) * noiseScale); + * stroke(noiseVal*255); + * point(x,y); + * noiseDetail(8,0.65); + * noiseVal = noise((mouseX + x + width/2) * noiseScale, + * (mouseY + y) * noiseScale); + * stroke(noiseVal*255); + * point(x + width/2, y); + * } + * } + * } + * </code> + * </div> + */ +p5.prototype.noiseDetail = function(lod, falloff) { + if (lod>0) { perlin_octaves=lod; } + if (falloff>0) { perlin_amp_falloff=falloff; } +}; + +/** + * Sets the seed value for <b>noise()</b>. By default, <b>noise()</b> + * produces different results each time the program is run. Set the + * <b>value</b> parameter to a constant to return the same pseudo-random + * numbers each time the software is run. + * + * @method noiseSeed + * @param {Number} seed the seed value + * @example + * <div> + * <code>var xoff = 0.0; + * + * function setup() { + * noiseSeed(99); + * stroke(0, 10); + * } + * + * function draw() { + * xoff = xoff + .01; + * var n = noise(xoff) * width; + * line(n, 0, n, height); + * } + * </code> + * </div> + */ +p5.prototype.noiseSeed = function(seed) { + // Linear Congruential Generator + // Variant of a Lehman Generator + var lcg = (function() { + // Set to values from http://en.wikipedia.org/wiki/Numerical_Recipes + // m is basically chosen to be large (as it is the max period) + // and for its relationships to a and c + var m = 4294967296, + // a - 1 should be divisible by m's prime factors + a = 1664525, + // c and m should be co-prime + c = 1013904223, + seed, z; + return { + setSeed : function(val) { + // pick a random seed if val is undefined or null + // the >>> 0 casts the seed to an unsigned 32-bit integer + z = seed = (val == null ? Math.random() * m : val) >>> 0; + }, + getSeed : function() { + return seed; + }, + rand : function() { + // define the recurrence relationship + z = (a * z + c) % m; + // return a float in [0, 1) + // if z = m then z / m = 0 therefore (z % m) / m < 1 always + return z / m; + } + }; + }()); + + lcg.setSeed(seed); + perlin = new Array(PERLIN_SIZE + 1); + for (var i = 0; i < PERLIN_SIZE + 1; i++) { + perlin[i] = lcg.rand(); + } +}; + +module.exports = p5; + +},{"../core/core":48}],76:[function(_dereq_,module,exports){ +/** + * @module Math + * @submodule Math + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var polarGeometry = _dereq_('./polargeometry'); +var constants = _dereq_('../core/constants'); + +/** + * A class to describe a two or three dimensional vector, specifically + * a Euclidean (also known as geometric) vector. A vector is an entity + * that has both magnitude and direction. The datatype, however, stores + * the components of the vector (x, y for 2D, and x, y, z for 3D). The magnitude + * and direction can be accessed via the methods mag() and heading(). + * <br><br> + * In many of the p5.js examples, you will see p5.Vector used to describe a + * position, velocity, or acceleration. For example, if you consider a rectangle + * moving across the screen, at any given instant it has a position (a vector + * that points from the origin to its location), a velocity (the rate at which + * the object's position changes per time unit, expressed as a vector), and + * acceleration (the rate at which the object's velocity changes per time + * unit, expressed as a vector). + * <br><br> + * Since vectors represent groupings of values, we cannot simply use + * traditional addition/multiplication/etc. Instead, we'll need to do some + * "vector" math, which is made easy by the methods inside the p5.Vector class. + * + * @class p5.Vector + * @constructor + * @param {Number} [x] x component of the vector + * @param {Number} [y] y component of the vector + * @param {Number} [z] z component of the vector + * @example + * <div> + * <code> + * var v1 = createVector(40, 50); + * var v2 = createVector(40, 50); + * + * ellipse(v1.x, v1.y, 50, 50); + * ellipse(v2.x, v2.y, 50, 50); + * v1.add(v2); + * ellipse(v1.x, v1.y, 50, 50); + * </code> + * </div> + */ +p5.Vector = function() { + var x,y,z; + // This is how it comes in with createVector() + if(arguments[0] instanceof p5) { + // save reference to p5 if passed in + this.p5 = arguments[0]; + x = arguments[1][0] || 0; + y = arguments[1][1] || 0; + z = arguments[1][2] || 0; + // This is what we'll get with new p5.Vector() + } else { + x = arguments[0] || 0; + y = arguments[1] || 0; + z = arguments[2] || 0; + } + /** + * The x component of the vector + * @property x + * @type {Number} + */ + this.x = x; + /** + * The y component of the vector + * @property y + * @type {Number} + */ + this.y = y; + /** + * The z component of the vector + * @property z + * @type {Number} + */ + this.z = z; +}; + +/** + * Returns a string representation of a vector v by calling String(v) + * or v.toString(). This method is useful for logging vectors in the + * console. + * @method toString + * @example + * <div class = "norender"><code> + * function setup() { + * var v = createVector(20,30); + * print(String(v)); // prints "p5.Vector Object : [20, 30, 0]" + * } + * </div></code> + * + */ +p5.Vector.prototype.toString = function p5VectorToString() { + return 'p5.Vector Object : ['+ this.x +', '+ this.y +', '+ this.z + ']'; +}; + +/** + * Sets the x, y, and z component of the vector using two or three separate + * variables, the data from a p5.Vector, or the values from a float array. + * @method set + * + * @param {Number|p5.Vector|Array} [x] the x component of the vector or a + * p5.Vector or an Array + * @param {Number} [y] the y component of the vector + * @param {Number} [z] the z component of the vector + * @example + * <div class="norender"> + * <code> + * function setup() { + * var v = createVector(1, 2, 3); + * v.set(4,5,6); // Sets vector to [4, 5, 6] + * + * var v1 = createVector(0, 0, 0); + * var arr = [1, 2, 3]; + * v1.set(arr); // Sets vector to [1, 2, 3] + * } + * </code> + * </div> + */ +p5.Vector.prototype.set = function (x, y, z) { + if (x instanceof p5.Vector) { + this.x = x.x || 0; + this.y = x.y || 0; + this.z = x.z || 0; + return this; + } + if (x instanceof Array) { + this.x = x[0] || 0; + this.y = x[1] || 0; + this.z = x[2] || 0; + return this; + } + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + return this; +}; + +/** + * Gets a copy of the vector, returns a p5.Vector object. + * + * @method copy + * @return {p5.Vector} the copy of the p5.Vector object + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(1, 2, 3); + * var v2 = v.copy(); + * print(v1.x == v2.x && v1.y == v2.y && v1.z == v2.z); + * // Prints "true" + * </code> + * </div> + */ +p5.Vector.prototype.copy = function () { + if (this.p5) { + return new p5.Vector(this.p5,[this.x, this.y, this.z]); + } else { + return new p5.Vector(this.x,this.y,this.z); + } +}; + +/** + * Adds x, y, and z components to a vector, adds one vector to another, or + * adds two independent vectors together. The version of the method that adds + * two vectors together is a static method and returns a p5.Vector, the others + * acts directly on the vector. See the examples for more context. + * + * @method add + * @chainable + * @param {Number|p5.Vector|Array} x the x component of the vector to be + * added or a p5.Vector or an Array + * @param {Number} [y] the y component of the vector to be + * added + * @param {Number} [z] the z component of the vector to be + * added + * @return {p5.Vector} the p5.Vector object. + * @example + * <div class="norender"> + * <code> + * var v = createVector(1, 2, 3); + * v.add(4,5,6); + * // v's compnents are set to [5, 7, 9] + * </code> + * </div> + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(1, 2, 3); + * var v2 = createVector(2, 3, 4); + * + * var v3 = p5.Vector.add(v1, v2); + * // v3 has components [3, 5, 7] + * </code> + * </div> + */ +p5.Vector.prototype.add = function (x, y, z) { + if (x instanceof p5.Vector) { + this.x += x.x || 0; + this.y += x.y || 0; + this.z += x.z || 0; + return this; + } + if (x instanceof Array) { + this.x += x[0] || 0; + this.y += x[1] || 0; + this.z += x[2] || 0; + return this; + } + this.x += x || 0; + this.y += y || 0; + this.z += z || 0; + return this; +}; + +/** + * Subtracts x, y, and z components from a vector, subtracts one vector from + * another, or subtracts two independent vectors. The version of the method + * that subtracts two vectors is a static method and returns a p5.Vector, the + * other acts directly on the vector. See the examples for more context. + * + * @method sub + * @chainable + * @param {Number|p5.Vector|Array} x the x component of the vector or a + * p5.Vector or an Array + * @param {Number} [y] the y component of the vector + * @param {Number} [z] the z component of the vector + * @return {p5.Vector} p5.Vector object. + * @example + * <div class="norender"> + * <code> + * var v = createVector(4, 5, 6); + * v.sub(1, 1, 1); + * // v's compnents are set to [3, 4, 5] + * </code> + * </div> + * + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(2, 3, 4); + * var v2 = createVector(1, 2, 3); + * + * var v3 = p5.Vector.sub(v1, v2); + * // v3 has compnents [1, 1, 1] + * </code> + * </div> + */ +p5.Vector.prototype.sub = function (x, y, z) { + if (x instanceof p5.Vector) { + this.x -= x.x || 0; + this.y -= x.y || 0; + this.z -= x.z || 0; + return this; + } + if (x instanceof Array) { + this.x -= x[0] || 0; + this.y -= x[1] || 0; + this.z -= x[2] || 0; + return this; + } + this.x -= x || 0; + this.y -= y || 0; + this.z -= z || 0; + return this; +}; + +/** + * Multiply the vector by a scalar. The static version of this method + * creates a new p5.Vector while the non static version acts on the vector + * directly. See the examples for more context. + * + * @method mult + * @chainable + * @param {Number} n the number to multiply with the vector + * @return {p5.Vector} a reference to the p5.Vector object (allow chaining) + * @example + * <div class="norender"> + * <code> + * var v = createVector(1, 2, 3); + * v.mult(2); + * // v's compnents are set to [2, 4, 6] + * </code> + * </div> + * + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(1, 2, 3); + * var v2 = p5.Vector.mult(v1, 2); + * // v2 has compnents [2, 4, 6] + * </code> + * </div> + */ +p5.Vector.prototype.mult = function (n) { + this.x *= n || 0; + this.y *= n || 0; + this.z *= n || 0; + return this; +}; + +/** + * Divide the vector by a scalar. The static version of this method creates a + * new p5.Vector while the non static version acts on the vector directly. + * See the examples for more context. + * + * @method div + * @chainable + * @param {number} n the number to divide the vector by + * @return {p5.Vector} a reference to the p5.Vector object (allow chaining) + * @example + * <div class="norender"> + * <code> + * var v = createVector(6, 4, 2); + * v.div(2); //v's compnents are set to [3, 2, 1] + * </code> + * </div> + * + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(6, 4, 2); + * var v2 = p5.Vector.div(v, 2); + * // v2 has compnents [3, 2, 1] + * </code> + * </div> + */ +p5.Vector.prototype.div = function (n) { + this.x /= n; + this.y /= n; + this.z /= n; + return this; +}; + +/** + * Calculates the magnitude (length) of the vector and returns the result as + * a float (this is simply the equation sqrt(x*x + y*y + z*z).) + * + * @method mag + * @return {Number} magnitude of the vector + * @example + * <div class="norender"> + * <code> + * var v = createVector(20.0, 30.0, 40.0); + * var m = v.mag(10); + * print(m); // Prints "53.85164807134504" + * </code> + * </div> + */ +p5.Vector.prototype.mag = function () { + return Math.sqrt(this.magSq()); +}; + +/** + * Calculates the squared magnitude of the vector and returns the result + * as a float (this is simply the equation <em>(x*x + y*y + z*z)</em>.) + * Faster if the real length is not required in the + * case of comparing vectors, etc. + * + * @method magSq + * @return {number} squared magnitude of the vector + * @example + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(6, 4, 2); + * print(v1.magSq()); // Prints "56" + * </code> + * </div> + */ +p5.Vector.prototype.magSq = function () { + var x = this.x, y = this.y, z = this.z; + return (x * x + y * y + z * z); +}; + +/** + * Calculates the dot product of two vectors. The version of the method + * that computes the dot product of two independent vectors is a static + * method. See the examples for more context. + * + * + * @method dot + * @param {Number|p5.Vector} x x component of the vector or a p5.Vector + * @param {Number} [y] y component of the vector + * @param {Number} [z] z component of the vector + * @return {Number} the dot product + * + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(1, 2, 3); + * var v2 = createVector(2, 3, 4); + * + * print(v1.dot(v2)); // Prints "20" + * </code> + * </div> + * + * <div class="norender"> + * <code> + * //Static method + * var v1 = createVector(1, 2, 3); + * var v2 = createVector(3, 2, 1); + * print (p5.Vector.dot(v1, v2)); // Prints "10" + * </code> + * </div> + */ +p5.Vector.prototype.dot = function (x, y, z) { + if (x instanceof p5.Vector) { + return this.dot(x.x, x.y, x.z); + } + return this.x * (x || 0) + + this.y * (y || 0) + + this.z * (z || 0); +}; + +/** + * Calculates and returns a vector composed of the cross product between + * two vectors. Both the static and non static methods return a new p5.Vector. + * See the examples for more context. + * + * @method cross + * @param {p5.Vector} v p5.Vector to be crossed + * @return {p5.Vector} p5.Vector composed of cross product + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(1, 2, 3); + * var v2 = createVector(1, 2, 3); + * + * v1.cross(v2); // v's components are [0, 0, 0] + * </code> + * </div> + * + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(1, 0, 0); + * var v2 = createVector(0, 1, 0); + * + * var crossProduct = p5.Vector.cross(v1, v2); + * // crossProduct has components [0, 0, 1] + * </code> + * </div> + */ +p5.Vector.prototype.cross = function (v) { + var x = this.y * v.z - this.z * v.y; + var y = this.z * v.x - this.x * v.z; + var z = this.x * v.y - this.y * v.x; + if (this.p5) { + return new p5.Vector(this.p5,[x,y,z]); + } else { + return new p5.Vector(x,y,z); + } +}; + +/** + * Calculates the Euclidean distance between two points (considering a + * point as a vector object). + * + * @method dist + * @param {p5.Vector} v the x, y, and z coordinates of a p5.Vector + * @return {Number} the distance + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(1, 0, 0); + * var v2 = createVector(0, 1, 0); + * + * var distance = v1.dist(v2); // distance is 1.4142... + * </code> + * </div> + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(1, 0, 0); + * var v2 = createVector(0, 1, 0); + * + * var distance = p5.Vector.dist(v1,v2); + * // distance is 1.4142... + * </code> + * </div> + */ +p5.Vector.prototype.dist = function (v) { + var d = v.copy().sub(this); + return d.mag(); +}; + +/** + * Normalize the vector to length 1 (make it a unit vector). + * + * @method normalize + * @return {p5.Vector} normalized p5.Vector + * @example + * <div class="norender"> + * <code> + * var v = createVector(10, 20, 2); + * // v has compnents [10.0, 20.0, 2.0] + * v.normalize(); + * // v's compnents are set to + * // [0.4454354, 0.8908708, 0.089087084] + * </code> + * </div> + * + */ +p5.Vector.prototype.normalize = function () { + return this.div(this.mag()); +}; + +/** + * Limit the magnitude of this vector to the value used for the <b>max</b> + * parameter. + * + * @method limit + * @param {Number} max the maximum magnitude for the vector + * @return {p5.Vector} the modified p5.Vector + * @example + * <div class="norender"> + * <code> + * var v = createVector(10, 20, 2); + * // v has compnents [10.0, 20.0, 2.0] + * v.limit(5); + * // v's compnents are set to + * // [2.2271771, 4.4543543, 0.4454354] + * </code> + * </div> + */ +p5.Vector.prototype.limit = function (l) { + var mSq = this.magSq(); + if(mSq > l*l) { + this.div(Math.sqrt(mSq)); //normalize it + this.mult(l); + } + return this; +}; + +/** + * Set the magnitude of this vector to the value used for the <b>len</b> + * parameter. + * + * @method setMag + * @param {number} len the new length for this vector + * @return {p5.Vector} the modified p5.Vector + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(10, 20, 2); + * // v has compnents [10.0, 20.0, 2.0] + * v1.setMag(10); + * // v's compnents are set to [6.0, 8.0, 0.0] + * </code> + * </div> + */ +p5.Vector.prototype.setMag = function (n) { + return this.normalize().mult(n); +}; + +/** + * Calculate the angle of rotation for this vector (only 2D vectors) + * + * @method heading + * @return {Number} the angle of rotation + * @example + * <div class = "norender"><code> + * function setup() { + * var v1 = createVector(30,50); + * print(v1.heading()); // 1.0303768265243125 + * + * var v1 = createVector(40,50); + * print(v1.heading()); // 0.8960553845713439 + * + * var v1 = createVector(30,70); + * print(v1.heading()); // 1.1659045405098132 + * } + * </div></code> + */ +p5.Vector.prototype.heading = function () { + var h = Math.atan2(this.y, this.x); + if (this.p5) { + if (this.p5._angleMode === constants.RADIANS) { + return h; + } else { + return polarGeometry.radiansToDegrees(h); + } + } else { + return h; + } +}; + +/** + * Rotate the vector by an angle (only 2D vectors), magnitude remains the + * same + * + * @method rotate + * @param {number} angle the angle of rotation + * @return {p5.Vector} the modified p5.Vector + * @example + * <div class="norender"> + * <code> + * var v = createVector(10.0, 20.0); + * // v has compnents [10.0, 20.0, 0.0] + * v.rotate(HALF_PI); + * // v's compnents are set to [-20.0, 9.999999, 0.0] + * </code> + * </div> + */ +p5.Vector.prototype.rotate = function (a) { + if (this.p5) { + if (this.p5._angleMode === constants.DEGREES) { + a = polarGeometry.degreesToRadians(a); + } + } + var newHeading = this.heading() + a; + var mag = this.mag(); + this.x = Math.cos(newHeading) * mag; + this.y = Math.sin(newHeading) * mag; + return this; +}; + +/** + * Linear interpolate the vector to another vector + * + * @method lerp + * @param {p5.Vector} x the x component or the p5.Vector to lerp to + * @param {p5.Vector} [y] y the y component + * @param {p5.Vector} [z] z the z component + * @param {Number} amt the amount of interpolation; some value between 0.0 + * (old vector) and 1.0 (new vector). 0.1 is very near + * the new vector. 0.5 is halfway in between. + * @return {p5.Vector} the modified p5.Vector + * @example + * <div class="norender"> + * <code> + * var v = createVector(1, 1, 0); + * + * v.lerp(3, 3, 0, 0.5); // v now has components [2,2,0] + * </code> + * </div> + * + * <div class="norender"> + * <code> + * var v1 = createVector(0, 0, 0); + * var v2 = createVector(100, 100, 0); + * + * var v3 = p5.Vector.lerp(v1, v2, 0.5); + * // v3 has components [50,50,0] + * </code> + * </div> + */ +p5.Vector.prototype.lerp = function (x, y, z, amt) { + if (x instanceof p5.Vector) { + return this.lerp(x.x, x.y, x.z, y); + } + this.x += (x - this.x) * amt || 0; + this.y += (y - this.y) * amt || 0; + this.z += (z - this.z) * amt || 0; + return this; +}; + +/** + * Return a representation of this vector as a float array. This is only + * for temporary use. If used in any other fashion, the contents should be + * copied by using the <b>p5.Vector.copy()</b> method to copy into your own + * array. + * + * @method array + * @return {Array} an Array with the 3 values + * @example + * <div class = "norender"><code> + * function setup() { + * var v = createVector(20,30); + * print(v.array()); // Prints : Array [20, 30, 0] + * } + * </div></code> + * <div class="norender"> + * <code> + * var v = createVector(10.0, 20.0, 30.0); + * var f = v.array(); + * print(f[0]); // Prints "10.0" + * print(f[1]); // Prints "20.0" + * print(f[2]); // Prints "30.0" + * </code> + * </div> + */ +p5.Vector.prototype.array = function () { + return [this.x || 0, this.y || 0, this.z || 0]; +}; + +/** + * Equality check against a p5.Vector + * + * @method equals + * @param {Number|p5.Vector|Array} [x] the x component of the vector or a + * p5.Vector or an Array + * @param {Number} [y] the y component of the vector + * @param {Number} [z] the z component of the vector + * @return {Boolean} whether the vectors are equals + * @example + * <div class = "norender"><code> + * v1 = createVector(5,10,20); + * v2 = createVector(5,10,20); + * v3 = createVector(13,10,19); + * + * print(v1.equals(v2.x,v2.y,v2.z)); // true + * print(v1.equals(v3.x,v3.y,v3.z)); // false + * </div></code> + * <div class="norender"> + * <code> + * var v1 = createVector(10.0, 20.0, 30.0); + * var v2 = createVector(10.0, 20.0, 30.0); + * var v3 = createVector(0.0, 0.0, 0.0); + * print (v1.equals(v2)) // true + * print (v1.equals(v3)) // false + * </code> + * </div> + */ +p5.Vector.prototype.equals = function (x, y, z) { + var a, b, c; + if (x instanceof p5.Vector) { + a = x.x || 0; + b = x.y || 0; + c = x.z || 0; + } else if (x instanceof Array) { + a = x[0] || 0; + b = x[1] || 0; + c = x[2] || 0; + } else { + a = x || 0; + b = y || 0; + c = z || 0; + } + return this.x === a && this.y === b && this.z === c; +}; + + +// Static Methods + + +/** + * Make a new 2D unit vector from an angle + * + * @method fromAngle + * @static + * @param {Number} angle the desired angle + * @return {p5.Vector} the new p5.Vector object + * @example + * <div> + * <code> + * function draw() { + * background (200); + * + * // Create a variable, proportional to the mouseX, + * // varying from 0-360, to represent an angle in degrees. + * angleMode(DEGREES); + * var myDegrees = map(mouseX, 0,width, 0,360); + * + * // Display that variable in an onscreen text. + * // (Note the nfc() function to truncate additional decimal places, + * // and the "\xB0" character for the degree symbol.) + * var readout = "angle = " + nfc(myDegrees,1,1) + "\xB0" + * noStroke(); + * fill (0); + * text (readout, 5, 15); + * + * // Create a p5.Vector using the fromAngle function, + * // and extract its x and y components. + * var v = p5.Vector.fromAngle(radians(myDegrees)); + * var vx = v.x; + * var vy = v.y; + * + * push(); + * translate (width/2, height/2); + * noFill(); + * stroke (150); + * line (0,0, 30,0); + * stroke (0); + * line (0,0, 30*vx, 30*vy); + * pop() + * } + * </code> + * </div> + */ +p5.Vector.fromAngle = function(angle) { + if (this.p5) { + if (this.p5._angleMode === constants.DEGREES) { + angle = polarGeometry.degreesToRadians(angle); + } + } + if (this.p5) { + return new p5.Vector(this.p5,[Math.cos(angle),Math.sin(angle),0]); + } else { + return new p5.Vector(Math.cos(angle),Math.sin(angle),0); + } +}; + +/** + * Make a new 2D unit vector from a random angle + * + * @method random2D + * @static + * @return {p5.Vector} the new p5.Vector object + * @example + * <div class="norender"> + * <code> + * var v = p5.Vector.random2D(); + * // May make v's attributes something like: + * // [0.61554617, -0.51195765, 0.0] or + * // [-0.4695841, -0.14366731, 0.0] or + * // [0.6091097, -0.22805278, 0.0] + * </code> + * </div> + */ +p5.Vector.random2D = function () { + var angle; + // A lot of nonsense to determine if we know about a + // p5 sketch and whether we should make a random angle in degrees or radians + if (this.p5) { + if (this.p5._angleMode === constants.DEGREES) { + angle = this.p5.random(360); + } else { + angle = this.p5.random(constants.TWO_PI); + } + } else { + angle = Math.random()*Math.PI*2; + } + return this.fromAngle(angle); +}; + +/** + * Make a new random 3D unit vector. + * + * @method random3D + * @static + * @return {p5.Vector} the new p5.Vector object + * @example + * <div class="norender"> + * <code> + * var v = p5.Vector.random3D(); + * // May make v's attributes something like: + * // [0.61554617, -0.51195765, 0.599168] or + * // [-0.4695841, -0.14366731, -0.8711202] or + * // [0.6091097, -0.22805278, -0.7595902] + * </code> + * </div> + */ +p5.Vector.random3D = function () { + var angle,vz; + // If we know about p5 + if (this.p5) { + angle = this.p5.random(0,constants.TWO_PI); + vz = this.p5.random(-1,1); + } else { + angle = Math.random()*Math.PI*2; + vz = Math.random()*2-1; + } + var vx = Math.sqrt(1-vz*vz)*Math.cos(angle); + var vy = Math.sqrt(1-vz*vz)*Math.sin(angle); + if (this.p5) { + return new p5.Vector(this.p5,[vx,vy,vz]); + } else { + return new p5.Vector(vx,vy,vz); + } +}; + + +/** + * Adds two vectors together and returns a new one. + * + * @static + * @param {p5.Vector} v1 a p5.Vector to add + * @param {p5.Vector} v2 a p5.Vector to add + * @param {p5.Vector} target if undefined a new vector will be created + * @return {p5.Vector} the resulting p5.Vector + * + */ + +p5.Vector.add = function (v1, v2, target) { + if (!target) { + target = v1.copy(); + } else { + target.set(v1); + } + target.add(v2); + return target; +}; + +/** + * Subtracts one p5.Vector from another and returns a new one. The second + * vector (v2) is subtracted from the first (v1), resulting in v1-v2. + * + * @static + * @param {p5.Vector} v1 a p5.Vector to subtract from + * @param {p5.Vector} v2 a p5.Vector to subtract + * @param {p5.Vector} target if undefined a new vector will be created + * @return {p5.Vector} the resulting p5.Vector + */ + +p5.Vector.sub = function (v1, v2, target) { + if (!target) { + target = v1.copy(); + } else { + target.set(v1); + } + target.sub(v2); + return target; +}; + + +/** + * Multiplies a vector by a scalar and returns a new vector. + * + * @static + * @param {p5.Vector} v the p5.Vector to multiply + * @param {Number} n the scalar + * @param {p5.Vector} target if undefined a new vector will be created + * @return {p5.Vector} the resulting new p5.Vector + */ +p5.Vector.mult = function (v, n, target) { + if (!target) { + target = v.copy(); + } else { + target.set(v); + } + target.mult(n); + return target; +}; + +/** + * Divides a vector by a scalar and returns a new vector. + * + * @static + * @param {p5.Vector} v the p5.Vector to divide + * @param {Number} n the scalar + * @param {p5.Vector} target if undefined a new vector will be created + * @return {p5.Vector} the resulting new p5.Vector + */ +p5.Vector.div = function (v, n, target) { + if (!target) { + target = v.copy(); + } else { + target.set(v); + } + target.div(n); + return target; +}; + + +/** + * Calculates the dot product of two vectors. + * + * @static + * @param {p5.Vector} v1 the first p5.Vector + * @param {p5.Vector} v2 the second p5.Vector + * @return {Number} the dot product + */ +p5.Vector.dot = function (v1, v2) { + return v1.dot(v2); +}; + +/** + * Calculates the cross product of two vectors. + * + * @static + * @param {p5.Vector} v1 the first p5.Vector + * @param {p5.Vector} v2 the second p5.Vector + * @return {Number} the cross product + */ +p5.Vector.cross = function (v1, v2) { + return v1.cross(v2); +}; + +/** + * Calculates the Euclidean distance between two points (considering a + * point as a vector object). + * + * @static + * @param {p5.Vector} v1 the first p5.Vector + * @param {p5.Vector} v2 the second p5.Vector + * @return {Number} the distance + */ +p5.Vector.dist = function (v1,v2) { + return v1.dist(v2); +}; + +/** + * Linear interpolate a vector to another vector and return the result as a + * new vector. + * + * @static + * @param {p5.Vector} v1 a starting p5.Vector + * @param {p5.Vector} v2 the p5.Vector to lerp to + * @param {Number} the amount of interpolation; some value between 0.0 + * (old vector) and 1.0 (new vector). 0.1 is very near + * the new vector. 0.5 is halfway in between. + */ +p5.Vector.lerp = function (v1, v2, amt, target) { + if (!target) { + target = v1.copy(); + } else { + target.set(v1); + } + target.lerp(v2, amt); + return target; +}; + +/** + * Calculates and returns the angle (in radians) between two vectors. + * @method angleBetween + * @static + * @param {p5.Vector} v1 the x, y, and z components of a p5.Vector + * @param {p5.Vector} v2 the x, y, and z components of a p5.Vector + * @return {Number} the angle between (in radians) + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(1, 0, 0); + * var v2 = createVector(0, 1, 0); + * + * var angle = p5.Vector.angleBetween(v1, v2); + * // angle is PI/2 + * </code> + * </div> + */ +p5.Vector.angleBetween = function (v1, v2) { + var angle = Math.acos(v1.dot(v2) / (v1.mag() * v2.mag())); + if (this.p5) { + if (this.p5._angleMode === constants.DEGREES) { + angle = polarGeometry.radiansToDegrees(angle); + } + } + return angle; +}; + +module.exports = p5.Vector; + +},{"../core/constants":47,"../core/core":48,"./polargeometry":77}],77:[function(_dereq_,module,exports){ + +module.exports = { + + degreesToRadians: function(x) { + return 2 * Math.PI * x / 360; + }, + + radiansToDegrees: function(x) { + return 360 * x / (2 * Math.PI); + } + +}; + +},{}],78:[function(_dereq_,module,exports){ +/** + * @module Math + * @submodule Random + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +var seeded = false; + +// Linear Congruential Generator +// Variant of a Lehman Generator +var lcg = (function() { + // Set to values from http://en.wikipedia.org/wiki/Numerical_Recipes + // m is basically chosen to be large (as it is the max period) + // and for its relationships to a and c + var m = 4294967296, + // a - 1 should be divisible by m's prime factors + a = 1664525, + // c and m should be co-prime + c = 1013904223, + seed, z; + return { + setSeed : function(val) { + // pick a random seed if val is undefined or null + // the >>> 0 casts the seed to an unsigned 32-bit integer + z = seed = (val == null ? Math.random() * m : val) >>> 0; + }, + getSeed : function() { + return seed; + }, + rand : function() { + // define the recurrence relationship + z = (a * z + c) % m; + // return a float in [0, 1) + // if z = m then z / m = 0 therefore (z % m) / m < 1 always + return z / m; + } + }; +}()); + +/** + * Sets the seed value for random(). + * + * By default, random() produces different results each time the program + * is run. Set the seed parameter to a constant to return the same + * pseudo-random numbers each time the software is run. + * + * @method randomSeed + * @param {Number} seed the seed value + * @example + * <div> + * <code> + * randomSeed(99); + * for (var i=0; i < 100; i++) { + * var r = random(0, 255); + * stroke(r); + * line(i, 0, i, 100); + * } + * </code> + * </div> + */ +p5.prototype.randomSeed = function(seed) { + lcg.setSeed(seed); + seeded = true; +}; + +/** + * Return a random number. + * + * Takes either 0, 1 or 2 arguments. + * If no argument is given, returns a random number between 0 and 1. + * If one argument is given, returns a random number between 0 and the number. + * If two arguments are given, returns a random number between them, + * inclusive. + * + * @method random + * @param {Number} min the lower bound + * @param {Number} max the upper bound + * @return {Number} the random number + * @example + * <div> + * <code> + * for (var i = 0; i < 100; i++) { + * var r = random(50); + * stroke(r*5); + * line(50, i, 50+r, i); + * } + * </code> + * </div> + * <div> + * <code> + * for (var i = 0; i < 100; i++) { + * var r = random(-50, 50); + * line(50,i,50+r,i); + * } + * </code> + * </div> + * <div> + * <code> + * // Get a random element from an array + * var words = [ "apple", "bear", "cat", "dog" ]; + * var index = floor(random(words.length)); // Convert to integer + * text(words[index],10,50); // Displays one of the four words + * </code> + * </div> + */ +p5.prototype.random = function (min, max) { + + var rand; + + if (seeded) { + rand = lcg.rand(); + } else { + rand = Math.random(); + } + + if (arguments.length === 0) { + return rand; + } else + if (arguments.length === 1) { + return rand * min; + } else { + if (min > max) { + var tmp = min; + min = max; + max = tmp; + } + + return rand * (max-min) + min; + } +}; + + +/** + * + * Returns a random number fitting a Gaussian, or + * normal, distribution. There is theoretically no minimum or maximum + * value that randomGaussian() might return. Rather, there is + * just a very low probability that values far from the mean will be + * returned; and a higher probability that numbers near the mean will + * be returned. + * <br><br> + * Takes either 0, 1 or 2 arguments.<br> + * If no args, returns a mean of 0 and standard deviation of 1.<br> + * If one arg, that arg is the mean (standard deviation is 1).<br> + * If two args, first is mean, second is standard deviation. + * + * @method randomGaussian + * @param {Number} mean the mean + * @param {Number} sd the standard deviation + * @return {Number} the random number + * @example + * <div> + * <code>for (var y = 0; y < 100; y++) { + * var x = randomGaussian(50,15); + * line(50, y, x, y); + *} + * </code> + * </div> + * <div> + * <code> + *var distribution = new Array(360); + * + *function setup() { + * createCanvas(100, 100); + * for (var i = 0; i < distribution.length; i++) { + * distribution[i] = floor(randomGaussian(0,15)); + * } + *} + * + *function draw() { + * background(204); + * + * translate(width/2, width/2); + * + * for (var i = 0; i < distribution.length; i++) { + * rotate(TWO_PI/distribution.length); + * stroke(0); + * var dist = abs(distribution[i]); + * line(0, 0, dist, 0); + * } + *} + * </code> + * </div> + */ +var y2; +var previous = false; +p5.prototype.randomGaussian = function(mean, sd) { + var y1,x1,x2,w; + if (previous) { + y1 = y2; + previous = false; + } else { + do { + x1 = this.random(2) - 1; + x2 = this.random(2) - 1; + w = x1 * x1 + x2 * x2; + } while (w >= 1); + w = Math.sqrt((-2 * Math.log(w))/w); + y1 = x1 * w; + y2 = x2 * w; + previous = true; + } + + var m = mean || 0; + var s = sd || 1; + return y1*s + m; +}; + +module.exports = p5; + +},{"../core/core":48}],79:[function(_dereq_,module,exports){ +/** + * @module Math + * @submodule Trigonometry + * @for p5 + * @requires core + * @requires polargeometry + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var polarGeometry = _dereq_('./polargeometry'); +var constants = _dereq_('../core/constants'); + +p5.prototype._angleMode = constants.RADIANS; + +/** + * The inverse of cos(), returns the arc cosine of a value. This function + * expects the values in the range of -1 to 1 and values are returned in + * the range 0 to PI (3.1415927). + * + * @method acos + * @param {Number} value the value whose arc cosine is to be returned + * @return {Number} the arc cosine of the given value + * + * @example + * <div class= “norender"> + * <code> + * var a = PI; + * var c = cos(a); + * var ac = acos(c); + * // Prints: "3.1415927 : -1.0 : 3.1415927" + * println(a + " : " + c + " : " + ac); + * </code> + * </div> + * + * <div class= “norender"> + * <code> + * var a = PI + PI/4.0; + * var c = cos(a); + * var ac = acos(c); + * // Prints: "3.926991 : -0.70710665 : 2.3561943" + * println(a + " : " + c + " : " + ac); + * </code> + * </div> + */ +p5.prototype.acos = function(ratio) { + if (this._angleMode === constants.RADIANS) { + return Math.acos(ratio); + } else { + return polarGeometry.radiansToDegrees(Math.acos(ratio)); + } +}; + +/** + * The inverse of sin(), returns the arc sine of a value. This function + * expects the values in the range of -1 to 1 and values are returned + * in the range -PI/2 to PI/2. + * + * @method asin + * @param {Number} value the value whose arc sine is to be returned + * @return {Number} the arc sine of the given value + * + * @example + * <div class= “norender"> + * <code> + * var a = PI + PI/3; + * var s = sin(a); + * var as = asin(s); + * // Prints: "1.0471976 : 0.86602545 : 1.0471976" + * println(a + " : " + s + " : " + as); + * </code> + * </div> + * + * <div class= “norender"> + * <code> + * var a = PI + PI/3.0; + * var s = sin(a); + * var as = asin(s); + * // Prints: "4.1887903 : -0.86602545 : -1.0471976" + * println(a + " : " + s + " : " + as); + * </code> + * </div> + * + */ +p5.prototype.asin = function(ratio) { + if (this._angleMode === constants.RADIANS) { + return Math.asin(ratio); + } else { + return polarGeometry.radiansToDegrees(Math.asin(ratio)); + } +}; + +/** + * The inverse of tan(), returns the arc tangent of a value. This function + * expects the values in the range of -Infinity to Infinity (exclusive) and + * values are returned in the range -PI/2 to PI/2. + * + * @method atan + * @param {Number} value the value whose arc tangent is to be returned + * @return {Number} the arc tangent of the given value + * + * @example + * <div class= “norender"> + * <code> + * var a = PI + PI/3; + * var t = tan(a); + * var at = atan(t); + * // Prints: "1.0471976 : 1.7320509 : 1.0471976" + * println(a + " : " + t + " : " + at); + * </code> + * </div> + * + * <div class= “norender"> + * <code> + * var a = PI + PI/3.0; + * var t = tan(a); + * var at = atan(t); + * // Prints: "4.1887903 : 1.7320513 : 1.0471977" + * println(a + " : " + t + " : " + at); + * </code> + * </div> + * + */ +p5.prototype.atan = function(ratio) { + if (this._angleMode === constants.RADIANS) { + return Math.atan(ratio); + } else { + return polarGeometry.radiansToDegrees(Math.atan(ratio)); + } +}; + +/** + * Calculates the angle (in radians) from a specified point to the coordinate + * origin as measured from the positive x-axis. Values are returned as a + * float in the range from PI to -PI. The atan2() function is most often used + * for orienting geometry to the position of the cursor. + * <br><br> + * Note: The y-coordinate of the point is the first parameter, and the + * x-coordinate is the second parameter, due the the structure of calculating + * the tangent. + * + * @method atan2 + * @param {Number} y y-coordinate of the point + * @param {Number} x x-coordinate of the point + * @return {Number} the arc tangent of the given point + * + * @example + * <div> + * <code> + * function draw() { + * background(204); + * translate(width/2, height/2); + * var a = atan2(mouseY-height/2, mouseX-width/2); + * rotate(a); + * rect(-30, -5, 60, 10); + * } + * </code> + * </div> + */ +p5.prototype.atan2 = function (y, x) { + if (this._angleMode === constants.RADIANS) { + return Math.atan2(y, x); + } else { + return polarGeometry.radiansToDegrees(Math.atan2(y, x)); + } +}; + +/** + * Calculates the cosine of an angle. This function takes into account the + * current angleMode. Values are returned in the range -1 to 1. + * + * @method cos + * @param {Number} angle the angle + * @return {Number} the cosine of the angle + * + * @example + * <div> + * <code> + * var a = 0.0; + * var inc = TWO_PI/25.0; + * for (var i = 0; i < 25; i++) { + * line(i*4, 50, i*4, 50+cos(a)*40.0); + * a = a + inc; + * } + * </code> + * </div> + * + */ +p5.prototype.cos = function(angle) { + if (this._angleMode === constants.RADIANS) { + return Math.cos(angle); + } else { + return Math.cos(this.radians(angle)); + } +}; + +/** + * Calculates the sine of an angle. This function takes into account the + * current angleMode. Values are returned in the range -1 to 1. + * + * @method sin + * @param {Number} angle the angle + * @return {Number} the sine of the angle + * + * @example + * <div> + * <code> + * var a = 0.0; + * var inc = TWO_PI/25.0; + * for (var i = 0; i < 25; i++) { + * line(i*4, 50, i*4, 50+sin(a)*40.0); + * a = a + inc; + * } + * </code> + * </div> + */ +p5.prototype.sin = function(angle) { + if (this._angleMode === constants.RADIANS) { + return Math.sin(angle); + } else { + return Math.sin(this.radians(angle)); + } +}; + +/** + * Calculates the tangent of an angle. This function takes into account + * the current angleMode. Values are returned in the range -1 to 1. + * + * @method tan + * @param {Number} angle the angle + * @return {Number} the tangent of the angle + * + * @example + * <div> + * <code> + * var a = 0.0; + * var inc = TWO_PI/50.0; + * for (var i = 0; i < 100; i = i+2) { + * line(i, 50, i, 50+tan(a)*2.0); + * a = a + inc; + * } + * </code> + * </div> + * + */ +p5.prototype.tan = function(angle) { + if (this._angleMode === constants.RADIANS) { + return Math.tan(angle); + } else { + return Math.tan(this.radians(angle)); + } +}; + +/** + * Converts a radian measurement to its corresponding value in degrees. + * Radians and degrees are two ways of measuring the same thing. There are + * 360 degrees in a circle and 2*PI radians in a circle. For example, + * 90° = PI/2 = 1.5707964. + * + * @method degrees + * @param {Number} radians the radians value to convert to degrees + * @return {Number} the converted angle + * + * + * @example + * <div class= “norender"> + * <code> + * var rad = PI/4; + * var deg = degrees(rad); + * println(rad + " radians is " + deg + " degrees"); + * // Prints: 45 degrees is 0.7853981633974483 radians + * </code> + * </div> + * + */ +p5.prototype.degrees = function(angle) { + return polarGeometry.radiansToDegrees(angle); +}; + +/** + * Converts a degree measurement to its corresponding value in radians. + * Radians and degrees are two ways of measuring the same thing. There are + * 360 degrees in a circle and 2*PI radians in a circle. For example, + * 90° = PI/2 = 1.5707964. + * + * @method radians + * @param {Number} degrees the degree value to convert to radians + * @return {Number} the converted angle + * + * @example + * <div class= “norender"> + * <code> + * var deg = 45.0; + * var rad = radians(deg); + * println(deg + " degrees is " + rad + " radians"); + * // Prints: 45 degrees is 0.7853981633974483 radians + * </code> + * </div> + */ +p5.prototype.radians = function(angle) { + return polarGeometry.degreesToRadians(angle); +}; + +/** + * Sets the current mode of p5 to given mode. Default mode is RADIANS. + * + * @method angleMode + * @param {Number/Constant} mode either RADIANS or DEGREES + * + * @example + * <div> + * <code> + * function draw(){ + * background(204); + * angleMode(DEGREES); // Change the mode to DEGREES + * var a = atan2(mouseY-height/2, mouseX-width/2); + * translate(width/2, height/2); + * push(); + * rotate(a); + * rect(-20, -5, 40, 10); // Larger rectangle is rotating in degrees + * pop(); + * angleMode(RADIANS); // Change the mode to RADIANS + * rotate(a); // var a stays the same + * rect(-40, -5, 20, 10); // Smaller rectangle is rotating in radians + * } + * </code> + * </div> + * + */ +p5.prototype.angleMode = function(mode) { + if (mode === constants.DEGREES || mode === constants.RADIANS) { + this._angleMode = mode; + } +}; + +module.exports = p5; + +},{"../core/constants":47,"../core/core":48,"./polargeometry":77}],80:[function(_dereq_,module,exports){ +/** + * @module Typography + * @submodule Attributes + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Sets the current alignment for drawing text. The parameters LEFT, CENTER, + * and RIGHT set the alignment of text in relation to the values for + * the x and y parameters of the text() function. + * + * @method textAlign + * @param {Number/Constant} horizAlign horizontal alignment, either LEFT, + * CENTER, or RIGHT + * @param {Number/Constant} vertAlign vertical alignment, either TOP, + * BOTTOM, CENTER, or BASELINE + * @return {Number} + * @example + * <div> + * <code> + * textSize(16); + * textAlign(RIGHT); + * text("ABCD", 50, 30); + * textAlign(CENTER); + * text("EFGH", 50, 50); + * textAlign(LEFT); + * text("IJKL", 50, 70); + * </code> + * </div> + */ +p5.prototype.textAlign = function(horizAlign, vertAlign) { + return this._renderer.textAlign.apply(this._renderer, arguments); +}; + +/** + * Sets/gets the spacing, in pixels, between lines of text. This + * setting will be used in all subsequent calls to the text() function. + * + * @method textLeading + * @param {Number} leading the size in pixels for spacing between lines + * @return {Object|Number} + * @example + * <div> + * <code> + * // Text to display. The "\n" is a "new line" character + * lines = "L1\nL2\nL3"; + * textSize(12); + * + * textLeading(10); // Set leading to 10 + * text(lines, 10, 25); + * + * textLeading(20); // Set leading to 20 + * text(lines, 40, 25); + * + * textLeading(30); // Set leading to 30 + * text(lines, 70, 25); + * </code> + * </div> + */ +p5.prototype.textLeading = function(theLeading) { + return this._renderer.textLeading.apply(this._renderer, arguments); +}; + +/** + * Sets/gets the current font size. This size will be used in all subsequent + * calls to the text() function. Font size is measured in pixels. + * + * @method textSize + * @param {Number} theSize the size of the letters in units of pixels + * @return {Object|Number} + * @example + * <div> + * <code> + * textSize(12); + * text("Font Size 12", 10, 30); + * textSize(14); + * text("Font Size 14", 10, 60); + * textSize(16); + * text("Font Size 16", 10, 90); + * </code> + * </div> + */ +p5.prototype.textSize = function(theSize) { + return this._renderer.textSize.apply(this._renderer, arguments); +}; + +/** + * Sets/gets the style of the text for system fonts to NORMAL, ITALIC, or BOLD. + * Note: this may be is overridden by CSS styling. For non-system fonts + * (opentype, truetype, etc.) please load styled fonts instead. + * + * @method textStyle + * @param {Number/Constant} theStyle styling for text, either NORMAL, + * ITALIC, or BOLD + * @return {Object|String} + * @example + * <div> + * <code> + * strokeWeight(0); + * textSize(12); + * textStyle(NORMAL); + * text("Font Style Normal", 10, 30); + * textStyle(ITALIC); + * text("Font Style Italic", 10, 60); + * textStyle(BOLD); + * text("Font Style Bold", 10, 90); + * </code> + * </div> + */ +p5.prototype.textStyle = function(theStyle) { + return this._renderer.textStyle.apply(this._renderer, arguments); +}; + +/** + * Calculates and returns the width of any character or text string. + * + * @method textWidth + * @param {String} theText the String of characters to measure + * @return {Number} + * @example + * <div> + * <code> + * textSize(28); + * + * var aChar = 'P'; + * var cWidth = textWidth(aChar); + * text(aChar, 0, 40); + * line(cWidth, 0, cWidth, 50); + * + * var aString = "p5.js"; + * var sWidth = textWidth(aString); + * text(aString, 0, 85); + * line(sWidth, 50, sWidth, 100); + * </code> + * </div> + */ +p5.prototype.textWidth = function(theText) { + return this._renderer.textWidth.apply(this._renderer, arguments); +}; + +/** + * Returns the ascent of the current font at its current size. The ascent + * represents the distance, in pixels, of the tallest character above + * the baseline. + * + * @return {Number} + * @example + * <div> + * <code> + * var base = height * 0.75; + * var scalar = 0.8; // Different for each font + * + * textSize(32); // Set initial text size + * var asc = textAscent() * scalar; // Calc ascent + * line(0, base - asc, width, base - asc); + * text("dp", 0, base); // Draw text on baseline + * + * textSize(64); // Increase text size + * asc = textAscent() * scalar; // Recalc ascent + * line(40, base - asc, width, base - asc); + * text("dp", 40, base); // Draw text on baseline + * </code> + * </div> + */ +p5.prototype.textAscent = function() { + return this._renderer.textAscent(); +}; + +/** + * Returns the descent of the current font at its current size. The descent + * represents the distance, in pixels, of the character with the longest + * descender below the baseline. + * + * @return {Number} + * @example + * <div> + * <code> + * var base = height * 0.75; + * var scalar = 0.8; // Different for each font + * + * textSize(32); // Set initial text size + * var desc = textDescent() * scalar; // Calc ascent + * line(0, base+desc, width, base+desc); + * text("dp", 0, base); // Draw text on baseline + * + * textSize(64); // Increase text size + * desc = textDescent() * scalar; // Recalc ascent + * line(40, base + desc, width, base + desc); + * text("dp", 40, base); // Draw text on baseline + * </code> + * </div> + */ +p5.prototype.textDescent = function() { + return this._renderer.textDescent(); +}; + +/** + * Helper function to measure ascent and descent. + */ +p5.prototype._updateTextMetrics = function() { + return this._renderer._updateTextMetrics(); +}; + +module.exports = p5; + +},{"../core/core":48}],81:[function(_dereq_,module,exports){ +/** + * @module Typography + * @submodule Loading & Displaying + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); + +_dereq_('../core/error_helpers'); + + +/** + * Draws text to the screen. Displays the information specified in the first + * parameter on the screen in the position specified by the additional + * parameters. A default font will be used unless a font is set with the + * textFont() function and a default size will be used unless a font is set + * with textSize(). Change the color of the text with the fill() function. + * Change the outline of the text with the stroke() and strokeWeight() + * functions. + * <br><br> + * The text displays in relation to the textAlign() function, which gives the + * option to draw to the left, right, and center of the coordinates. + * <br><br> + * The x2 and y2 parameters define a rectangular area to display within and + * may only be used with string data. When these parameters are specified, + * they are interpreted based on the current rectMode() setting. Text that + * does not fit completely within the rectangle specified will not be drawn + * to the screen. + * + * @method text + * @param {String} str the alphanumeric symbols to be displayed + * @param {Number} x x-coordinate of text + * @param {Number} y y-coordinate of text + * @param {Number} x2 by default, the width of the text box, + * see rectMode() for more info + * @param {Number} y2 by default, the height of the text box, + * see rectMode() for more info + * @return {Object} this + * @example + * <div> + * <code> + * textSize(32); + * text("word", 10, 30); + * fill(0, 102, 153); + * text("word", 10, 60); + * fill(0, 102, 153, 51); + * text("word", 10, 90); + * </code> + * </div> + * <div> + * <code> + * s = "The quick brown fox jumped over the lazy dog."; + * fill(50); + * text(s, 10, 10, 70, 80); // Text wraps within text box + * </code> + * </div> + */ +p5.prototype.text = function(str, x, y, maxWidth, maxHeight) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'text', + args, + [ + ['*', 'Number', 'Number'], + ['*', 'Number', 'Number', 'Number', 'Number'] + ] + ); + + return (!(this._renderer._doFill || this._renderer._doStroke)) ? this : + this._renderer.text.apply(this._renderer, arguments); +}; + +/** + * Sets the current font that will be drawn with the text() function. + * + * @method textFont + * @param {Object|String} f a font loaded via loadFont(), or a String + * representing a browser-based dfault font. + * @return {Object} this + * @example + * <div> + * <code> + * fill(0); + * textSize(12); + * textFont("Georgia"); + * text("Georgia", 12, 30); + * textFont("Helvetica"); + * text("Helvetica", 12, 60); + * </code> + * </div> + * <div> + * <code> + * var fontRegular, fontItalic, fontBold; + * function preload() { + * fontRegular = loadFont("assets/Regular.otf"); + * fontItalic = loadFont("assets/Italic.ttf"); + * fontBold = loadFont("assets/Bold.ttf"); + * } + * function setup() { + * background(210); + * fill(0).strokeWeight(0).textSize(10); + * textFont(fontRegular); + * text("Font Style Normal", 10, 30); + * textFont(fontItalic); + * text("Font Style Italic", 10, 50); + * textFont(fontBold); + * text("Font Style Bold", 10, 70); + * } + * </code> + * </div> + */ +p5.prototype.textFont = function(theFont, theSize) { + + if (arguments.length) { + + if (!theFont) { + + throw Error('null font passed to textFont'); + } + + this._renderer._setProperty('_textFont', theFont); + + if (theSize) { + + this._renderer._setProperty('_textSize', theSize); + this._renderer._setProperty('_textLeading', + theSize * constants._DEFAULT_LEADMULT); + } + + return this._renderer._applyTextProperties(); + } + + return this; +}; + +module.exports = p5; + +},{"../core/constants":47,"../core/core":48,"../core/error_helpers":51}],82:[function(_dereq_,module,exports){ +/** + * This module defines the p5.Font class and functions for + * drawing text to the display canvas. + * @module Typography + * @submodule Font + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); + +/* + * TODO: + * + * API: + * -- textBounds() + * -- getPath() + * -- getPoints() + * + * =========================================== + * -- PFont functions: + * PFont.list() + * + * -- kerning + * -- alignment: justified? + * -- integrate p5.dom? (later) + */ + +/** + * Base class for font handling + * @class p5.Font + * @constructor + * @param {Object} [pInst] pointer to p5 instance + */ +p5.Font = function(p) { + + this.parent = p; + + this.cache = {}; + + /** + * Underlying opentype font implementation + * @property font + */ + this.font = undefined; +}; + +p5.Font.prototype.list = function() { + + // TODO + throw 'not yet implemented'; +}; + +/** + * Returns a tight bounding box for the given text string using this + * font (currently only supports single lines) + * + * @method textBounds + * @param {String} line a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Number} fontSize font size to use (optional) + * @param {Object} options opentype options (optional) + * + * @return {Object} a rectangle object with properties: x, y, w, h + * + * @example + * <div> + * <code> + * var font; + * var textString = 'Lorem ipsum dolor sit amet.'; + * function preload() { + * font = loadFont('./assets/Regular.otf'); + * }; + * function setup() { + * background(210); + * + * var bbox = font.textBounds(textString, 10, 30, 12); + * fill(255); + * stroke(0); + * rect(bbox.x, bbox.y, bbox.w, bbox.h); + * fill(0); + * noStroke(); + * + * textFont(font); + * textSize(12); + * text(textString, 10, 30); + * }; + * </code> + * </div> + */ +p5.Font.prototype.textBounds = function(str, x, y, fontSize, options) { + + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize || this.parent._renderer._textSize; + + var result = this.cache[cacheKey('textBounds', str, x, y, fontSize)]; + if (!result) { + + var xCoords = [], yCoords = [], self = this, + scale = this._scale(fontSize), minX, minY, maxX, maxY; + + this.font.forEachGlyph(str, x, y, fontSize, options, + function(glyph, gX, gY, gFontSize) { + + xCoords.push(gX); + yCoords.push(gY); + + var gm = glyph.getMetrics(); + + if (glyph.name !== 'space') { + + xCoords.push(gX + (gm.xMax * scale)); + yCoords.push(gY + (-gm.yMin * scale)); + yCoords.push(gY + (-gm.yMax * scale)); + + } else { // NOTE: deals with broken metrics for spaces in opentype.js + + xCoords.push(gX + self.font.charToGlyph(' ').advanceWidth * + self._scale(fontSize)); + } + }); + + minX = Math.max(0, Math.min.apply(null, xCoords)); + minY = Math.max(0, Math.min.apply(null, yCoords)); + maxX = Math.max(0, Math.max.apply(null, xCoords)); + maxY = Math.max(0, Math.max.apply(null, yCoords)); + + result = { + x: minX, + y: minY, + h: maxY - minY, + w: maxX - minX, + advance: minX - x + }; + + this.cache[cacheKey('textBounds', str, x, y, fontSize)] = result; + } + //else console.log('cache-hit'); + + return result; +}; + + +/** + * Computes an array of points following the path for specified text + * + * @param {String} txt a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Number} fontSize font size to use (optional) + * @param {Object} options an (optional) object that can contain: + * + * <br>sampleFactor - the ratio of path-length to number of samples + * (default=.25); higher values yield more points and are therefore + * more precise + * + * <br>simplifyThreshold - if set to a non-zero value, collinear points will be + * be removed from the polygon; the value represents the threshold angle to use + * when determining whether two edges are collinear + * + * @return {Array} an array of points, each with x, y, alpha (the path angle) + */ +p5.Font.prototype.textToPoints = function(txt, x, y, fontSize, options) { + + var xoff = 0, result = [], glyphs = this._getGlyphs(txt); + + fontSize = fontSize || this.parent._renderer._textSize; + + for (var i = 0; i < glyphs.length; i++) { + + var gpath = glyphs[i].getPath(x, y, fontSize), + paths = splitPaths(gpath.commands); + + for (var j = 0; j < paths.length; j++) { + + var pts = pathToPoints(paths[j], options); + + for (var k = 0; k < pts.length; k++) { + pts[k].x += xoff; + result.push(pts[k]); + } + } + + xoff += glyphs[i].advanceWidth * this._scale(fontSize); + } + + return result; +}; + +// ----------------------------- End API ------------------------------ + +/** + * Returns the set of opentype glyphs for the supplied string. + * + * Note that there is not a strict one-to-one mapping between characters + * and glyphs, so the list of returned glyphs can be larger or smaller + * than the length of the given string. + * + * @param {String} str the string to be converted + * @return {array} the opentype glyphs + */ +p5.Font.prototype._getGlyphs = function(str) { + + return this.font.stringToGlyphs(str); +}; + +/** + * Returns an opentype path for the supplied string and position. + * + * @param {String} line a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Object} options opentype options (optional) + * @return {Object} the opentype path + */ +p5.Font.prototype._getPath = function(line, x, y, options) { + + var p = this.parent, + ctx = p._renderer.drawingContext, + pos = this._handleAlignment(p, ctx, line, x, y); + + return this.font.getPath(line, pos.x, pos.y, p._renderer._textSize, options); +}; + +/* + * Creates an SVG-formatted path-data string + * (See http://www.w3.org/TR/SVG/paths.html#PathData) + * from the given opentype path or string/position + * + * @param {Object} path an opentype path, OR the following: + * + * @param {String} line a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Object} options opentype options (optional), set options.decimals + * to set the decimal precision of the path-data + * + * @return {Object} this p5.Font object + */ +p5.Font.prototype._getPathData = function(line, x, y, options) { + + var decimals = 3; + + // create path from string/position + if (typeof line === 'string' && arguments.length > 2) { + + line = this._getPath(line, x, y, options); + } + // handle options specified in 2nd arg + else if (typeof x === 'object') { + + options = x; + } + + // handle svg arguments + if (options && typeof options.decimals === 'number') { + + decimals = options.decimals; + } + + return line.toPathData(decimals); +}; + +/* + * Creates an SVG <path> element, as a string, + * from the given opentype path or string/position + * + * @param {Object} path an opentype path, OR the following: + * + * @param {String} line a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Object} options opentype options (optional), set options.decimals + * to set the decimal precision of the path-data in the <path> element, + * options.fill to set the fill color for the <path> element, + * options.stroke to set the stroke color for the <path> element, + * options.strokeWidth to set the strokeWidth for the <path> element. + * + * @return {Object} this p5.Font object + */ +p5.Font.prototype._getSVG = function(line, x, y, options) { + + var decimals = 3; + + // create path from string/position + if (typeof line === 'string' && arguments.length > 2) { + + line = this._getPath(line, x, y, options); + } + // handle options specified in 2nd arg + else if (typeof x === 'object') { + + options = x; + } + + // handle svg arguments + if (options) { + if (typeof options.decimals === 'number') { + decimals = options.decimals; + } + if (typeof options.strokeWidth === 'number') { + line.strokeWidth = options.strokeWidth; + } + if (typeof options.fill !== 'undefined') { + line.fill = options.fill; + } + if (typeof options.stroke !== 'undefined') { + line.stroke = options.stroke; + } + } + + return line.toSVG(decimals); +}; + +/* + * Renders an opentype path or string/position + * to the current graphics context + * + * @param {Object} path an opentype path, OR the following: + * + * @param {String} line a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Object} options opentype options (optional) + * + * @return {Object} this p5.Font object + */ +p5.Font.prototype._renderPath = function(line, x, y, options) { + + var pdata, pg = (options && options.renderer) || this.parent._renderer, + ctx = pg.drawingContext; + + if (typeof line === 'object' && line.commands) { + + pdata = line.commands; + } else { + + //pos = handleAlignment(p, ctx, line, x, y); + pdata = this._getPath(line, x, y, pg._textSize, options).commands; + } + + ctx.beginPath(); + for (var i = 0; i < pdata.length; i += 1) { + + var cmd = pdata[i]; + if (cmd.type === 'M') { + ctx.moveTo(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + ctx.lineTo(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + ctx.closePath(); + } + } + + // only draw stroke if manually set by user + if (pg._doStroke && pg._strokeSet) { + + ctx.stroke(); + } + + if (pg._doFill) { + + // if fill hasn't been set by user, use default-text-fill + ctx.fillStyle = pg._fillSet ? ctx.fillStyle : constants._DEFAULT_TEXT_FILL; + ctx.fill(); + } + + return this; +}; + +p5.Font.prototype._textWidth = function(str, fontSize) { + + if (str === ' ') { // special case for now + + return this.font.charToGlyph(' ').advanceWidth * this._scale(fontSize); + } + + var bounds = this.textBounds(str, 0, 0, fontSize); + return bounds.w + bounds.advance; +}; + +p5.Font.prototype._textAscent = function(fontSize) { + + return this.font.ascender * this._scale(fontSize); +}; + +p5.Font.prototype._textDescent = function(fontSize) { + + return -this.font.descender * this._scale(fontSize); +}; + +p5.Font.prototype._scale = function(fontSize) { + + return (1 / this.font.unitsPerEm) * (fontSize || + this.parent._renderer._textSize); +}; + +p5.Font.prototype._handleAlignment = function(p, ctx, line, x, y) { + + var textWidth = this._textWidth(line), + textAscent = this._textAscent(), + textDescent = this._textDescent(), + textHeight = textAscent + textDescent; + + if (ctx.textAlign === constants.CENTER) { + x -= textWidth / 2; + } else if (ctx.textAlign === constants.RIGHT) { + x -= textWidth; + } + + if (ctx.textBaseline === constants.TOP) { + y += textHeight; + } else if (ctx.textBaseline === constants._CTX_MIDDLE) { + y += textHeight / 2 - textDescent; + } else if (ctx.textBaseline === constants.BOTTOM) { + y -= textDescent; + } + + return { x: x, y: y }; +}; + +// path-utils + +function pathToPoints(cmds, options) { + + var opts = parseOpts(options, { + sampleFactor: 0.1, + simplifyThreshold: 0, + }); + + var len = pointAtLength(cmds,0,1), // total-length + t = len / (len * opts.sampleFactor), + pts = []; + + for (var i = 0; i < len; i += t) { + pts.push(pointAtLength(cmds, i)); + } + + if (opts.simplifyThreshold) { + /*var count = */simplify(pts, opts.simplifyThreshold); + //console.log('Simplify: removed ' + count + ' pts'); + } + + return pts; +} + +function simplify(pts, angle) { + + angle = (typeof angle === 'undefined') ? 0 : angle; + + var num = 0; + for (var i = pts.length - 1; pts.length > 3 && i >= 0; --i) { + + if (collinear(at(pts, i - 1), at(pts, i), at(pts, i + 1), angle)) { + + // Remove the middle point + pts.splice(i % pts.length, 1); + num++; + } + } + return num; +} + +function splitPaths(cmds) { + + var paths = [], current; + for (var i = 0; i < cmds.length; i++) { + if (cmds[i].type === 'M') { + if (current) { + paths.push(current); + } + current = []; + } + current.push(cmdToArr(cmds[i])); + } + paths.push(current); + + return paths; +} + +function cmdToArr(cmd) { + + var arr = [ cmd.type ]; + if (cmd.type === 'M' || cmd.type === 'L') { // moveto or lineto + arr.push(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + arr.push(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + arr.push(cmd.x1, cmd.y1, cmd.x, cmd.y); + } + // else if (cmd.type === 'Z') { /* no-op */ } + return arr; +} + +function parseOpts(options, defaults) { + + if (typeof options !== 'object') { + options = defaults; + } + else { + for (var key in defaults) { + if (typeof options[key] === 'undefined') { + options[key] = defaults[key]; + } + } + } + return options; +} + +//////////////////////// Helpers //////////////////////////// + +function at(v, i) { + var s = v.length; + return v[i < 0 ? i % s + s : i % s]; +} + +function collinear(a, b, c, thresholdAngle) { + + if (!thresholdAngle) { + return areaTriangle(a, b, c) === 0; + } + + if (typeof collinear.tmpPoint1 === 'undefined') { + collinear.tmpPoint1 = []; + collinear.tmpPoint2 = []; + } + + var ab = collinear.tmpPoint1, bc = collinear.tmpPoint2; + ab.x = b.x - a.x; + ab.y = b.y - a.y; + bc.x = c.x - b.x; + bc.y = c.y - b.y; + + var dot = ab.x * bc.x + ab.y * bc.y, + magA = Math.sqrt(ab.x * ab.x + ab.y * ab.y), + magB = Math.sqrt(bc.x * bc.x + bc.y * bc.y), + angle = Math.acos(dot / (magA * magB)); + + return angle < thresholdAngle; +} + +function areaTriangle(a, b, c) { + return (((b[0] - a[0]) * (c[1] - a[1])) - ((c[0] - a[0]) * (b[1] - a[1]))); +} + +// Portions of below code copyright 2008 Dmitry Baranovskiy (via MIT license) + +function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { + + var t1 = 1 - t, t13 = Math.pow(t1, 3), t12 = Math.pow(t1, 2), t2 = t * t, + t3 = t2 * t, x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + + t3 * p2x, y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + + t3 * p2y, mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x), + my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y), + nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x), + ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y), + ax = t1 * p1x + t * c1x, ay = t1 * p1y + t * c1y, + cx = t1 * c2x + t * p2x, cy = t1 * c2y + t * p2y, + alpha = (90 - Math.atan2(mx - nx, my - ny) * 180 / Math.PI); + + if (mx > nx || my < ny) { alpha += 180; } + + return { x: x, y: y, m: { x: mx, y: my }, n: { x: nx, y: ny }, + start: { x: ax, y: ay }, end: { x: cx, y: cy }, alpha: alpha + }; +} + +function getPointAtSegmentLength(p1x,p1y,c1x,c1y,c2x,c2y,p2x,p2y,length) { + return (length == null) ? bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) : + findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, + getTatLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length)); +} + +function pointAtLength(path, length, istotal) { + path = path2curve(path); + var x, y, p, l, sp = '', subpaths = {}, point, len = 0; + for (var i = 0, ii = path.length; i < ii; i++) { + p = path[i]; + if (p[0] === 'M') { + x = +p[1]; + y = +p[2]; + } else { + l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]); + if (len + l > length) { + if (!istotal) { + point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], + p[6], length - len); + return { x: point.x, y: point.y, alpha: point.alpha }; + } + } + len += l; + x = +p[5]; + y = +p[6]; + } + sp += p.shift() + p; + } + subpaths.end = sp; + + point = istotal ? len : findDotsAtSegment + (x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1); + + if (point.alpha) { + point = { x: point.x, y: point.y, alpha: point.alpha }; + } + + return point; +} + +function pathToAbsolute(pathArray) { + + var res = [], x = 0, y = 0, mx = 0, my = 0, start = 0; + if (pathArray[0][0] === 'M') { + x = +pathArray[0][1]; + y = +pathArray[0][2]; + mx = x; + my = y; + start++; + res[0] = ['M', x, y]; + } + + var dots,crz = pathArray.length===3 && pathArray[0][0]==='M' && + pathArray[1][0].toUpperCase()==='R' && pathArray[2][0].toUpperCase()==='Z'; + + for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) { + res.push(r = []); + pa = pathArray[i]; + if (pa[0] !== String.prototype.toUpperCase.call(pa[0])) { + r[0] = String.prototype.toUpperCase.call(pa[0]); + switch (r[0]) { + case 'A': + r[1] = pa[1]; + r[2] = pa[2]; + r[3] = pa[3]; + r[4] = pa[4]; + r[5] = pa[5]; + r[6] = +(pa[6] + x); + r[7] = +(pa[7] + y); + break; + case 'V': + r[1] = +pa[1] + y; + break; + case 'H': + r[1] = +pa[1] + x; + break; + case 'R': + dots = [x, y].concat(pa.slice(1)); + for (var j = 2, jj = dots.length; j < jj; j++) { + dots[j] = +dots[j] + x; + dots[++j] = +dots[j] + y; + } + res.pop(); + res = res.concat(catmullRom2bezier(dots, crz)); + break; + case 'M': + mx = +pa[1] + x; + my = +pa[2] + y; + break; + default: + for (j = 1, jj = pa.length; j < jj; j++) { + r[j] = +pa[j] + ((j % 2) ? x : y); + } + } + } else if (pa[0] === 'R') { + dots = [x, y].concat(pa.slice(1)); + res.pop(); + res = res.concat(catmullRom2bezier(dots, crz)); + r = ['R'].concat(pa.slice(-2)); + } else { + for (var k = 0, kk = pa.length; k < kk; k++) { + r[k] = pa[k]; + } + } + switch (r[0]) { + case 'Z': + x = mx; + y = my; + break; + case 'H': + x = r[1]; + break; + case 'V': + y = r[1]; + break; + case 'M': + mx = r[r.length - 2]; + my = r[r.length - 1]; + break; + default: + x = r[r.length - 2]; + y = r[r.length - 1]; + } + } + return res; +} + +function path2curve(path, path2) { + + var p = pathToAbsolute(path), p2 = path2 && pathToAbsolute(path2), + attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, + attrs2 = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, + + processPath = function(path, d, pcom) { + var nx, ny, tq = { T: 1, Q: 1 }; + if (!path) { return ['C', d.x, d.y, d.x, d.y, d.x, d.y]; } + if (!(path[0] in tq)) { d.qx = d.qy = null; } + switch (path[0]) { + case 'M': + d.X = path[1]; + d.Y = path[2]; + break; + case 'A': + path = ['C'].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1)))); + break; + case 'S': + if (pcom === 'C' || pcom === 'S') { + nx = d.x * 2 - d.bx; + ny = d.y * 2 - d.by; + } else { + nx = d.x; + ny = d.y; + } + path = ['C', nx, ny].concat(path.slice(1)); + break; + case 'T': + if (pcom === 'Q' || pcom === 'T') { + d.qx = d.x * 2 - d.qx; + d.qy = d.y * 2 - d.qy; + } else { + d.qx = d.x; + d.qy = d.y; + } + path = ['C'].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2])); + break; + case 'Q': + d.qx = path[1]; + d.qy = path[2]; + path = ['C'].concat(q2c(d.x,d.y,path[1],path[2],path[3],path[4])); + break; + case 'L': + path = ['C'].concat(l2c(d.x, d.y, path[1], path[2])); + break; + case 'H': + path = ['C'].concat(l2c(d.x, d.y, path[1], d.y)); + break; + case 'V': + path = ['C'].concat(l2c(d.x, d.y, d.x, path[1])); + break; + case 'Z': + path = ['C'].concat(l2c(d.x, d.y, d.X, d.Y)); + break; + } + return path; + }, + + fixArc = function(pp, i) { + if (pp[i].length > 7) { + pp[i].shift(); + var pi = pp[i]; + while (pi.length) { + pcoms1[i] = 'A'; + if (p2) { pcoms2[i] = 'A'; } + pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6))); + } + pp.splice(i, 1); + ii = Math.max(p.length, p2 && p2.length || 0); + } + }, + + fixM = function(path1, path2, a1, a2, i) { + if (path1 && path2 && path1[i][0] === 'M' && path2[i][0] !== 'M') { + path2.splice(i, 0, ['M', a2.x, a2.y]); + a1.bx = 0; + a1.by = 0; + a1.x = path1[i][1]; + a1.y = path1[i][2]; + ii = Math.max(p.length, p2 && p2.length || 0); + } + }, + + pcoms1 = [], // path commands of original path p + pcoms2 = [], // path commands of original path p2 + pfirst = '', // temporary holder for original path command + pcom = ''; // holder for previous path command of original path + + for (var i = 0, ii = Math.max(p.length, p2 && p2.length || 0); i < ii; i++) { + if (p[i]) { pfirst = p[i][0]; } // save current path command + + if (pfirst !== 'C') { + pcoms1[i] = pfirst; // Save current path command + if (i) { pcom = pcoms1[i - 1]; } // Get previous path command pcom + } + p[i] = processPath(p[i], attrs, pcom); + + if (pcoms1[i] !== 'A' && pfirst === 'C') { pcoms1[i] = 'C'; } + + fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1 + + if (p2) { // the same procedures is done to p2 + if (p2[i]) { pfirst = p2[i][0]; } + if (pfirst !== 'C') { + pcoms2[i] = pfirst; + if (i) { pcom = pcoms2[i - 1]; } + } + p2[i] = processPath(p2[i], attrs2, pcom); + + if (pcoms2[i] !== 'A' && pfirst === 'C') { pcoms2[i] = 'C'; } + + fixArc(p2, i); + } + fixM(p, p2, attrs, attrs2, i); + fixM(p2, p, attrs2, attrs, i); + var seg = p[i], seg2 = p2 && p2[i], seglen = seg.length, + seg2len = p2 && seg2.length; + attrs.x = seg[seglen - 2]; + attrs.y = seg[seglen - 1]; + attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x; + attrs.by = parseFloat(seg[seglen - 3]) || attrs.y; + attrs2.bx = p2 && (parseFloat(seg2[seg2len - 4]) || attrs2.x); + attrs2.by = p2 && (parseFloat(seg2[seg2len - 3]) || attrs2.y); + attrs2.x = p2 && seg2[seg2len - 2]; + attrs2.y = p2 && seg2[seg2len - 1]; + } + + return p2 ? [p, p2] : p; +} + +function a2c(x1, y1, rx, ry, angle, lac, sweep_flag, x2, y2, recursive) { + // for more information of where this Math came from visit: + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + var PI = Math.PI, _120 = PI * 120 / 180, f1, f2, cx, cy, + rad = PI / 180 * (+angle || 0), res = [], xy, + rotate = function (x, y, rad) { + var X = x * Math.cos(rad) - y * Math.sin(rad), + Y = x * Math.sin(rad) + y * Math.cos(rad); + return { x: X, y: Y }; + }; + if (!recursive) { + xy = rotate(x1, y1, -rad); + x1 = xy.x; + y1 = xy.y; + xy = rotate(x2, y2, -rad); + x2 = xy.x; + y2 = xy.y; + var x = (x1 - x2) / 2, y = (y1 - y2) / 2, + h = (x * x) / (rx * rx) + (y * y) / (ry * ry); + if (h > 1) { + h = Math.sqrt(h); + rx = h * rx; + ry = h * ry; + } + var rx2 = rx * rx, ry2 = ry * ry, + k = (lac === sweep_flag ? -1 : 1) * Math.sqrt(Math.abs + ((rx2 * ry2 - rx2 * y * y - ry2 * x * x)/(rx2 * y * y + ry2 * x * x))); + + cx = k * rx * y / ry + (x1 + x2) / 2; + cy = k * -ry * x / rx + (y1 + y2) / 2; + f1 = Math.asin(((y1 - cy) / ry).toFixed(9)); + f2 = Math.asin(((y2 - cy) / ry).toFixed(9)); + + f1 = x1 < cx ? PI - f1 : f1; + f2 = x2 < cx ? PI - f2 : f2; + + if (f1 < 0) { f1 = PI * 2 + f1; } + if (f2 < 0) { f2 = PI * 2 + f2; } + + if (sweep_flag && f1 > f2) { + f1 = f1 - PI * 2; + } + if (!sweep_flag && f2 > f1) { + f2 = f2 - PI * 2; + } + } else { + f1 = recursive[0]; + f2 = recursive[1]; + cx = recursive[2]; + cy = recursive[3]; + } + var df = f2 - f1; + if (Math.abs(df) > _120) { + var f2old = f2, x2old = x2, y2old = y2; + f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1); + x2 = cx + rx * Math.cos(f2); + y2 = cy + ry * Math.sin(f2); + res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, + [f2, f2old, cx, cy]); + } + df = f2 - f1; + var c1 = Math.cos(f1), + s1 = Math.sin(f1), + c2 = Math.cos(f2), + s2 = Math.sin(f2), + t = Math.tan(df / 4), + hx = 4 / 3 * rx * t, + hy = 4 / 3 * ry * t, + m1 = [x1, y1], + m2 = [x1 + hx * s1, y1 - hy * c1], + m3 = [x2 + hx * s2, y2 - hy * c2], + m4 = [x2, y2]; + m2[0] = 2 * m1[0] - m2[0]; + m2[1] = 2 * m1[1] - m2[1]; + if (recursive) { + return [m2, m3, m4].concat(res); + } else { + res = [m2, m3, m4].concat(res).join().split(','); + var newres = []; + for (var i = 0, ii = res.length; i < ii; i++) { + newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], + res[i + 1], rad).x; + } + return newres; + } +} + +// http://schepers.cc/getting-to-the-point +function catmullRom2bezier(crp, z) { + var d = []; + for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) { + var p = [{ + x: +crp[i - 2], + y: +crp[i - 1] + }, { + x: +crp[i], + y: +crp[i + 1] + }, { + x: +crp[i + 2], + y: +crp[i + 3] + }, { + x: +crp[i + 4], + y: +crp[i + 5] + }]; + if (z) { + if (!i) { + p[0] = { + x: +crp[iLen - 2], + y: +crp[iLen - 1] + }; + } else if (iLen - 4 === i) { + p[3] = { + x: +crp[0], + y: +crp[1] + }; + } else if (iLen - 2 === i) { + p[2] = { + x: +crp[0], + y: +crp[1] + }; + p[3] = { + x: +crp[2], + y: +crp[3] + }; + } + } else { + if (iLen - 4 === i) { + p[3] = p[2]; + } else if (!i) { + p[0] = { + x: +crp[i], + y: +crp[i + 1] + }; + } + } + d.push(['C', (-p[0].x + 6 * p[1].x + p[2].x) / 6, (-p[0].y + 6 * p[1].y + + p[2].y) / 6, (p[1].x + 6 * p[2].x - p[3].x) / 6, (p[1].y + 6 * p[2].y - + p[3].y) / 6, p[2].x, p[2].y ]); + } + + return d; +} + +function l2c(x1, y1, x2, y2) { return [x1, y1, x2, y2, x2, y2]; } + +function q2c(x1, y1, ax, ay, x2, y2) { + var _13 = 1 / 3, _23 = 2 / 3; + return [ + _13 * x1 + _23 * ax, _13 * y1 + _23 * ay, + _13 * x2 + _23 * ax, _13 * y2 + _23 * ay, x2, y2 + ]; +} + +function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) { + if (z == null) { z = 1; } + z = z > 1 ? 1 : z < 0 ? 0 : z; + var z2 = z / 2, + n = 12, Tvalues = [-0.1252, 0.1252, -0.3678, 0.3678, -0.5873, 0.5873, + -0.7699, 0.7699, -0.9041, 0.9041, -0.9816, 0.9816], + sum = 0, Cvalues = [0.2491, 0.2491, 0.2335, 0.2335, 0.2032, 0.2032, + 0.1601, 0.1601, 0.1069, 0.1069, 0.0472, 0.0472 ]; + for (var i = 0; i < n; i++) { + var ct = z2 * Tvalues[i] + z2, + xbase = base3(ct, x1, x2, x3, x4), + ybase = base3(ct, y1, y2, y3, y4), + comb = xbase * xbase + ybase * ybase; + sum += Cvalues[i] * Math.sqrt(comb); + } + return z2 * sum; +} + +function getTatLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) { + if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) { + return; + } + var t = 1, step = t / 2, t2 = t - step, l, e = 0.01; + l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2); + while (Math.abs(l - ll) > e) { + step /= 2; + t2 += (l < ll ? 1 : -1) * step; + l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2); + } + return t2; +} + +function base3(t, p1, p2, p3, p4) { + var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4, + t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3; + return t * t2 - 3 * p1 + 3 * p2; +} + +function cacheKey() { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + i = args.length; + var hash = ''; + while (i--) { + hash += (args[i] === Object(args[i])) ? + JSON.stringify(args[i]) : args[i]; + } + return hash; +} + +module.exports = p5.Font; + +},{"../core/constants":47,"../core/core":48}],83:[function(_dereq_,module,exports){ +/** + * @module Data + * @submodule Array Functions + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Adds a value to the end of an array. Extends the length of + * the array by one. Maps to Array.push(). + * + * @method append + * @param {Array} array Array to append + * @param {any} value to be added to the Array + * @example + * <div class = "norender"><code> + * function setup() { + * + * var myArray = new Array("Mango", "Apple", "Papaya") + * print(myArray) // ["Mango", "Apple", "Papaya"] + * + * append(myArray, "Peach") + * print(myArray) // ["Mango", "Apple", "Papaya", "Peach"] + * + * } + * </div></code> + */ +p5.prototype.append = function(array, value) { + array.push(value); + return array; +}; + +/** + * 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 + * srcPosition and into the position specified by dstPosition. The number of + * elements to copy is determined by length. Note that copying values + * overwrites existing values in the destination array. To append values + * instead of overwriting them, use concat(). + * <br><br> + * The simplified version with only two arguments, arrayCopy(src, dst), + * copies an entire array to another of the same size. It is equivalent to + * arrayCopy(src, 0, dst, 0, src.length). + * <br><br> + * Using this function is far more efficient for copying array data than + * iterating through a for() loop and copying each element individually. + * + * @method arrayCopy + * @param {Array} src the source Array + * @param {Number} [srcPosition] starting position in the source Array + * @param {Array} dst the destination Array + * @param {Number} [dstPosition] starting position in the destination Array + * @param {Number} [length] number of Array elements to be copied + * + * @example + * <div class="norender"><code> + * function setup() { + * + * var src = new Array("A", "B", "C"); + * var dst = new Array( 1 , 2 , 3 ); + * var srcPosition = 1; + * var dstPosition = 0; + * var length = 2; + * + * print(src); // ["A", "B", "C"] + * print(dst); // [ 1 , 2 , 3 ] + * + * arrayCopy(src, srcPosition, dst, dstPosition, length); + * print(dst); // ["B", "C", 3] + * + * } + * </div></code> + */ +p5.prototype.arrayCopy = function( + src, + srcPosition, + dst, + dstPosition, + length) { + + // the index to begin splicing from dst array + var start, + end; + + if (typeof length !== 'undefined') { + + end = Math.min(length, src.length); + start = dstPosition; + src = src.slice(srcPosition, end + srcPosition); + + } else { + + if (typeof dst !== 'undefined') { // src, dst, length + // rename so we don't get confused + end = dst; + end = Math.min(end, src.length); + } else { // src, dst + end = src.length; + } + + start = 0; + // rename so we don't get confused + dst = srcPosition; + src = src.slice(0, end); + } + + // Since we are not returning the array and JavaScript is pass by reference + // we must modify the actual values of the array + // instead of reassigning arrays + Array.prototype.splice.apply(dst, [start, end].concat(src)); + +}; + +/** + * Concatenates two arrays, maps to Array.concat(). Does not modify the + * input arrays. + * + * @method concat + * @param {Array} a first Array to concatenate + * @param {Array} b second Array to concatenate + * @return {Array} concatenated array + * + * @example + * <div class = "norender"><code> + * function setup() { + * var arr1 = new Array("A", "B", "C"); + * var arr2 = new Array( 1 , 2 , 3 ); + * + * print(arr1); // ["A","B","C"] + * print(arr2); // [1,2,3] + * + * var arr3 = concat(arr1, arr2); + * + * print(arr1); // ["A","B","C"] + * print(arr2); // [1,2,3] + * print(arr3); // ["A","B","C",1,2,3] + * + * } + * </div></code> + */ +p5.prototype.concat = function(list0, list1) { + return list0.concat(list1); +}; + +/** + * Reverses the order of an array, maps to Array.reverse() + * + * @method reverse + * @param {Array} list Array to reverse + * @example + * <div class="norender"><code> + * function setup() { + * var myArray = new Array("A", "B", "C"); + * print(myArray); // ["A","B","C"] + * + * reverse(myArray); + * print(myArray); // ["C","B","A"] + * } + * </div></code> + */ +p5.prototype.reverse = function(list) { + return list.reverse(); +}; + +/** + * Decreases an array by one element and returns the shortened array, + * maps to Array.pop(). + * + * @method shorten + * @param {Array} list Array to shorten + * @return {Array} shortened Array + * @example + * <div class = "norender"><code> + * function setup() { + * var myArray = new Array("A", "B", "C"); + * print(myArray); // ["A","B","C"] + * + * var newArray = shorten(myArray); + * print(myArray); // ["A","B","C"] + * print(newArray); // ["A","B"] + * } + * </div></code> + */ +p5.prototype.shorten = function(list) { + list.pop(); + return list; +}; + +/** + * Randomizes the order of the elements of an array. Implements + * <a href="http://Bost.Ocks.org/mike/shuffle/" target=_blank> + * Fisher-Yates Shuffle Algorithm</a>. + * + * @method shuffle + * @param {Array} array Array to shuffle + * @param {Boolean} [bool] modify passed array + * @return {Array} shuffled Array + * @example + * <div><code> + * function setup() { + * var regularArr = ['ABC', 'def', createVector(), TAU, Math.E]; + * print(regularArr); + * shuffle(regularArr, true); // force modifications to passed array + * print(regularArr); + * + * // By default shuffle() returns a shuffled cloned array: + * var newArr = shuffle(regularArr); + * print(regularArr); + * print(newArr); + * } + * </code></div> + */ +p5.prototype.shuffle = function(arr, bool) { + var isView = ArrayBuffer && ArrayBuffer.isView && ArrayBuffer.isView(arr); + arr = bool || isView ? arr : arr.slice(); + + var rnd, tmp, idx = arr.length; + while (idx > 1) { + rnd = Math.random()*idx | 0; + + tmp = arr[--idx]; + arr[idx] = arr[rnd]; + arr[rnd] = tmp; + } + + return arr; +}; + +/** + * Sorts an array of numbers from smallest to largest, or 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 + * count is set to 5, only the first 5 elements in the array will be sorted. + * + * @method sort + * @param {Array} list Array to sort + * @param {Number} [count] number of elements to sort, starting from 0 + * + * @example + * <div class = "norender"><code> + * function setup() { + * var words = new Array("banana", "apple", "pear","lime"); + * print(words); // ["banana", "apple", "pear", "lime"] + * var count = 4; // length of array + * + * sort(words, count); + * print(words); // ["apple", "banana", "lime", "pear"] + * } + * </div></code> + * <div class = "norender"><code> + * function setup() { + * var numbers = new Array(2,6,1,5,14,9,8,12); + * print(numbers); // [2,6,1,5,14,9,8,12] + * var count = 5; // Less than the length of the array + * + * sort(numbers, count); + * print(numbers); // [1,2,5,6,14,9,8,12] + * } + * </div></code> + */ +p5.prototype.sort = function(list, count) { + var arr = count ? list.slice(0, Math.min(count, list.length)) : list; + var rest = count ? list.slice(Math.min(count, list.length)) : []; + if (typeof arr[0] === 'string') { + arr = arr.sort(); + } else { + arr = arr.sort(function(a,b){return a-b;}); + } + return arr.concat(rest); +}; + +/** + * Inserts a value or an array of values into an existing array. The first + * parameter specifies the initial array to be modified, and the second + * parameter defines the data to be inserted. The third parameter is an index + * value which specifies the array position from which to insert data. + * (Remember that array index numbering starts at zero, so the first position + * is 0, the second position is 1, and so on.) + * + * @method splice + * @param {Array} list Array to splice into + * @param {any} value value to be spliced in + * @param {Number} position in the array from which to insert data + * + * @example + * <div class = "norender"><code> + * function setup() { + * var myArray = new Array(0,1,2,3,4); + * var insArray = new Array("A","B","C"); + * print(myArray); // [0,1,2,3,4] + * print(insArray); // ["A","B","C"] + * + * splice(myArray, insArray, 3); + * print(myArray); // [0,1,2,"A","B","C",3,4] + * } + * </div></code> + */ +p5.prototype.splice = function(list, value, index) { + + // note that splice returns spliced elements and not an array + Array.prototype.splice.apply(list, [index, 0].concat(value)); + + return list; +}; + +/** + * Extracts an array of elements from an existing array. The list parameter + * defines the array from which the elements will be copied, and the start + * and count parameters specify which elements to extract. If no count is + * given, elements will be extracted from the start to the end of the array. + * When specifying the start, remember that the first array element is 0. + * This function does not change the source array. + * + * @method subset + * @param {Array} list Array to extract from + * @param {Number} start position to begin + * @param {Number} [count] number of values to extract + * @return {Array} Array of extracted elements + * + * @example + * <div class = "norender"><code> + * function setup() { + * var myArray = new Array(1,2,3,4,5); + * print(myArray); // [1,2,3,4,5] + * + * var sub1 = subset(myArray, 0, 3); + * var sub2 = subset(myArray, 2, 2); + * print(sub1); // [1,2,3] + * print(sub2); // [3,4] + * } + * </div></code> + */ +p5.prototype.subset = function(list, start, count) { + if (typeof count !== 'undefined') { + return list.slice(start, start + count); + } else { + return list.slice(start, list.length); + } +}; + +module.exports = p5; + +},{"../core/core":48}],84:[function(_dereq_,module,exports){ +/** + * @module Data + * @submodule Conversion + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Converts a string to its floating point representation. The contents of a + * string must resemble a number, or NaN (not a number) will be returned. + * For example, float("1234.56") evaluates to 1234.56, but float("giraffe") + * will return NaN. + * + * @method float + * @param {String} str float string to parse + * @return {Number} floating point representation of string + * @example + * <div><code> + * var str = '20'; + * var diameter = float(str); + * ellipse(width/2, height/2, diameter, diameter); + * </code></div> + */ +p5.prototype.float = function(str) { + return parseFloat(str); +}; + +/** + * Converts a boolean, string, or float to its integer representation. + * When an array of values is passed in, then an int array of the same length + * is returned. + * + * @method int + * @param {String|Boolean|Number|Array} n value to parse + * @return {Number} integer representation of value + * @example + * <div class='norender'><code> + * print(int("10")); // 10 + * print(int(10.31)); // 10 + * print(int(-10)); // -10 + * print(int(true)); // 1 + * print(int(false)); // 0 + * print(int([false, true, "10.3", 9.8])); // [0, 1, 10, 9] + * </code></div> + */ +p5.prototype.int = function(n, radix) { + if (typeof n === 'string') { + radix = radix || 10; + return parseInt(n, radix); + } else if (typeof n === 'number') { + return n | 0; + } else if (typeof n === 'boolean') { + return n ? 1 : 0; + } else if (n instanceof Array) { + return n.map(function(n) { return p5.prototype.int(n, radix); }); + } +}; + +/** + * Converts a boolean, string or number to its string representation. + * When an array of values is passed in, then an array of strings of the same + * length is returned. + * + * @method str + * @param {String|Boolean|Number|Array} n value to parse + * @return {String} string representation of value + * @example + * <div class='norender'><code> + * print(str("10")); // "10" + * print(str(10.31)); // "10.31" + * print(str(-10)); // "-10" + * print(str(true)); // "true" + * print(str(false)); // "false" + * print(str([true, "10.3", 9.8])); // [ "true", "10.3", "9.8" ] + * </code></div> + */ +p5.prototype.str = function(n) { + if (n instanceof Array) { + return n.map(p5.prototype.str); + } else { + return String(n); + } +}; + +/** + * Converts a number or string to its boolean representation. + * For a number, any non-zero value (positive or negative) evaluates to true, + * while zero evaluates to false. For a string, the value "true" evaluates to + * true, while any other value evaluates to false. When an array of number or + * string values is passed in, then a array of booleans of the same length is + * returned. + * + * @method boolean + * @param {String|Boolean|Number|Array} n value to parse + * @return {Boolean} boolean representation of value + * @example + * <div class='norender'><code> + * print(boolean(0)); // false + * print(boolean(1)); // true + * print(boolean("true")); // true + * print(boolean("abcd")); // false + * print(boolean([0, 12, "true"])); // [false, true, false] + * </code></div> + */ +p5.prototype.boolean = function(n) { + if (typeof n === 'number') { + return n !== 0; + } else if (typeof n === 'string') { + return n.toLowerCase() === 'true'; + } else if (typeof n === 'boolean') { + return n; + } else if (n instanceof Array) { + return n.map(p5.prototype.boolean); + } +}; + +/** + * Converts a number, string or boolean to its byte representation. + * A byte can be only a whole number between -128 and 127, so when a value + * outside of this range is converted, it wraps around to the corresponding + * byte representation. When an array of number, string or boolean values is + * passed in, then an array of bytes the same length is returned. + * + * @method byte + * @param {String|Boolean|Number|Array} n value to parse + * @return {Number} byte representation of value + * @example + * <div class='norender'><code> + * print(byte(127)); // 127 + * print(byte(128)); // -128 + * print(byte(23.4)); // 23 + * print(byte("23.4")); // 23 + * print(byte(true)); // 1 + * print(byte([0, 255, "100"])); // [0, -1, 100] + * </code></div> + */ +p5.prototype.byte = function(n) { + var nn = p5.prototype.int(n, 10); + if (typeof nn === 'number') { + return ((nn + 128) % 256) - 128; + } else if (nn instanceof Array) { + return nn.map(p5.prototype.byte); + } +}; + +/** + * Converts a number or string to its corresponding single-character + * string representation. If a string parameter is provided, it is first + * parsed as an integer and then translated into a single-character string. + * When an array of number or string values is passed in, then an array of + * single-character strings of the same length is returned. + * + * @method char + * @param {String|Number|Array} n value to parse + * @return {String} string representation of value + * @example + * <div class='norender'><code> + * print(char(65)); // "A" + * print(char("65")); // "A" + * print(char([65, 66, 67])); // [ "A", "B", "C" ] + * print(join(char([65, 66, 67]), '')); // "ABC" + * </code></div> + */ +p5.prototype.char = function(n) { + if (typeof n === 'number' && !isNaN(n)) { + return String.fromCharCode(n); + } else if (n instanceof Array) { + return n.map(p5.prototype.char); + } else if (typeof n === 'string') { + return p5.prototype.char(parseInt(n, 10)); + } +}; + +/** + * Converts a single-character string to its corresponding integer + * representation. When an array of single-character string values is passed + * in, then an array of integers of the same length is returned. + * + * @method unchar + * @param {String|Array} n value to parse + * @return {Number} integer representation of value + * @example + * <div class='norender'><code> + * print(unchar("A")); // 65 + * print(unchar(["A", "B", "C"])); // [ 65, 66, 67 ] + * print(unchar(split("ABC", ""))); // [ 65, 66, 67 ] + * </code></div> + */ +p5.prototype.unchar = function(n) { + if (typeof n === 'string' && n.length === 1) { + return n.charCodeAt(0); + } else if (n instanceof Array) { + return n.map(p5.prototype.unchar); + } +}; + +/** + * Converts a number to a string in its equivalent hexadecimal notation. If a + * second parameter is passed, it is used to set the number of characters to + * generate in the hexadecimal notation. When an array is passed in, an + * array of strings in hexadecimal notation of the same length is returned. + * + * @method hex + * @param {Number|Array} n value to parse + * @return {String} hexadecimal string representation of value + * @example + * <div class='norender'><code> + * print(hex(255)); // "000000FF" + * print(hex(255, 6)); // "0000FF" + * print(hex([0, 127, 255], 6)); // [ "000000", "00007F", "0000FF" ] + * </code></div> + */ +p5.prototype.hex = function(n, digits) { + digits = (digits === undefined || digits === null) ? digits = 8 : digits; + if (n instanceof Array) { + return n.map(function(n) { return p5.prototype.hex(n, digits); }); + } else if (typeof n === 'number') { + if (n < 0) { + n = 0xFFFFFFFF + n + 1; + } + var hex = Number(n).toString(16).toUpperCase(); + while (hex.length < digits) { + hex = '0' + hex; + } + if (hex.length >= digits) { + hex = hex.substring(hex.length - digits, hex.length); + } + return hex; + } +}; + +/** + * Converts a string representation of a hexadecimal number to its equivalent + * integer value. When an array of strings in hexadecimal notation is passed + * in, an array of integers of the same length is returned. + * + * @method unhex + * @param {String|Array} n value to parse + * @return {Number} integer representation of hexadecimal value + * @example + * <div class='norender'><code> + * print(unhex("A")); // 10 + * print(unhex("FF")); // 255 + * print(unhex(["FF", "AA", "00"])); // [ 255, 170, 0 ] + * </code></div> + */ +p5.prototype.unhex = function(n) { + if (n instanceof Array) { + return n.map(p5.prototype.unhex); + } else { + return parseInt('0x' + n, 16); + } +}; + +module.exports = p5; + +},{"../core/core":48}],85:[function(_dereq_,module,exports){ +/** + * @module Data + * @submodule String Functions + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +//return p5; //LM is this a mistake? + +/** + * 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(). + * + * @method join + * @param {Array} list array of Strings to be joined + * @param {String} separator String to be placed between each item + * @return {String} joined String + * @example + * <div> + * <code> + * var array = ["Hello", "world!"] + * var separator = " " + * var message = join(array, separator); + * text(message, 5, 50); + * </code> + * </div> + */ +p5.prototype.join = function(list, separator) { + return list.join(separator); +}; + +/** + * This function is used to apply a regular expression to a piece of text, + * and return matching groups (elements found inside parentheses) as a + * String array. If there are no matches, a null value will be returned. + * If no groups are specified in the regular expression, but the sequence + * matches, an array of length 1 (with the matched text as the first element + * of the array) will be returned. + * <br><br> + * To use the function, first check to see if the result is null. If the + * result is null, then the sequence did not match at all. If the sequence + * did match, an array is returned. + * <br><br> + * If there are groups (specified by sets of parentheses) in the regular + * expression, then the contents of each will be returned in the array. + * Element [0] of a regular expression match returns the entire matching + * string, and the match groups start at element [1] (the first group is [1], + * the second [2], and so on). + * + * @method match + * @param {String} str the String to be searched + * @param {String} regexp the regexp to be used for matching + * @return {Array} Array of Strings found + * @example + * <div> + * <code> + * var string = "Hello p5js*!" + * var regexp = "p5js\\*" + * var match = match(string, regexp); + * text(match, 5, 50); + * </code> + * </div> + */ +p5.prototype.match = function(str, reg) { + return str.match(reg); +}; + +/** + * This function is used to apply a regular expression to a piece of text, + * and return a list of matching groups (elements found inside parentheses) + * as a two-dimensional String array. If there are no matches, a null value + * will be returned. If no groups are specified in the regular expression, + * but the sequence matches, a two dimensional array is still returned, but + * the second dimension is only of length one. + * <br><br> + * To use the function, first check to see if the result is null. If the + * result is null, then the sequence did not match at all. If the sequence + * did match, a 2D array is returned. + * <br><br> + * If there are groups (specified by sets of parentheses) in the regular + * expression, then the contents of each will be returned in the array. + * Assuming a loop with counter variable i, element [i][0] of a regular + * expression match returns the entire matching string, and the match groups + * start at element [i][1] (the first group is [i][1], the second [i][2], + * and so on). + * + * @method matchAll + * @param {String} str the String to be searched + * @param {String} regexp the regexp to be used for matching + * @return {Array} 2d Array of Strings found + * @example + * <div class="norender"> + * <code> + * var string = "Hello p5js*! Hello world!" + * var regexp = "Hello" + * matchAll(string, regexp); + * </code> + * </div> + + */ +p5.prototype.matchAll = function(str, reg) { + var re = new RegExp(reg, 'g'); + var match = re.exec(str); + var matches = []; + while (match !== null) { + matches.push(match); + // matched text: match[0] + // match start: match.index + // capturing group n: match[n] + match = re.exec(str); + } + return matches; +}; + +/** + * Utility function for formatting numbers into strings. There are two + * versions: one for formatting floats, and one for formatting ints. + * The values for the digits, left, and right parameters should always + * be positive integers. + * + * @method nf + * @param {Number|Array} num the Number to format + * @param {Number} [left] number of digits to the left of the + * decimal point + * @param {Number} [right] number of digits to the right of the + * decimal point + * @return {String|Array} formatted String + * @example + * <div> + * <code> + * function setup() { + * background(200); + * var num = 112.53106115; + * + * noStroke(); + * fill(0); + * textSize(14); + * // Draw formatted numbers + * text(nf(num, 5, 2), 10, 20); + * + * text(nf(num, 4, 3), 10, 55); + * + * text(nf(num, 3, 6), 10, 85); + * + * // Draw dividing lines + * stroke(120); + * line(0, 30, width, 30); + * line(0, 65, width, 65); + * } + * </code> + * </div> + */ +p5.prototype.nf = function () { + if (arguments[0] instanceof Array) { + var a = arguments[1]; + var b = arguments[2]; + return arguments[0].map(function (x) { + return doNf(x, a, b); + }); + } + else{ + var typeOfFirst = Object.prototype.toString.call(arguments[0]); + if(typeOfFirst === '[object Arguments]'){ + if(arguments[0].length===3){ + return this.nf(arguments[0][0],arguments[0][1],arguments[0][2]); + } + else if(arguments[0].length===2){ + return this.nf(arguments[0][0],arguments[0][1]); + } + else{ + return this.nf(arguments[0][0]); + } + } + else { + return doNf.apply(this, arguments); + } + } +}; + +function doNf() { + var num = arguments[0]; + var neg = num < 0; + var n = neg ? num.toString().substring(1) : num.toString(); + var decimalInd = n.indexOf('.'); + var intPart = decimalInd !== -1 ? n.substring(0, decimalInd) : n; + var decPart = decimalInd !== -1 ? n.substring(decimalInd + 1) : ''; + var str = neg ? '-' : ''; + if (arguments.length === 3) { + var decimal = ''; + if(decimalInd !== -1 || arguments[2] - decPart.length > 0){ + decimal = '.'; + } + if (decPart.length > arguments[2]) { + decPart = decPart.substring(0, arguments[2]); + } + for (var i = 0; i < arguments[1] - intPart.length; i++) { + str += '0'; + } + str += intPart; + str += decimal; + str += decPart; + for (var j = 0; j < arguments[2] - decPart.length; j++) { + str += '0'; + } + return str; + } + else { + for (var k = 0; k < Math.max(arguments[1] - intPart.length, 0); k++) { + str += '0'; + } + str += n; + return str; + } +} + +/** + * Utility function for formatting numbers into strings and placing + * appropriate commas to mark units of 1000. There are two versions: one + * for formatting ints, and one for formatting an array of ints. The value + * for the right parameter should always be a positive integer. + * + * @method nfc + * @param {Number|Array} num the Number to format + * @param {Number} [right] number of digits to the right of the + * decimal point + * @return {String|Array} formatted String + * @example + * <div> + * <code> + * function setup() { + * background(200); + * var num = 11253106.115; + * var numArr = new Array(1,1,2); + * + * noStroke(); + * fill(0); + * textSize(12); + * + * // Draw formatted numbers + * text(nfc(num, 4, 2), 10, 30); + * text(nfc(numArr, 2, 1), 10, 80); + * + * // Draw dividing line + * stroke(120); + * line(0, 50, width, 50); + * } + * </code> + * </div> + */ +p5.prototype.nfc = function () { + if (arguments[0] instanceof Array) { + var a = arguments[1]; + return arguments[0].map(function (x) { + return doNfc(x, a); + }); + } else { + return doNfc.apply(this, arguments); + } +}; +function doNfc() { + var num = arguments[0].toString(); + var dec = num.indexOf('.'); + var rem = dec !== -1 ? num.substring(dec) : ''; + var n = dec !== -1 ? num.substring(0, dec) : num; + n = n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + if (arguments[1] === 0) { + rem = ''; + } + else if(arguments[1] !== undefined){ + if(arguments[1] > rem.length){ + rem+= dec === -1 ? '.' : ''; + var len = arguments[1] - rem.length + 1; + for(var i =0; i< len; i++){ + rem += '0'; + } + } + else{ + rem = rem.substring(0, arguments[1] + 1); + } + } + return n + rem; +} + +/** + * Utility function for formatting numbers into strings. Similar to nf() but + * puts a "+" in front of positive numbers and a "-" in front of negative + * numbers. There are two versions: one for formatting floats, and one for + * formatting ints. The values for left, and right parameters + * should always be positive integers. + * + * @method nfp + * @param {Number|Array} num the Number to format + * @param {Number} [left] number of digits to the left of the decimal + * point + * @param {Number} [right] number of digits to the right of the + * decimal point + * @return {String|Array} formatted String + * @example + * <div> + * <code> + * function setup() { + * background(200); + * var num1 = 11253106.115; + * var num2 = -11253106.115; + * + * noStroke(); + * fill(0); + * textSize(12); + * + * // Draw formatted numbers + * text(nfp(num1, 4, 2), 10, 30); + * text(nfp(num2, 4, 2), 10, 80); + * + * // Draw dividing line + * stroke(120); + * line(0, 50, width, 50); + * } + * </code> + * </div> + */ +p5.prototype.nfp = function() { + var nfRes = this.nf.apply(this, arguments); + if (nfRes instanceof Array) { + return nfRes.map(addNfp); + } else { + return addNfp(nfRes); + } +}; + +function addNfp() { + return ( + parseFloat(arguments[0]) > 0) ? + '+'+arguments[0].toString() : + arguments[0].toString(); +} + +/** + * Utility function for formatting numbers into strings. Similar to nf() but + * puts a " " (space) in front of positive numbers and a "-" in front of + * negative numbers. There are two versions: one for formatting floats, and + * one for formatting ints. The values for the digits, left, and right + * parameters should always be positive integers. + * + * @method nfs + * @param {Number|Array} num the Number to format + * @param {Number} [left] number of digits to the left of the decimal + * point + * @param {Number} [right] number of digits to the right of the + * decimal point + * @return {String|Array} formatted String + * @example + * <div> + * <code> + * function setup() { + * background(200); + * var num1 = 11253106.115; + * var num2 = -11253106.115; + * + * noStroke(); + * fill(0); + * textSize(12); + * // Draw formatted numbers + * text(nfs(num1, 4, 2), 10, 30); + * + * text(nfs(num2, 4, 2), 10, 80); + * + * // Draw dividing line + * stroke(120); + * line(0, 50, width, 50); + * } + * </code> + * </div> + */ +p5.prototype.nfs = function() { + var nfRes = this.nf.apply(this, arguments); + if (nfRes instanceof Array) { + return nfRes.map(addNfs); + } else { + return addNfs(nfRes); + } +}; + +function addNfs() { + return ( + parseFloat(arguments[0]) > 0) ? + ' '+arguments[0].toString() : + arguments[0].toString(); +} + +/** + * The split() function maps to String.split(), it breaks a String into + * pieces using a character or string as the delimiter. 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. + * + * The splitTokens() function works in a similar fashion, except that it + * splits using a range of characters instead of a specific character or + * sequence. + * + * @method split + * @param {String} value the String to be split + * @param {String} delim the String used to separate the data + * @return {Array} Array of Strings + * @example + * <div> + * <code> + * var names = "Pat,Xio,Alex" + * var splitString = split(names, ","); + * text(splitString[0], 5, 30); + * text(splitString[1], 5, 50); + * text(splitString[2], 5, 70); + * </code> + * </div> + */ +p5.prototype.split = function(str, delim) { + return str.split(delim); +}; + +/** + * The splitTokens() function splits a String at one or many character + * delimiters or "tokens." The delim parameter specifies the character or + * characters to be used as a boundary. + * <br><br> + * If no delim characters are specified, any whitespace character is used to + * split. Whitespace characters include tab (\t), line feed (\n), carriage + * return (\r), form feed (\f), and space. + * + * @method splitTokens + * @param {String} value the String to be split + * @param {String} [delim] list of individual Strings that will be used as + * separators + * @return {Array} Array of Strings + * @example + * <div class = "norender"> + * <code> + * function setup() { + * var myStr = "Mango, Banana, Lime"; + * var myStrArr = splitTokens(myStr, ","); + * + * print(myStrArr); // prints : ["Mango"," Banana"," Lime"] + * } + * </code> + * </div> + */ +p5.prototype.splitTokens = function() { + var d,sqo,sqc,str; + str = arguments[1]; + if (arguments.length > 1) { + sqc = /\]/g.exec(str); + sqo = /\[/g.exec(str); + if ( sqo && sqc ) { + str = str.slice(0, sqc.index) + str.slice(sqc.index+1); + sqo = /\[/g.exec(str); + str = str.slice(0, sqo.index) + str.slice(sqo.index+1); + d = new RegExp('[\\['+str+'\\]]','g'); + } else if ( sqc ) { + str = str.slice(0, sqc.index) + str.slice(sqc.index+1); + d = new RegExp('[' + str + '\\]]', 'g'); + } else if(sqo) { + str = str.slice(0, sqo.index) + str.slice(sqo.index+1); + d = new RegExp('[' + str + '\\[]', 'g'); + } else { + d = new RegExp('[' + str + ']', 'g'); + } + } else { + d = /\s/g; + } + return arguments[0].split(d).filter(function(n){return n;}); +}; + +/** + * Removes whitespace characters from the beginning and end of a String. In + * addition to standard whitespace characters such as space, carriage return, + * and tab, this function also removes the Unicode "nbsp" character. + * + * @method trim + * @param {String|Array} [str] a String or Array of Strings to be trimmed + * @return {String|Array} a trimmed String or Array of Strings + * @example + * <div> + * <code> + * var string = trim(" No new lines\n "); + * text(string +" here", 2, 50); + * </code> + * </div> + */ +p5.prototype.trim = function(str) { + if (str instanceof Array) { + return str.map(this.trim); + } else { + return str.trim(); + } +}; + +module.exports = p5; + +},{"../core/core":48}],86:[function(_dereq_,module,exports){ +/** + * @module Input + * @submodule Time & Date + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * p5.js communicates with the clock on your computer. The day() function + * returns the current day as a value from 1 - 31. + * + * @method day + * @return {Number} the current day + * @example + * <div> + * <code> + * var day = day(); + * text("Current day: \n" + day, 5, 50); + * </code> + * </div> + */ +p5.prototype.day = function() { + return new Date().getDate(); +}; + +/** + * p5.js communicates with the clock on your computer. The hour() function + * returns the current hour as a value from 0 - 23. + * + * @method hour + * @return {Number} the current hour + * @example + * <div> + * <code> + * var hour = hour(); + * text("Current hour:\n" + hour, 5, 50); + * </code> + * </div> + */ +p5.prototype.hour = function() { + return new Date().getHours(); +}; + +/** + * p5.js communicates with the clock on your computer. The minute() function + * returns the current minute as a value from 0 - 59. + * + * @method minute + * @return {Number} the current minute + * @example + * <div> + * <code> + * var minute = minute(); + * text("Current minute: \n" + minute, 5, 50); + * </code> + * </div> + */ +p5.prototype.minute = function() { + return new Date().getMinutes(); +}; + +/** + * Returns the number of milliseconds (thousandths of a second) since + * starting the program. This information is often used for timing events and + * animation sequences. + * + * @method millis + * @return {Number} the number of milliseconds since starting the program + * @example + * <div> + * <code> + * var millisecond = millis(); + * text("Milliseconds \nrunning: \n" + millisecond, 5, 40); + * </code> + * </div> + */ +p5.prototype.millis = function() { + return window.performance.now(); +}; + +/** + * p5.js communicates with the clock on your computer. The month() function + * returns the current month as a value from 1 - 12. + * + * @method month + * @return {Number} the current month + * @example + * <div> + * <code> + * var month = month(); + * text("Current month: \n" + month, 5, 50); + * </code> + * </div> + */ +p5.prototype.month = function() { + return new Date().getMonth() + 1; //January is 0! +}; + +/** + * p5.js communicates with the clock on your computer. The second() function + * returns the current second as a value from 0 - 59. + * + * @method second + * @return {Number} the current second + * @example + * <div> + * <code> + * var second = second(); + * text("Current second: \n" + second, 5, 50); + * </code> + * </div> + */ +p5.prototype.second = function() { + return new Date().getSeconds(); +}; + +/** + * p5.js communicates with the clock on your computer. The year() function + * returns the current year as an integer (2014, 2015, 2016, etc). + * + * @method year + * @return {Number} the current year + * @example + * <div> + * <code> + * var year = year(); + * text("Current year: \n" + year, 5, 50); + * </code> + * </div> + */ +p5.prototype.year = function() { + return new Date().getFullYear(); +}; + +module.exports = p5; + +},{"../core/core":48}]},{},[39])(39) +});
\ No newline at end of file diff --git a/js/processing.js b/js/processing.js new file mode 100644 index 0000000..4cebd06 --- /dev/null +++ b/js/processing.js @@ -0,0 +1,21748 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ +// build script for generating processing.js + +var Browser = { + isDomPresent: true, + navigator: navigator, + window: window, + document: document, + ajax: function(url) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, false); + if (xhr.overrideMimeType) { + xhr.overrideMimeType("text/plain"); + } + xhr.setRequestHeader("If-Modified-Since", "Fri, 01 Jan 1960 00:00:00 GMT"); + xhr.send(null); + // failed request? + if (xhr.status !== 200 && xhr.status !== 0) { throw ("XMLHttpRequest failed, status code " + xhr.status); } + return xhr.responseText; + } +}; + +window.Processing = require('./src/')(Browser); + +},{"./src/":28}],2:[function(require,module,exports){ +module.exports={ + "name": "processing-js", + "version": "1.4.16", + "author": "Processing.js", + "repository": { + "type": "git", + "url": "git@github.com/processing-js/processing-js.git" + }, + "main": "processing.min.js", + "bugs": "https://github.com/processing-js/processing-js/issues", + "devDependencies": { + "argv": "~0.0.2", + "browserify": "^11.0.1", + "express": "~3.3.3", + "node-minify": "~0.7.3", + "nunjucks": "~0.1.9", + "open": "0.0.3", + "grunt": "~0.4.1", + "grunt-cli": "~0.1.8", + "grunt-contrib-jshint": "~0.4.3" + }, + "scripts": { + "test": "node test", + "start": "browserify build.js -o processing.js && node minify" + }, + "license": "MIT" +} + +},{}],3:[function(require,module,exports){ +/** +* A ObjectIterator is an iterator wrapper for objects. If passed object contains +* the iterator method, the object instance will be replaced by the result returned by +* this method call. If passed object is an array, the ObjectIterator instance iterates +* through its items. +* +* @param {Object} obj The object to be iterated. +*/ +module.exports = function ObjectIterator(obj) { + if (obj instanceof Array) { + // iterate through array items + var index = -1; + this.hasNext = function() { + return ++index < obj.length; + }; + this.next = function() { + return obj[index]; + }; + } else if (obj.iterator instanceof Function) { + return obj.iterator(); + } else { + throw "Unable to iterate: " + obj; + } +}; + +},{}],4:[function(require,module,exports){ +/** + * Processing.js environment constants + */ +module.exports = { + X: 0, + Y: 1, + Z: 2, + + R: 3, + G: 4, + B: 5, + A: 6, + + U: 7, + V: 8, + + NX: 9, + NY: 10, + NZ: 11, + + EDGE: 12, + + // Stroke + SR: 13, + SG: 14, + SB: 15, + SA: 16, + + SW: 17, + + // Transformations (2D and 3D) + TX: 18, + TY: 19, + TZ: 20, + + VX: 21, + VY: 22, + VZ: 23, + VW: 24, + + // Material properties + AR: 25, + AG: 26, + AB: 27, + + DR: 3, + DG: 4, + DB: 5, + DA: 6, + + SPR: 28, + SPG: 29, + SPB: 30, + + SHINE: 31, + + ER: 32, + EG: 33, + EB: 34, + + BEEN_LIT: 35, + + VERTEX_FIELD_COUNT: 36, + + // Renderers + P2D: 1, + JAVA2D: 1, + WEBGL: 2, + P3D: 2, + OPENGL: 2, + PDF: 0, + DXF: 0, + + // Platform IDs + OTHER: 0, + WINDOWS: 1, + MAXOSX: 2, + LINUX: 3, + + EPSILON: 0.0001, + + MAX_FLOAT: 3.4028235e+38, + MIN_FLOAT: -3.4028235e+38, + MAX_INT: 2147483647, + MIN_INT: -2147483648, + + PI: Math.PI, + TWO_PI: 2 * Math.PI, + TAU: 2 * Math.PI, + HALF_PI: Math.PI / 2, + THIRD_PI: Math.PI / 3, + QUARTER_PI: Math.PI / 4, + + DEG_TO_RAD: Math.PI / 180, + RAD_TO_DEG: 180 / Math.PI, + + WHITESPACE: " \t\n\r\f\u00A0", + + // Color modes + RGB: 1, + ARGB: 2, + HSB: 3, + ALPHA: 4, + CMYK: 5, + + // Image file types + TIFF: 0, + TARGA: 1, + JPEG: 2, + GIF: 3, + + // Filter/convert types + BLUR: 11, + GRAY: 12, + INVERT: 13, + OPAQUE: 14, + POSTERIZE: 15, + THRESHOLD: 16, + ERODE: 17, + DILATE: 18, + + // Blend modes + REPLACE: 0, + BLEND: 1 << 0, + ADD: 1 << 1, + SUBTRACT: 1 << 2, + LIGHTEST: 1 << 3, + DARKEST: 1 << 4, + DIFFERENCE: 1 << 5, + EXCLUSION: 1 << 6, + MULTIPLY: 1 << 7, + SCREEN: 1 << 8, + OVERLAY: 1 << 9, + HARD_LIGHT: 1 << 10, + SOFT_LIGHT: 1 << 11, + DODGE: 1 << 12, + BURN: 1 << 13, + + // Color component bit masks + ALPHA_MASK: 0xff000000, + RED_MASK: 0x00ff0000, + GREEN_MASK: 0x0000ff00, + BLUE_MASK: 0x000000ff, + + // Projection matrices + CUSTOM: 0, + ORTHOGRAPHIC: 2, + PERSPECTIVE: 3, + + // Shapes + POINT: 2, + POINTS: 2, + LINE: 4, + LINES: 4, + TRIANGLE: 8, + TRIANGLES: 9, + TRIANGLE_STRIP: 10, + TRIANGLE_FAN: 11, + QUAD: 16, + QUADS: 16, + QUAD_STRIP: 17, + POLYGON: 20, + PATH: 21, + RECT: 30, + ELLIPSE: 31, + ARC: 32, + SPHERE: 40, + BOX: 41, + + GROUP: 0, + PRIMITIVE: 1, + //PATH: 21, // shared with Shape PATH + GEOMETRY: 3, + + // Shape Vertex + VERTEX: 0, + BEZIER_VERTEX: 1, + CURVE_VERTEX: 2, + BREAK: 3, + CLOSESHAPE: 4, + + // Shape closing modes + OPEN: 1, + CLOSE: 2, + + // Shape drawing modes + CORNER: 0, // Draw mode convention to use (x, y) to (width, height) + CORNERS: 1, // Draw mode convention to use (x1, y1) to (x2, y2) coordinates + RADIUS: 2, // Draw mode from the center, and using the radius + CENTER_RADIUS: 2, // Deprecated! Use RADIUS instead + CENTER: 3, // Draw from the center, using second pair of values as the diameter + DIAMETER: 3, // Synonym for the CENTER constant. Draw from the center + CENTER_DIAMETER: 3, // Deprecated! Use DIAMETER instead + + // Text vertical alignment modes + BASELINE: 0, // Default vertical alignment for text placement + TOP: 101, // Align text to the top + BOTTOM: 102, // Align text from the bottom, using the baseline + + // UV Texture coordinate modes + NORMAL: 1, + NORMALIZED: 1, + IMAGE: 2, + + // Text placement modes + MODEL: 4, + SHAPE: 5, + + // Stroke modes + SQUARE: 'butt', + ROUND: 'round', + PROJECT: 'square', + MITER: 'miter', + BEVEL: 'bevel', + + // Lighting modes + AMBIENT: 0, + DIRECTIONAL: 1, + //POINT: 2, Shared with Shape constant + SPOT: 3, + + // Key constants + + // Both key and keyCode will be equal to these values + BACKSPACE: 8, + TAB: 9, + ENTER: 10, + RETURN: 13, + ESC: 27, + DELETE: 127, + CODED: 0xffff, + + // p.key will be CODED and p.keyCode will be this value + SHIFT: 16, + CONTROL: 17, + ALT: 18, + CAPSLK: 20, + PGUP: 33, + PGDN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + F1: 112, + F2: 113, + F3: 114, + F4: 115, + F5: 116, + F6: 117, + F7: 118, + F8: 119, + F9: 120, + F10: 121, + F11: 122, + F12: 123, + NUMLK: 144, + META: 157, + INSERT: 155, + + // Cursor types + ARROW: 'default', + CROSS: 'crosshair', + HAND: 'pointer', + MOVE: 'move', + TEXT: 'text', + WAIT: 'wait', + NOCURSOR: "url('data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='), auto", + + // Hints + DISABLE_OPENGL_2X_SMOOTH: 1, + ENABLE_OPENGL_2X_SMOOTH: -1, + ENABLE_OPENGL_4X_SMOOTH: 2, + ENABLE_NATIVE_FONTS: 3, + DISABLE_DEPTH_TEST: 4, + ENABLE_DEPTH_TEST: -4, + ENABLE_DEPTH_SORT: 5, + DISABLE_DEPTH_SORT: -5, + DISABLE_OPENGL_ERROR_REPORT: 6, + ENABLE_OPENGL_ERROR_REPORT: -6, + ENABLE_ACCURATE_TEXTURES: 7, + DISABLE_ACCURATE_TEXTURES: -7, + HINT_COUNT: 10, + + // PJS defined constants + SINCOS_LENGTH: 720, // every half degree + PRECISIONB: 15, // fixed point precision is limited to 15 bits!! + PRECISIONF: 1 << 15, + PREC_MAXVAL: (1 << 15) - 1, + PREC_ALPHA_SHIFT: 24 - 15, + PREC_RED_SHIFT: 16 - 15, + NORMAL_MODE_AUTO: 0, + NORMAL_MODE_SHAPE: 1, + NORMAL_MODE_VERTEX: 2, + MAX_LIGHTS: 8 +}; + +},{}],5:[function(require,module,exports){ +// the logger for println() +module.exports = function PjsConsole(document) { + var e = { BufferMax: 200 }, + style = document.createElement("style"), + added = false; + + style.textContent = [ + ".pjsconsole.hidden {", + " display: none!important;", + "}" + ].join('\n'); + + e.wrapper = document.createElement("div"); + style.textContent += [ + "", + ".pjsconsole {", + " opacity: .75;", + " display: block;", + " position: fixed;", + " bottom: 0px;", + " left: 0px;", + " right: 0px;", + " height: 50px;", + " background-color: #aaa;", + "}" + ].join('\n'); + e.wrapper.classList.add("pjsconsole"); + + e.dragger = document.createElement("div"); + style.textContent += [ + "", + ".pjsconsole .dragger {", + " display: block;", + " border: 3px black raised;", + " cursor: n-resize;", + " position: absolute;", + " top: 0px;", + " left: 0px;", + " right: 0px;", + " height: 5px;", + " background-color: #333;", + "}" + ].join('\n'); + e.dragger.classList.add("dragger"); + + e.closer = document.createElement("div"); + style.textContent += [ + "", + ".pjsconsole .closer {", + " opacity: .5;", + " display: block;", + " border: 3px black raised;", + " position: absolute;", + " top: 10px;", + " right: 30px;", + " height: 20px;", + " width: 20px;", + " background-color: #ddd;", + " color: #000;", + " line-height: 20px;", + " text-align: center;", + " cursor: pointer", + "}" + ].join('\n'); + e.closer.classList.add("closer"); + e.closer.innerHTML = "✖"; + + e.javaconsole = document.createElement("div"); + style.textContent += [ + "", + ".pjsconsole .console {", + " overflow-x: auto;", + " display: block;", + " position: absolute;", + " left: 10px;", + " right: 0px;", + " bottom: 5px;", + " top: 10px;", + " overflow-y: scroll;", + " height: 40px;", + "}" + ].join('\n'); + e.javaconsole.setAttribute("class", "console"); + + e.wrapper.appendChild(e.dragger); + e.wrapper.appendChild(e.javaconsole); + e.wrapper.appendChild(e.closer); + + e.dragger.onmousedown = function (t) { + e.divheight = e.wrapper.style.height; + if (document.selection) document.selection.empty(); + else window.getSelection().removeAllRanges(); + var n = t.screenY; + window.onmousemove = function (t) { + e.wrapper.style.height = parseFloat(e.divheight) + (n - t.screenY) + "px"; + e.javaconsole.style.height = parseFloat(e.divheight) + (n - t.screenY) - 10 + "px"; + }; + window.onmouseup = function (t) { + if (document.selection) document.selection.empty(); + else window.getSelection().removeAllRanges(); + e.wrapper.style.height = parseFloat(e.divheight) + (n - t.screenY) + "px"; + e.javaconsole.style.height = parseFloat(e.divheight) + (n - t.screenY) - 10 + "px"; + window.onmousemove = null; + window.onmouseup = null; + }; + }; + + e.BufferArray = []; + + e.print = e.log = function () { + var args = Array.prototype.slice.call(arguments); + t = args.map(function(t, idx) { return t + (idx+1 === args.length ? "" : " "); }).join(''); + if (e.BufferArray[e.BufferArray.length - 1]) e.BufferArray[e.BufferArray.length - 1] += (t) + ""; + else e.BufferArray.push(t); + e.javaconsole.innerHTML = e.BufferArray.join(''); + e.showconsole(); + }; + + e.println = function () { + if(!added) { + document.body.appendChild(style); + document.body.appendChild(e.wrapper); + added = true; + } + var args = Array.prototype.slice.call(arguments); + args.push('<br>'); + e.print.apply(e, args); + if (e.BufferArray.length > e.BufferMax) { + e.BufferArray.splice(0, 1); + } else { + e.javaconsole.scrollTop = e.javaconsole.scrollHeight; + } + }; + + e.showconsole = function () { e.wrapper.classList.remove("hidden"); }; + e.hideconsole = function () { e.wrapper.classList.add("hidden"); }; + + e.closer.onclick = function () { e.hideconsole(); }; + + e.hideconsole(); + + return e; +}; + +},{}],6:[function(require,module,exports){ +/** + * Processing.js default scope + */ +module.exports = function(options) { + + // Building defaultScope. Changing of the prototype protects + // internal Processing code from the changes in defaultScope + function DefaultScope() {} + DefaultScope.prototype = options.PConstants; + + var defaultScope = new DefaultScope(); + + // copy over all known Object types and helper objects + Object.keys(options).forEach(function(prop) { + defaultScope[prop] = options[prop]; + }); + + //////////////////////////////////////////////////////////////////////////// + // Class inheritance helper methods + //////////////////////////////////////////////////////////////////////////// + + defaultScope.defineProperty = function(obj, name, desc) { + if("defineProperty" in Object) { + Object.defineProperty(obj, name, desc); + } else { + if (desc.hasOwnProperty("get")) { + obj.__defineGetter__(name, desc.get); + } + if (desc.hasOwnProperty("set")) { + obj.__defineSetter__(name, desc.set); + } + } + }; + + /** + * class overloading, part 1 + */ + function overloadBaseClassFunction(object, name, basefn) { + if (!object.hasOwnProperty(name) || typeof object[name] !== 'function') { + // object method is not a function or just inherited from Object.prototype + object[name] = basefn; + return; + } + var fn = object[name]; + if ("$overloads" in fn) { + // the object method already overloaded (see defaultScope.addMethod) + // let's just change a fallback method + fn.$defaultOverload = basefn; + return; + } + if (!("$overloads" in basefn) && fn.length === basefn.length) { + // special case when we just overriding the method + return; + } + var overloads, defaultOverload; + if ("$overloads" in basefn) { + // let's inherit base class overloads to speed up things + overloads = basefn.$overloads.slice(0); + overloads[fn.length] = fn; + defaultOverload = basefn.$defaultOverload; + } else { + overloads = []; + overloads[basefn.length] = basefn; + overloads[fn.length] = fn; + defaultOverload = fn; + } + var hubfn = function() { + var fn = hubfn.$overloads[arguments.length] || + ("$methodArgsIndex" in hubfn && arguments.length > hubfn.$methodArgsIndex ? + hubfn.$overloads[hubfn.$methodArgsIndex] : null) || + hubfn.$defaultOverload; + return fn.apply(this, arguments); + }; + hubfn.$overloads = overloads; + if ("$methodArgsIndex" in basefn) { + hubfn.$methodArgsIndex = basefn.$methodArgsIndex; + } + hubfn.$defaultOverload = defaultOverload; + hubfn.name = name; + object[name] = hubfn; + } + + /** + * class overloading, part 2 + */ + + function extendClass(subClass, baseClass) { + function extendGetterSetter(propertyName) { + defaultScope.defineProperty(subClass, propertyName, { + get: function() { + return baseClass[propertyName]; + }, + set: function(v) { + baseClass[propertyName]=v; + }, + enumerable: true + }); + } + + var properties = []; + for (var propertyName in baseClass) { + if (typeof baseClass[propertyName] === 'function') { + overloadBaseClassFunction(subClass, propertyName, baseClass[propertyName]); + } else if(propertyName.charAt(0) !== "$" && !(propertyName in subClass)) { + // Delaying the properties extension due to the IE9 bug (see #918). + properties.push(propertyName); + } + } + while (properties.length > 0) { + extendGetterSetter(properties.shift()); + } + + subClass.$super = baseClass; + } + + /** + * class overloading, part 3 + */ + defaultScope.extendClassChain = function(base) { + var path = [base]; + for (var self = base.$upcast; self; self = self.$upcast) { + extendClass(self, base); + path.push(self); + base = self; + } + while (path.length > 0) { + path.pop().$self=base; + } + }; + + // static + defaultScope.extendStaticMembers = function(derived, base) { + extendClass(derived, base); + }; + + // interface + defaultScope.extendInterfaceMembers = function(derived, base) { + extendClass(derived, base); + }; + + /** + * Java methods and JavaScript functions differ enough that + * we need a special function to make sure it all links up + * as classical hierarchical class chains. + */ + defaultScope.addMethod = function(object, name, fn, hasMethodArgs) { + var existingfn = object[name]; + if (existingfn || hasMethodArgs) { + var args = fn.length; + // builds the overload methods table + if ("$overloads" in existingfn) { + existingfn.$overloads[args] = fn; + } else { + var hubfn = function() { + var fn = hubfn.$overloads[arguments.length] || + ("$methodArgsIndex" in hubfn && arguments.length > hubfn.$methodArgsIndex ? + hubfn.$overloads[hubfn.$methodArgsIndex] : null) || + hubfn.$defaultOverload; + return fn.apply(this, arguments); + }; + var overloads = []; + if (existingfn) { + overloads[existingfn.length] = existingfn; + } + overloads[args] = fn; + hubfn.$overloads = overloads; + hubfn.$defaultOverload = existingfn || fn; + if (hasMethodArgs) { + hubfn.$methodArgsIndex = args; + } + hubfn.name = name; + object[name] = hubfn; + } + } else { + object[name] = fn; + } + }; + + // internal helper function + function isNumericalJavaType(type) { + if (typeof type !== "string") { + return false; + } + return ["byte", "int", "char", "color", "float", "long", "double"].indexOf(type) !== -1; + } + + /** + * Java's arrays are pre-filled when declared with + * an initial size, but no content. JS arrays are not. + */ + defaultScope.createJavaArray = function(type, bounds) { + var result = null, + defaultValue = null; + if (typeof type === "string") { + if (type === "boolean") { + defaultValue = false; + } else if (isNumericalJavaType(type)) { + defaultValue = 0; + } + } + if (typeof bounds[0] === 'number') { + var itemsCount = 0 | bounds[0]; + if (bounds.length <= 1) { + result = []; + result.length = itemsCount; + for (var i = 0; i < itemsCount; ++i) { + result[i] = defaultValue; + } + } else { + result = []; + var newBounds = bounds.slice(1); + for (var j = 0; j < itemsCount; ++j) { + result.push(defaultScope.createJavaArray(type, newBounds)); + } + } + } + return result; + }; + + // screenWidth and screenHeight are shared by all instances. + // and return the width/height of the browser's viewport. + defaultScope.defineProperty(defaultScope, 'screenWidth', + { get: function() { return window.innerWidth; } }); + + defaultScope.defineProperty(defaultScope, 'screenHeight', + { get: function() { return window.innerHeight; } }); + + return defaultScope; +}; + +},{}],7:[function(require,module,exports){ +/** + * Finalise the Processing.js object. + */ +module.exports = function finalizeProcessing(Processing, options) { + + // unpack options + var window = options.window, + document = options.document, + XMLHttpRequest = window.XMLHttpRequest, + noop = options.noop, + isDOMPresent = options.isDOMPresent, + version = options.version, + undef; + + // versioning + Processing.version = (version ? version : "@DEV-VERSION@"); + + // Share lib space + Processing.lib = {}; + + /** + * External libraries can be added to the global Processing + * objects with the `registerLibrary` function. + */ + Processing.registerLibrary = function(name, library) { + Processing.lib[name] = library; + if(library.hasOwnProperty("init")) { + library.init(defaultScope); + } + }; + + /** + * This is the object that acts as our version of PApplet. + * This can be called as Processing.Sketch() or as + * Processing.Sketch(function) in which case the function + * must be an already-compiled-to-JS sketch function. + */ + Processing.Sketch = function(attachFunction) { + this.attachFunction = attachFunction; + this.options = { + pauseOnBlur: false, + globalKeyEvents: false + }; + + /* Optional Sketch event hooks: + * onLoad - parsing/preloading is done, before sketch starts + * onSetup - setup() has been called, before first draw() + * onPause - noLoop() has been called, pausing draw loop + * onLoop - loop() has been called, resuming draw loop + * onFrameStart - draw() loop about to begin + * onFrameEnd - draw() loop finished + * onExit - exit() done being called + */ + this.onLoad = noop; + this.onSetup = noop; + this.onPause = noop; + this.onLoop = noop; + this.onFrameStart = noop; + this.onFrameEnd = noop; + this.onExit = noop; + + this.params = {}; + this.imageCache = { + pending: 0, + images: {}, + // Opera requires special administration for preloading + operaCache: {}, + // Specify an optional img arg if the image is already loaded in the DOM, + // otherwise href will get loaded. + add: function(href, img) { + // Prevent muliple loads for an image, in case it gets + // preloaded more than once, or is added via JS and then preloaded. + if (this.images[href]) { + return; + } + + if (!isDOMPresent) { + this.images[href] = null; + } + + // No image in the DOM, kick-off a background load + if (!img) { + img = new Image(); + img.onload = (function(owner) { + return function() { + owner.pending--; + }; + }(this)); + this.pending++; + img.src = href; + } + + this.images[href] = img; + + // Opera will not load images until they are inserted into the DOM. + if (window.opera) { + var div = document.createElement("div"); + div.appendChild(img); + // we can't use "display: none", since that makes it invisible, and thus not load + div.style.position = "absolute"; + div.style.opacity = 0; + div.style.width = "1px"; + div.style.height= "1px"; + if (!this.operaCache[href]) { + document.body.appendChild(div); + this.operaCache[href] = div; + } + } + } + }; + + this.sourceCode = undefined; + this.attach = function(processing) { + // either attachFunction or sourceCode must be present on attach + if(typeof this.attachFunction === "function") { + this.attachFunction(processing); + } else if(this.sourceCode) { + var func = ((new Function("return (" + this.sourceCode + ");"))()); + func(processing); + this.attachFunction = func; + } else { + throw "Unable to attach sketch to the processing instance"; + } + }; + + this.toString = function() { + var i; + var code = "((function(Sketch) {\n"; + code += "var sketch = new Sketch(\n" + this.sourceCode + ");\n"; + for(i in this.options) { + if(this.options.hasOwnProperty(i)) { + var value = this.options[i]; + code += "sketch.options." + i + " = " + + (typeof value === 'string' ? '\"' + value + '\"' : "" + value) + ";\n"; + } + } + for(i in this.imageCache) { + if(this.options.hasOwnProperty(i)) { + code += "sketch.imageCache.add(\"" + i + "\");\n"; + } + } + // TODO serialize fonts + code += "return sketch;\n})(Processing.Sketch))"; + return code; + }; + }; + + /** + * aggregate all source code into a single file, then rewrite that + * source and bind to canvas via new Processing(canvas, sourcestring). + * @param {CANVAS} canvas The html canvas element to bind to + * @param {String[]} source The array of files that must be loaded + */ + var loadSketchFromSources = Processing.loadSketchFromSources = function(canvas, sources) { + var code = [], errors = [], sourcesCount = sources.length, loaded = 0; + + function ajaxAsync(url, callback) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + var error; + if (xhr.status !== 200 && xhr.status !== 0) { + error = "Invalid XHR status " + xhr.status; + } else if (xhr.responseText === "") { + // Give a hint when loading fails due to same-origin issues on file:/// urls + if ( ("withCredentials" in new XMLHttpRequest()) && + (new XMLHttpRequest()).withCredentials === false && + window.location.protocol === "file:" ) { + error = "XMLHttpRequest failure, possibly due to a same-origin policy violation. You can try loading this page in another browser, or load it from http://localhost using a local webserver. See the Processing.js README for a more detailed explanation of this problem and solutions."; + } else { + error = "File is empty."; + } + } + + callback(xhr.responseText, error); + } + }; + xhr.open("GET", url, true); + if (xhr.overrideMimeType) { + xhr.overrideMimeType("application/json"); + } + xhr.setRequestHeader("If-Modified-Since", "Fri, 01 Jan 1960 00:00:00 GMT"); // no cache + xhr.send(null); + } + + function loadBlock(index, filename) { + function callback(block, error) { + code[index] = block; + ++loaded; + if (error) { + errors.push(filename + " ==> " + error); + } + if (loaded === sourcesCount) { + if (errors.length === 0) { + // This used to throw, but it was constantly getting in the way of debugging where things go wrong! + return new Processing(canvas, code.join("\n")); + } else { + throw "Processing.js: Unable to load pjs sketch files: " + errors.join("\n"); + } + } + } + if (filename.charAt(0) === '#') { + // trying to get script from the element + var scriptElement = document.getElementById(filename.substring(1)); + if (scriptElement) { + callback(scriptElement.text || scriptElement.textContent); + } else { + callback("", "Unable to load pjs sketch: element with id \'" + filename.substring(1) + "\' was not found"); + } + return; + } + + ajaxAsync(filename, callback); + } + + for (var i = 0; i < sourcesCount; ++i) { + loadBlock(i, sources[i]); + } + }; + + /** + * Automatic initialization function. + */ + var init = function() { + document.removeEventListener('DOMContentLoaded', init, false); + var i; + + // before running through init, clear the instances list, to prevent + // sketch duplication when page content is dynamically swapped without + // swapping out processing.js + while (Processing.instances.length > 0) { + for (i = Processing.instances.length - 1; i >= 0; i--) { + if (Processing.instances[i]) { + Processing.instances[i].exit(); + } + } + } + + var canvas = document.getElementsByTagName('canvas'), + filenames; + + for (i = 0, l = canvas.length; i < l; i++) { + // datasrc and data-src are deprecated. + var processingSources = canvas[i].getAttribute('data-processing-sources'); + if (processingSources === null) { + // Temporary fallback for datasrc and data-src + processingSources = canvas[i].getAttribute('data-src'); + if (processingSources === null) { + processingSources = canvas[i].getAttribute('datasrc'); + } + } + if (processingSources) { + filenames = processingSources.split(/\s+/g); + for (var j = 0; j < filenames.length;) { + if (filenames[j]) { + j++; + } else { + filenames.splice(j, 1); + } + } + loadSketchFromSources(canvas[i], filenames); + } + } + + // also process all <script>-indicated sketches, if there are any + var s, last, source, instance, + nodelist = document.getElementsByTagName('script'), + scripts=[]; + + // snapshot the DOM, as the nodelist is only a DOM view, and is + // updated instantly when a script element is added or removed. + for (s = nodelist.length - 1; s >= 0; s--) { + scripts.push(nodelist[s]); + } + + // iterate over all script elements to see if they contain Processing code + for (s = 0, last = scripts.length; s < last; s++) { + var script = scripts[s]; + if (!script.getAttribute) { + continue; + } + + var type = script.getAttribute("type"); + if (type && (type.toLowerCase() === "text/processing" || type.toLowerCase() === "application/processing")) { + var target = script.getAttribute("data-processing-target"); + canvas = undef; + if (target) { + canvas = document.getElementById(target); + } else { + var nextSibling = script.nextSibling; + while (nextSibling && nextSibling.nodeType !== 1) { + nextSibling = nextSibling.nextSibling; + } + if (nextSibling && nextSibling.nodeName.toLowerCase() === "canvas") { + canvas = nextSibling; + } + } + + if (canvas) { + if (script.getAttribute("src")) { + filenames = script.getAttribute("src").split(/\s+/); + loadSketchFromSources(canvas, filenames); + continue; + } + source = script.textContent || script.text; + instance = new Processing(canvas, source); + } + } + } + }; + + /** + * automatic loading of all sketches on the page + */ + document.addEventListener('DOMContentLoaded', init, false); + + /** + * Make Processing run through init after already having + * been set up for a page. This function exists mostly for pages + * that swap content in/out without reloading a page. + */ + Processing.reload = init; + + /** + * Disable the automatic loading of all sketches on the page. + * This will work as long as it's issued before DOMContentLoaded. + */ + Processing.disableInit = function() { + document.removeEventListener('DOMContentLoaded', init, false); + }; + + // done. + return Processing; +}; + +},{}],8:[function(require,module,exports){ +/** + * Returns Java equals() result for two objects. If the first object + * has the "equals" function, it preforms the call of this function. + * Otherwise the method uses the JavaScript === operator. + * + * @param {Object} obj The first object. + * @param {Object} other The second object. + * + * @returns {boolean} true if the objects are equal. + */ +module.exports = function virtEquals(obj, other) { + if (obj === null || other === null) { + return (obj === null) && (other === null); + } + if (typeof (obj) === "string") { + return obj === other; + } + if (typeof(obj) !== "object") { + return obj === other; + } + if (obj.equals instanceof Function) { + return obj.equals(other); + } + return obj === other; +}; + +},{}],9:[function(require,module,exports){ +/** + * Returns Java hashCode() result for the object. If the object has the "hashCode" function, + * it preforms the call of this function. Otherwise it uses/creates the "$id" property, + * which is used as the hashCode. + * + * @param {Object} obj The object. + * @returns {int} The object's hash code. + */ +module.exports = function virtHashCode(obj, undef) { + if (typeof(obj) === "string") { + var hash = 0; + for (var i = 0; i < obj.length; ++i) { + hash = (hash * 31 + obj.charCodeAt(i)) & 0xFFFFFFFF; + } + return hash; + } + if (typeof(obj) !== "object") { + return obj & 0xFFFFFFFF; + } + if (obj.hashCode instanceof Function) { + return obj.hashCode(); + } + if (obj.$id === undef) { + obj.$id = ((Math.floor(Math.random() * 0x10000) - 0x8000) << 16) | Math.floor(Math.random() * 0x10000); + } + return obj.$id; +}; + +},{}],10:[function(require,module,exports){ +/** + * An ArrayList stores a variable number of objects. + * + * @param {int} initialCapacity optional defines the initial capacity of the list, it's empty by default + * + * @returns {ArrayList} new ArrayList object + */ +module.exports = function(options) { + var virtHashCode = options.virtHashCode, + virtEquals = options.virtEquals; + + function Iterator(array) { + var index = -1; + this.hasNext = function() { + return (index + 1) < array.length; + }; + + this.next = function() { + return array[++index]; + }; + + this.remove = function() { + array.splice(index--, 1); + }; + } + + function ArrayList(a) { + var array = []; + + if (a && a.toArray) { + array = a.toArray(); + } + + /** + * @member ArrayList + * ArrayList.get() Returns the element at the specified position in this list. + * + * @param {int} i index of element to return + * + * @returns {Object} the element at the specified position in this list. + */ + this.get = function(i) { + return array[i]; + }; + /** + * @member ArrayList + * ArrayList.contains() Returns true if this list contains the specified element. + * + * @param {Object} item element whose presence in this List is to be tested. + * + * @returns {boolean} true if the specified element is present; false otherwise. + */ + this.contains = function(item) { + return this.indexOf(item)>-1; + }; + /** + * @member ArrayList + * ArrayList.indexOf() Returns the position this element takes in the list, or -1 if the element is not found. + * + * @param {Object} item element whose position in this List is to be tested. + * + * @returns {int} the list position that the first match for this element holds in the list, or -1 if it is not in the list. + */ + this.indexOf = function(item) { + for (var i = 0, len = array.length; i < len; ++i) { + if (virtEquals(item, array[i])) { + return i; + } + } + return -1; + }; + /** + * @member ArrayList + * ArrayList.lastIndexOf() Returns the index of the last occurrence of the specified element in this list, + * or -1 if this list does not contain the element. More formally, returns the highest index i such that + * (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no such index. + * + * @param {Object} item element to search for. + * + * @returns {int} the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element. + */ + this.lastIndexOf = function(item) { + for (var i = array.length-1; i >= 0; --i) { + if (virtEquals(item, array[i])) { + return i; + } + } + return -1; + }; + /** + * @member ArrayList + * ArrayList.add() Adds the specified element to this list. + * + * @param {int} index optional index at which the specified element is to be inserted + * @param {Object} object element to be added to the list + */ + this.add = function() { + if (arguments.length === 1) { + array.push(arguments[0]); // for add(Object) + } else if (arguments.length === 2) { + var arg0 = arguments[0]; + if (typeof arg0 === 'number') { + if (arg0 >= 0 && arg0 <= array.length) { + array.splice(arg0, 0, arguments[1]); // for add(i, Object) + } else { + throw(arg0 + " is not a valid index"); + } + } else { + throw(typeof arg0 + " is not a number"); + } + } else { + throw("Please use the proper number of parameters."); + } + }; + /** + * @member ArrayList + * ArrayList.addAll(collection) appends all of the elements in the specified + * Collection to the end of this list, in the order that they are returned by + * the specified Collection's Iterator. + * + * When called as addAll(index, collection) the elements are inserted into + * this list at the position indicated by index. + * + * @param {index} Optional; specifies the position the colletion should be inserted at + * @param {collection} Any iterable object (ArrayList, HashMap.keySet(), etc.) + * @throws out of bounds error for negative index, or index greater than list size. + */ + this.addAll = function(arg1, arg2) { + // addAll(int, Collection) + var it; + if (typeof arg1 === "number") { + if (arg1 < 0 || arg1 > array.length) { + throw("Index out of bounds for addAll: " + arg1 + " greater or equal than " + array.length); + } + it = new ObjectIterator(arg2); + while (it.hasNext()) { + array.splice(arg1++, 0, it.next()); + } + } + // addAll(Collection) + else { + it = new ObjectIterator(arg1); + while (it.hasNext()) { + array.push(it.next()); + } + } + }; + /** + * @member ArrayList + * ArrayList.set() Replaces the element at the specified position in this list with the specified element. + * + * @param {int} index index of element to replace + * @param {Object} object element to be stored at the specified position + */ + this.set = function() { + if (arguments.length === 2) { + var arg0 = arguments[0]; + if (typeof arg0 === 'number') { + if (arg0 >= 0 && arg0 < array.length) { + array.splice(arg0, 1, arguments[1]); + } else { + throw(arg0 + " is not a valid index."); + } + } else { + throw(typeof arg0 + " is not a number"); + } + } else { + throw("Please use the proper number of parameters."); + } + }; + + /** + * @member ArrayList + * ArrayList.size() Returns the number of elements in this list. + * + * @returns {int} the number of elements in this list + */ + this.size = function() { + return array.length; + }; + + /** + * @member ArrayList + * ArrayList.clear() Removes all of the elements from this list. The list will be empty after this call returns. + */ + this.clear = function() { + array.length = 0; + }; + + /** + * @member ArrayList + * ArrayList.remove() Removes an element either based on index, if the argument is a number, or + * by equality check, if the argument is an object. + * + * @param {int|Object} item either the index of the element to be removed, or the element itself. + * + * @returns {Object|boolean} If removal is by index, the element that was removed, or null if nothing was removed. If removal is by object, true if removal occurred, otherwise false. + */ + this.remove = function(item) { + if (typeof item === 'number') { + return array.splice(item, 1)[0]; + } + item = this.indexOf(item); + if (item > -1) { + array.splice(item, 1); + return true; + } + return false; + }; + + /** + * @member ArrayList + * ArrayList.removeAll Removes from this List all of the elements from + * the current ArrayList which are present in the passed in paramater ArrayList 'c'. + * Shifts any succeeding elements to the left (reduces their index). + * + * @param {ArrayList} the ArrayList to compare to the current ArrayList + * + * @returns {boolean} true if the ArrayList had an element removed; false otherwise + */ + this.removeAll = function(c) { + var i, x, item, + newList = new ArrayList(); + newList.addAll(this); + this.clear(); + // For every item that exists in the original ArrayList and not in the c ArrayList + // copy it into the empty 'this' ArrayList to create the new 'this' Array. + for (i = 0, x = 0; i < newList.size(); i++) { + item = newList.get(i); + if (!c.contains(item)) { + this.add(x++, item); + } + } + if (this.size() < newList.size()) { + return true; + } + return false; + }; + + /** + * @member ArrayList + * ArrayList.isEmpty() Tests if this list has no elements. + * + * @returns {boolean} true if this list has no elements; false otherwise + */ + this.isEmpty = function() { + return !array.length; + }; + + /** + * @member ArrayList + * ArrayList.clone() Returns a shallow copy of this ArrayList instance. (The elements themselves are not copied.) + * + * @returns {ArrayList} a clone of this ArrayList instance + */ + this.clone = function() { + return new ArrayList(this); + }; + + /** + * @member ArrayList + * ArrayList.toArray() Returns an array containing all of the elements in this list in the correct order. + * + * @returns {Object[]} Returns an array containing all of the elements in this list in the correct order + */ + this.toArray = function() { + return array.slice(0); + }; + + this.iterator = function() { + return new Iterator(array); + }; + } + + return ArrayList; +}; + +},{}],11:[function(require,module,exports){ +module.exports = (function(charMap, undef) { + + var Char = function(chr) { + if (typeof chr === 'string' && chr.length === 1) { + this.code = chr.charCodeAt(0); + } else if (typeof chr === 'number') { + this.code = chr; + } else if (chr instanceof Char) { + this.code = chr; + } else { + this.code = NaN; + } + return (charMap[this.code] === undef) ? charMap[this.code] = this : charMap[this.code]; + }; + + Char.prototype.toString = function() { + return String.fromCharCode(this.code); + }; + + Char.prototype.valueOf = function() { + return this.code; + }; + + return Char; +}({})); + +},{}],12:[function(require,module,exports){ +/** +* A HashMap stores a collection of objects, each referenced by a key. This is similar to an Array, only +* instead of accessing elements with a numeric index, a String is used. (If you are familiar with +* associative arrays from other languages, this is the same idea.) +* +* @param {int} initialCapacity defines the initial capacity of the map, it's 16 by default +* @param {float} loadFactor the load factor for the map, the default is 0.75 +* @param {Map} m gives the new HashMap the same mappings as this Map +*/ +module.exports = function(options) { + var virtHashCode = options.virtHashCode, + virtEquals = options.virtEquals; + + /** + * @member HashMap + * A HashMap stores a collection of objects, each referenced by a key. This is similar to an Array, only + * instead of accessing elements with a numeric index, a String is used. (If you are familiar with + * associative arrays from other languages, this is the same idea.) + * + * @param {int} initialCapacity defines the initial capacity of the map, it's 16 by default + * @param {float} loadFactor the load factor for the map, the default is 0.75 + * @param {Map} m gives the new HashMap the same mappings as this Map + */ + function HashMap() { + if (arguments.length === 1 && arguments[0] instanceof HashMap) { + return arguments[0].clone(); + } + + var initialCapacity = arguments.length > 0 ? arguments[0] : 16; + var loadFactor = arguments.length > 1 ? arguments[1] : 0.75; + var buckets = []; + buckets.length = initialCapacity; + var count = 0; + var hashMap = this; + + function getBucketIndex(key) { + var index = virtHashCode(key) % buckets.length; + return index < 0 ? buckets.length + index : index; + } + function ensureLoad() { + if (count <= loadFactor * buckets.length) { + return; + } + var allEntries = []; + for (var i = 0; i < buckets.length; ++i) { + if (buckets[i] !== undefined) { + allEntries = allEntries.concat(buckets[i]); + } + } + var newBucketsLength = buckets.length * 2; + buckets = []; + buckets.length = newBucketsLength; + for (var j = 0; j < allEntries.length; ++j) { + var index = getBucketIndex(allEntries[j].key); + var bucket = buckets[index]; + if (bucket === undefined) { + buckets[index] = bucket = []; + } + bucket.push(allEntries[j]); + } + } + + function Iterator(conversion, removeItem) { + var bucketIndex = 0; + var itemIndex = -1; + var endOfBuckets = false; + var currentItem; + + function findNext() { + while (!endOfBuckets) { + ++itemIndex; + if (bucketIndex >= buckets.length) { + endOfBuckets = true; + } else if (buckets[bucketIndex] === undefined || itemIndex >= buckets[bucketIndex].length) { + itemIndex = -1; + ++bucketIndex; + } else { + return; + } + } + } + + /* + * @member Iterator + * Checks if the Iterator has more items + */ + this.hasNext = function() { + return !endOfBuckets; + }; + + /* + * @member Iterator + * Return the next Item + */ + this.next = function() { + currentItem = conversion(buckets[bucketIndex][itemIndex]); + findNext(); + return currentItem; + }; + + /* + * @member Iterator + * Remove the current item + */ + this.remove = function() { + if (currentItem !== undefined) { + removeItem(currentItem); + --itemIndex; + findNext(); + } + }; + + findNext(); + } + + function Set(conversion, isIn, removeItem) { + this.clear = function() { + hashMap.clear(); + }; + + this.contains = function(o) { + return isIn(o); + }; + + this.containsAll = function(o) { + var it = o.iterator(); + while (it.hasNext()) { + if (!this.contains(it.next())) { + return false; + } + } + return true; + }; + + this.isEmpty = function() { + return hashMap.isEmpty(); + }; + + this.iterator = function() { + return new Iterator(conversion, removeItem); + }; + + this.remove = function(o) { + if (this.contains(o)) { + removeItem(o); + return true; + } + return false; + }; + + this.removeAll = function(c) { + var it = c.iterator(); + var changed = false; + while (it.hasNext()) { + var item = it.next(); + if (this.contains(item)) { + removeItem(item); + changed = true; + } + } + return true; + }; + + this.retainAll = function(c) { + var it = this.iterator(); + var toRemove = []; + while (it.hasNext()) { + var entry = it.next(); + if (!c.contains(entry)) { + toRemove.push(entry); + } + } + for (var i = 0; i < toRemove.length; ++i) { + removeItem(toRemove[i]); + } + return toRemove.length > 0; + }; + + this.size = function() { + return hashMap.size(); + }; + + this.toArray = function() { + var result = []; + var it = this.iterator(); + while (it.hasNext()) { + result.push(it.next()); + } + return result; + }; + } + + function Entry(pair) { + this._isIn = function(map) { + return map === hashMap && (pair.removed === undefined); + }; + + this.equals = function(o) { + return virtEquals(pair.key, o.getKey()); + }; + + this.getKey = function() { + return pair.key; + }; + + this.getValue = function() { + return pair.value; + }; + + this.hashCode = function(o) { + return virtHashCode(pair.key); + }; + + this.setValue = function(value) { + var old = pair.value; + pair.value = value; + return old; + }; + } + + this.clear = function() { + count = 0; + buckets = []; + buckets.length = initialCapacity; + }; + + this.clone = function() { + var map = new HashMap(); + map.putAll(this); + return map; + }; + + this.containsKey = function(key) { + var index = getBucketIndex(key); + var bucket = buckets[index]; + if (bucket === undefined) { + return false; + } + for (var i = 0; i < bucket.length; ++i) { + if (virtEquals(bucket[i].key, key)) { + return true; + } + } + return false; + }; + + this.containsValue = function(value) { + for (var i = 0; i < buckets.length; ++i) { + var bucket = buckets[i]; + if (bucket === undefined) { + continue; + } + for (var j = 0; j < bucket.length; ++j) { + if (virtEquals(bucket[j].value, value)) { + return true; + } + } + } + return false; + }; + + this.entrySet = function() { + return new Set( + + function(pair) { + return new Entry(pair); + }, + + function(pair) { + return (pair instanceof Entry) && pair._isIn(hashMap); + }, + + function(pair) { + return hashMap.remove(pair.getKey()); + }); + }; + + this.get = function(key) { + var index = getBucketIndex(key); + var bucket = buckets[index]; + if (bucket === undefined) { + return null; + } + for (var i = 0; i < bucket.length; ++i) { + if (virtEquals(bucket[i].key, key)) { + return bucket[i].value; + } + } + return null; + }; + + this.isEmpty = function() { + return count === 0; + }; + + this.keySet = function() { + return new Set( + // get key from pair + function(pair) { + return pair.key; + }, + // is-in test + function(key) { + return hashMap.containsKey(key); + }, + // remove from hashmap by key + function(key) { + return hashMap.remove(key); + } + ); + }; + + this.values = function() { + return new Set( + // get value from pair + function(pair) { + return pair.value; + }, + // is-in test + function(value) { + return hashMap.containsValue(value); + }, + // remove from hashmap by value + function(value) { + return hashMap.removeByValue(value); + } + ); + }; + + this.put = function(key, value) { + var index = getBucketIndex(key); + var bucket = buckets[index]; + if (bucket === undefined) { + ++count; + buckets[index] = [{ + key: key, + value: value + }]; + ensureLoad(); + return null; + } + for (var i = 0; i < bucket.length; ++i) { + if (virtEquals(bucket[i].key, key)) { + var previous = bucket[i].value; + bucket[i].value = value; + return previous; + } + } + ++count; + bucket.push({ + key: key, + value: value + }); + ensureLoad(); + return null; + }; + + this.putAll = function(m) { + var it = m.entrySet().iterator(); + while (it.hasNext()) { + var entry = it.next(); + this.put(entry.getKey(), entry.getValue()); + } + }; + + this.remove = function(key) { + var index = getBucketIndex(key); + var bucket = buckets[index]; + if (bucket === undefined) { + return null; + } + for (var i = 0; i < bucket.length; ++i) { + if (virtEquals(bucket[i].key, key)) { + --count; + var previous = bucket[i].value; + bucket[i].removed = true; + if (bucket.length > 1) { + bucket.splice(i, 1); + } else { + buckets[index] = undefined; + } + return previous; + } + } + return null; + }; + + this.removeByValue = function(value) { + var bucket, i, ilen, pair; + for (bucket in buckets) { + if (buckets.hasOwnProperty(bucket)) { + for (i = 0, ilen = buckets[bucket].length; i < ilen; i++) { + pair = buckets[bucket][i]; + // removal on values is based on identity, not equality + if (pair.value === value) { + buckets[bucket].splice(i, 1); + return true; + } + } + } + } + return false; + }; + + this.size = function() { + return count; + }; + } + + return HashMap; +}; + +},{}],13:[function(require,module,exports){ +// module export +module.exports = function(options,undef) { + var window = options.Browser.window, + document = options.Browser.document, + noop = options.noop; + + /** + * [internal function] computeFontMetrics() calculates various metrics for text + * placement. Currently this function computes the ascent, descent and leading + * (from "lead", used for vertical space) values for the currently active font. + */ + function computeFontMetrics(pfont) { + var emQuad = 250, + correctionFactor = pfont.size / emQuad, + canvas = document.createElement("canvas"); + canvas.width = 2*emQuad; + canvas.height = 2*emQuad; + canvas.style.opacity = 0; + var cfmFont = pfont.getCSSDefinition(emQuad+"px", "normal"), + ctx = canvas.getContext("2d"); + ctx.font = cfmFont; + + // Size the canvas using a string with common max-ascent and max-descent letters. + // Changing the canvas dimensions resets the context, so we must reset the font. + var protrusions = "dbflkhyjqpg"; + canvas.width = ctx.measureText(protrusions).width; + ctx.font = cfmFont; + + // for text lead values, we meaure a multiline text container. + var leadDiv = document.createElement("div"); + leadDiv.style.position = "absolute"; + leadDiv.style.opacity = 0; + leadDiv.style.fontFamily = '"' + pfont.name + '"'; + leadDiv.style.fontSize = emQuad + "px"; + leadDiv.innerHTML = protrusions + "<br/>" + protrusions; + document.body.appendChild(leadDiv); + + var w = canvas.width, + h = canvas.height, + baseline = h/2; + + // Set all canvas pixeldata values to 255, with all the content + // data being 0. This lets us scan for data[i] != 255. + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, w, h); + ctx.fillStyle = "black"; + ctx.fillText(protrusions, 0, baseline); + var pixelData = ctx.getImageData(0, 0, w, h).data; + + // canvas pixel data is w*4 by h*4, because R, G, B and A are separate, + // consecutive values in the array, rather than stored as 32 bit ints. + var i = 0, + w4 = w * 4, + len = pixelData.length; + + // Finding the ascent uses a normal, forward scanline + while (++i < len && pixelData[i] === 255) { + noop(); + } + var ascent = Math.round(i / w4); + + // Finding the descent uses a reverse scanline + i = len - 1; + while (--i > 0 && pixelData[i] === 255) { + noop(); + } + var descent = Math.round(i / w4); + + // set font metrics + pfont.ascent = correctionFactor * (baseline - ascent); + pfont.descent = correctionFactor * (descent - baseline); + + // Then we try to get the real value from the browser + if (document.defaultView.getComputedStyle) { + var leadDivHeight = document.defaultView.getComputedStyle(leadDiv,null).getPropertyValue("height"); + leadDivHeight = correctionFactor * leadDivHeight.replace("px",""); + if (leadDivHeight >= pfont.size * 2) { + pfont.leading = Math.round(leadDivHeight/2); + } + } + document.body.removeChild(leadDiv); + + // if we're caching, cache the context used for this pfont + if (pfont.caching) { + return ctx; + } + } + + /** + * Constructor for a system or from-file (non-SVG) font. + */ + function PFont(name, size) { + // according to the P5 API, new PFont() is legal (albeit completely useless) + if (name === undef) { + name = ""; + } + this.name = name; + if (size === undef) { + size = 0; + } + this.size = size; + this.glyph = false; + this.ascent = 0; + this.descent = 0; + // For leading, the "safe" value uses the standard TEX ratio + this.leading = 1.2 * size; + + // Note that an italic, bold font must used "... Bold Italic" + // in P5. "... Italic Bold" is treated as normal/normal. + var illegalIndicator = name.indexOf(" Italic Bold"); + if (illegalIndicator !== -1) { + name = name.substring(0, illegalIndicator); + } + + // determine font style + this.style = "normal"; + var italicsIndicator = name.indexOf(" Italic"); + if (italicsIndicator !== -1) { + name = name.substring(0, italicsIndicator); + this.style = "italic"; + } + + // determine font weight + this.weight = "normal"; + var boldIndicator = name.indexOf(" Bold"); + if (boldIndicator !== -1) { + name = name.substring(0, boldIndicator); + this.weight = "bold"; + } + + // determine font-family name + this.family = "sans-serif"; + if (name !== undef) { + switch(name) { + case "sans-serif": + case "serif": + case "monospace": + case "fantasy": + case "cursive": + this.family = name; + break; + default: + this.family = '"' + name + '", sans-serif'; + break; + } + } + // Calculate the ascent/descent/leading value based on + // how the browser renders this font. + this.context2d = computeFontMetrics(this); + this.css = this.getCSSDefinition(); + if (this.context2d) { + this.context2d.font = this.css; + } + } + + /** + * regulates whether or not we're caching the canvas + * 2d context for quick text width computation. + */ + PFont.prototype.caching = true; + + /** + * This function generates the CSS "font" string for this PFont + */ + PFont.prototype.getCSSDefinition = function(fontSize, lineHeight) { + if(fontSize===undef) { + fontSize = this.size + "px"; + } + if(lineHeight===undef) { + lineHeight = this.leading + "px"; + } + // CSS "font" definition: font-style font-variant font-weight font-size/line-height font-family + var components = [this.style, "normal", this.weight, fontSize + "/" + lineHeight, this.family]; + return components.join(" "); + }; + + /** + * Rely on the cached context2d measureText function. + */ + PFont.prototype.measureTextWidth = function(string) { + return this.context2d.measureText(string).width; + }; + + /** + * FALLBACK FUNCTION -- replaces Pfont.prototype.measureTextWidth + * when the font cache becomes too large. This contructs a new + * canvas 2d context object for calling measureText on. + */ + PFont.prototype.measureTextWidthFallback = function(string) { + var canvas = document.createElement("canvas"), + ctx = canvas.getContext("2d"); + ctx.font = this.css; + return ctx.measureText(string).width; + }; + + /** + * Global "loaded fonts" list, internal to PFont + */ + PFont.PFontCache = { length: 0 }; + + /** + * This function acts as single access point for getting and caching + * fonts across all sketches handled by an instance of Processing.js + */ + PFont.get = function(fontName, fontSize) { + // round fontSize to one decimal point + fontSize = ((fontSize*10)+0.5|0)/10; + var cache = PFont.PFontCache, + idx = fontName+"/"+fontSize; + if (!cache[idx]) { + cache[idx] = new PFont(fontName, fontSize); + cache.length++; + + // FALLBACK FUNCTIONALITY 1: + // If the cache has become large, switch over from full caching + // to caching only the static metrics for each new font request. + if (cache.length === 50) { + PFont.prototype.measureTextWidth = PFont.prototype.measureTextWidthFallback; + PFont.prototype.caching = false; + // clear contexts stored for each cached font + var entry; + for (entry in cache) { + if (entry !== "length") { + cache[entry].context2d = null; + } + } + return new PFont(fontName, fontSize); + } + + // FALLBACK FUNCTIONALITY 2: + // If the cache has become too large, switch off font caching entirely. + if (cache.length === 400) { + PFont.PFontCache = {}; + PFont.get = PFont.getFallback; + return new PFont(fontName, fontSize); + } + } + return cache[idx]; + }; + + /** + * FALLBACK FUNCTION -- replaces PFont.get when the font cache + * becomes too large. This function bypasses font caching entirely. + */ + PFont.getFallback = function(fontName, fontSize) { + return new PFont(fontName, fontSize); + }; + + /** + * Lists all standard fonts. Due to browser limitations, this list is + * not the system font list, like in P5, but the CSS "genre" list. + */ + PFont.list = function() { + return ["sans-serif", "serif", "monospace", "fantasy", "cursive"]; + }; + + /** + * Loading external fonts through @font-face rules is handled by PFont, + * to ensure fonts loaded in this way are globally available. + */ + PFont.preloading = { + // template element used to compare font sizes + template: {}, + // indicates whether or not the reference tiny font has been loaded + initialized: false, + // load the reference tiny font via a css @font-face rule + initialize: function() { + var generateTinyFont = function() { + var encoded = "#E3KAI2wAgT1MvMg7Eo3VmNtYX7ABi3CxnbHlm" + + "7Abw3kaGVhZ7ACs3OGhoZWE7A53CRobXR47AY3" + + "AGbG9jYQ7G03Bm1heH7ABC3CBuYW1l7Ae3AgcG" + + "9zd7AI3AE#B3AQ2kgTY18PPPUACwAg3ALSRoo3" + + "#yld0xg32QAB77#E777773B#E3C#I#Q77773E#" + + "Q7777777772CMAIw7AB77732B#M#Q3wAB#g3B#" + + "E#E2BB//82BB////w#B7#gAEg3E77x2B32B#E#" + + "Q#MTcBAQ32gAe#M#QQJ#E32M#QQJ#I#g32Q77#"; + var expand = function(input) { + return "AAAAAAAA".substr(~~input ? 7-input : 6); + }; + return encoded.replace(/[#237]/g, expand); + }; + var fontface = document.createElement("style"); + fontface.setAttribute("type","text/css"); + fontface.innerHTML = "@font-face {\n" + + ' font-family: "PjsEmptyFont";' + "\n" + + " src: url('data:application/x-font-ttf;base64,"+generateTinyFont()+"')\n" + + " format('truetype');\n" + + "}"; + document.head.appendChild(fontface); + + // set up the template element + var element = document.createElement("span"); + element.style.cssText = 'position: absolute; top: -1000; left: 0; opacity: 0; font-family: "PjsEmptyFont", fantasy;'; + element.innerHTML = "AAAAAAAA"; + document.body.appendChild(element); + this.template = element; + + this.initialized = true; + }, + // Shorthand function to get the computed width for an element. + getElementWidth: function(element) { + return document.defaultView.getComputedStyle(element,"").getPropertyValue("width"); + }, + // time taken so far in attempting to load a font + timeAttempted: 0, + // returns false if no fonts are pending load, or true otherwise. + pending: function(intervallength) { + if (!this.initialized) { + this.initialize(); + } + var element, + computedWidthFont, + computedWidthRef = this.getElementWidth(this.template); + for (var i = 0; i < this.fontList.length; i++) { + // compares size of text in pixels. if equal, custom font is not yet loaded + element = this.fontList[i]; + computedWidthFont = this.getElementWidth(element); + if (this.timeAttempted < 4000 && computedWidthFont === computedWidthRef) { + this.timeAttempted += intervallength; + return true; + } else { + document.body.removeChild(element); + this.fontList.splice(i--, 1); + this.timeAttempted = 0; + } + } + // if there are no more fonts to load, pending is false + if (this.fontList.length === 0) { + return false; + } + // We should have already returned before getting here. + // But, if we do get here, length!=0 so fonts are pending. + return true; + }, + // fontList contains elements to compare font sizes against a template + fontList: [], + // addedList contains the fontnames of all the fonts loaded via @font-face + addedList: {}, + // adds a font to the font cache + // creates an element using the font, to start loading the font, + // and compare against a default font to see if the custom font is loaded + add: function(fontSrc) { + if (!this.initialized) { + this.initialize(); + } + // fontSrc can be a string or a javascript object + // acceptable fonts are .ttf, .otf, and data uri + var fontName = (typeof fontSrc === 'object' ? fontSrc.fontFace : fontSrc), + fontUrl = (typeof fontSrc === 'object' ? fontSrc.url : fontSrc); + + // check whether we already created the @font-face rule for this font + if (this.addedList[fontName]) { + return; + } + + // if we didn't, create the @font-face rule + var style = document.createElement("style"); + style.setAttribute("type","text/css"); + style.innerHTML = "@font-face{\n font-family: '" + fontName + "';\n src: url('" + fontUrl + "');\n}\n"; + document.head.appendChild(style); + this.addedList[fontName] = true; + + // also create the element to load and compare the new font + var element = document.createElement("span"); + element.style.cssText = "position: absolute; top: 0; left: 0; opacity: 0;"; + element.style.fontFamily = '"' + fontName + '", "PjsEmptyFont", fantasy'; + element.innerHTML = "AAAAAAAA"; + document.body.appendChild(element); + this.fontList.push(element); + } + }; + + return PFont; +}; +},{}],14:[function(require,module,exports){ +module.exports = function(options, undef) { + + // FIXME: hack + var p = options.p; + + /** + * PMatrix2D is a 3x2 affine matrix implementation. The constructor accepts another PMatrix2D or a list of six float elements. + * If no parameters are provided the matrix is set to the identity matrix. + * + * @param {PMatrix2D} matrix the initial matrix to set to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the second element of the matrix + * @param {float} m02 the third element of the matrix + * @param {float} m10 the fourth element of the matrix + * @param {float} m11 the fifth element of the matrix + * @param {float} m12 the sixth element of the matrix + */ + var PMatrix2D = function() { + if (arguments.length === 0) { + this.reset(); + } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { + this.set(arguments[0].array()); + } else if (arguments.length === 6) { + this.set(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); + } + }; + + /** + * PMatrix2D methods + */ + PMatrix2D.prototype = { + /** + * @member PMatrix2D + * The set() function sets the matrix elements. The function accepts either another PMatrix2D, an array of elements, or a list of six floats. + * + * @param {PMatrix2D} matrix the matrix to set this matrix to + * @param {float[]} elements an array of elements to set this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the third element of the matrix + * @param {float} m10 the fourth element of the matrix + * @param {float} m11 the fith element of the matrix + * @param {float} m12 the sixth element of the matrix + */ + set: function() { + if (arguments.length === 6) { + var a = arguments; + this.set([a[0], a[1], a[2], + a[3], a[4], a[5]]); + } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { + this.elements = arguments[0].array(); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + this.elements = arguments[0].slice(); + } + }, + /** + * @member PMatrix2D + * The get() function returns a copy of this PMatrix2D. + * + * @return {PMatrix2D} a copy of this PMatrix2D + */ + get: function() { + var outgoing = new PMatrix2D(); + outgoing.set(this.elements); + return outgoing; + }, + /** + * @member PMatrix2D + * The reset() function sets this PMatrix2D to the identity matrix. + */ + reset: function() { + this.set([1, 0, 0, 0, 1, 0]); + }, + /** + * @member PMatrix2D + * The array() function returns a copy of the element values. + * @addon + * + * @return {float[]} returns a copy of the element values + */ + array: function array() { + return this.elements.slice(); + }, + /** + * @member PMatrix2D + * The translate() function translates this matrix by moving the current coordinates to the location specified by tx and ty. + * + * @param {float} tx the x-axis coordinate to move to + * @param {float} ty the y-axis coordinate to move to + */ + translate: function(tx, ty) { + this.elements[2] = tx * this.elements[0] + ty * this.elements[1] + this.elements[2]; + this.elements[5] = tx * this.elements[3] + ty * this.elements[4] + this.elements[5]; + }, + /** + * @member PMatrix2D + * The invTranslate() function translates this matrix by moving the current coordinates to the negative location specified by tx and ty. + * + * @param {float} tx the x-axis coordinate to move to + * @param {float} ty the y-axis coordinate to move to + */ + invTranslate: function(tx, ty) { + this.translate(-tx, -ty); + }, + /** + * @member PMatrix2D + * The transpose() function is not used in processingjs. + */ + transpose: function() { + // Does nothing in Processing. + }, + /** + * @member PMatrix2D + * The mult() function multiplied this matrix. + * If two array elements are passed in the function will multiply a two element vector against this matrix. + * If target is null or not length four, a new float array will be returned. + * The values for vec and target can be the same (though that's less efficient). + * If two PVectors are passed in the function multiply the x and y coordinates of a PVector against this matrix. + * + * @param {PVector} source, target the PVectors used to multiply this matrix + * @param {float[]} source, target the arrays used to multiply this matrix + * + * @return {PVector|float[]} returns a PVector or an array representing the new matrix + */ + mult: function(source, target) { + var x, y; + if (source instanceof PVector) { + x = source.x; + y = source.y; + if (!target) { + target = new PVector(); + } + } else if (source instanceof Array) { + x = source[0]; + y = source[1]; + if (!target) { + target = []; + } + } + if (target instanceof Array) { + target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2]; + target[1] = this.elements[3] * x + this.elements[4] * y + this.elements[5]; + } else if (target instanceof PVector) { + target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2]; + target.y = this.elements[3] * x + this.elements[4] * y + this.elements[5]; + target.z = 0; + } + return target; + }, + /** + * @member PMatrix2D + * The multX() function calculates the x component of a vector from a transformation. + * + * @param {float} x the x component of the vector being transformed + * @param {float} y the y component of the vector being transformed + * + * @return {float} returnes the result of the calculation + */ + multX: function(x, y) { + return (x * this.elements[0] + y * this.elements[1] + this.elements[2]); + }, + /** + * @member PMatrix2D + * The multY() function calculates the y component of a vector from a transformation. + * + * @param {float} x the x component of the vector being transformed + * @param {float} y the y component of the vector being transformed + * + * @return {float} returnes the result of the calculation + */ + multY: function(x, y) { + return (x * this.elements[3] + y * this.elements[4] + this.elements[5]); + }, + /** + * @member PMatrix2D + * The skewX() function skews the matrix along the x-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> 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 <b>radians()</b> function. + * + * @param {float} angle angle of skew specified in radians + */ + skewY: function(angle) { + this.apply(1, 0, 1, 0, angle, 0); + }, + /** + * @member PMatrix2D + * The shearX() function shears the matrix along the x-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. + * + * @param {float} angle angle of skew specified in radians + */ + shearX: function(angle) { + this.apply(1, 0, 1, Math.tan(angle) , 0, 0); + }, + /** + * @member PMatrix2D + * The shearY() function shears the matrix along the y-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. + * + * @param {float} angle angle of skew specified in radians + */ + shearY: function(angle) { + this.apply(1, 0, 1, 0, Math.tan(angle), 0); + }, + /** + * @member PMatrix2D + * The determinant() function calvculates the determinant of this matrix. + * + * @return {float} the determinant of the matrix + */ + determinant: function() { + return (this.elements[0] * this.elements[4] - this.elements[1] * this.elements[3]); + }, + /** + * @member PMatrix2D + * The invert() function inverts this matrix + * + * @return {boolean} true if successful + */ + invert: function() { + var d = this.determinant(); + if (Math.abs( d ) > PConstants.MIN_INT) { + var old00 = this.elements[0]; + var old01 = this.elements[1]; + var old02 = this.elements[2]; + var old10 = this.elements[3]; + var old11 = this.elements[4]; + var old12 = this.elements[5]; + this.elements[0] = old11 / d; + this.elements[3] = -old10 / d; + this.elements[1] = -old01 / d; + this.elements[4] = old00 / d; + this.elements[2] = (old01 * old12 - old11 * old02) / d; + this.elements[5] = (old10 * old02 - old00 * old12) / d; + return true; + } + return false; + }, + /** + * @member PMatrix2D + * The scale() function increases or decreases the size of a shape by expanding and contracting vertices. When only one parameter is specified scale will occur in all dimensions. + * This is equivalent to a two parameter call. + * + * @param {float} sx the amount to scale on the x-axis + * @param {float} sy the amount to scale on the y-axis + */ + scale: function(sx, sy) { + if (sx && !sy) { + sy = sx; + } + if (sx && sy) { + this.elements[0] *= sx; + this.elements[1] *= sy; + this.elements[3] *= sx; + this.elements[4] *= sy; + } + }, + /** + * @member PMatrix2D + * The invScale() function decreases or increases the size of a shape by contracting and expanding vertices. When only one parameter is specified scale will occur in all dimensions. + * This is equivalent to a two parameter call. + * + * @param {float} sx the amount to scale on the x-axis + * @param {float} sy the amount to scale on the y-axis + */ + invScale: function(sx, sy) { + if (sx && !sy) { + sy = sx; + } + this.scale(1 / sx, 1 / sy); + }, + /** + * @member PMatrix2D + * The apply() function multiplies the current matrix by the one specified through the parameters. Note that either a PMatrix2D or a list of floats can be passed in. + * + * @param {PMatrix2D} matrix the matrix to apply this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the third element of the matrix + * @param {float} m10 the fourth element of the matrix + * @param {float} m11 the fith element of the matrix + * @param {float} m12 the sixth element of the matrix + */ + apply: function() { + var source; + if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { + source = arguments[0].array(); + } else if (arguments.length === 6) { + source = Array.prototype.slice.call(arguments); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + source = arguments[0]; + } + + var result = [0, 0, this.elements[2], + 0, 0, this.elements[5]]; + var e = 0; + for (var row = 0; row < 2; row++) { + for (var col = 0; col < 3; col++, e++) { + result[e] += this.elements[row * 3 + 0] * source[col + 0] + + this.elements[row * 3 + 1] * source[col + 3]; + } + } + this.elements = result.slice(); + }, + /** + * @member PMatrix2D + * The preApply() function applies another matrix to the left of this one. Note that either a PMatrix2D or elements of a matrix can be passed in. + * + * @param {PMatrix2D} matrix the matrix to apply this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the third element of the matrix + * @param {float} m10 the fourth element of the matrix + * @param {float} m11 the fith element of the matrix + * @param {float} m12 the sixth element of the matrix + */ + preApply: function() { + var source; + if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { + source = arguments[0].array(); + } else if (arguments.length === 6) { + source = Array.prototype.slice.call(arguments); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + source = arguments[0]; + } + var result = [0, 0, source[2], + 0, 0, source[5]]; + result[2] = source[2] + this.elements[2] * source[0] + this.elements[5] * source[1]; + result[5] = source[5] + this.elements[2] * source[3] + this.elements[5] * source[4]; + result[0] = this.elements[0] * source[0] + this.elements[3] * source[1]; + result[3] = this.elements[0] * source[3] + this.elements[3] * source[4]; + result[1] = this.elements[1] * source[0] + this.elements[4] * source[1]; + result[4] = this.elements[1] * source[3] + this.elements[4] * source[4]; + this.elements = result.slice(); + }, + /** + * @member PMatrix2D + * The rotate() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotate: function(angle) { + var c = Math.cos(angle); + var s = Math.sin(angle); + var temp1 = this.elements[0]; + var temp2 = this.elements[1]; + this.elements[0] = c * temp1 + s * temp2; + this.elements[1] = -s * temp1 + c * temp2; + temp1 = this.elements[3]; + temp2 = this.elements[4]; + this.elements[3] = c * temp1 + s * temp2; + this.elements[4] = -s * temp1 + c * temp2; + }, + /** + * @member PMatrix2D + * The rotateZ() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotateZ: function(angle) { + this.rotate(angle); + }, + /** + * @member PMatrix2D + * The invRotateZ() function rotates the matrix in opposite direction. + * + * @param {float} angle the angle of rotation in radiants + */ + invRotateZ: function(angle) { + this.rotateZ(angle - Math.PI); + }, + /** + * @member PMatrix2D + * The print() function prints out the elements of this matrix + */ + print: function() { + var digits = printMatrixHelper(this.elements); + var output = "" + p.nfs(this.elements[0], digits, 4) + " " + + p.nfs(this.elements[1], digits, 4) + " " + + p.nfs(this.elements[2], digits, 4) + "\n" + + p.nfs(this.elements[3], digits, 4) + " " + + p.nfs(this.elements[4], digits, 4) + " " + + p.nfs(this.elements[5], digits, 4) + "\n\n"; + p.println(output); + } + }; + + return PMatrix2D; +}; + +},{}],15:[function(require,module,exports){ +module.exports = function(options, undef) { + + // FIXME: hack + var p = options.p; + + /** + * PMatrix3D is a 4x4 matrix implementation. The constructor accepts another PMatrix3D or a list of six or sixteen float elements. + * If no parameters are provided the matrix is set to the identity matrix. + */ + var PMatrix3D = function() { + // When a matrix is created, it is set to an identity matrix + this.reset(); + }; + + /** + * PMatrix3D methods + */ + PMatrix3D.prototype = { + /** + * @member PMatrix2D + * The set() function sets the matrix elements. The function accepts either another PMatrix3D, an array of elements, or a list of six or sixteen floats. + * + * @param {PMatrix3D} matrix the initial matrix to set to + * @param {float[]} elements an array of elements to set this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the second element of the matrix + * @param {float} m02 the third element of the matrix + * @param {float} m03 the fourth element of the matrix + * @param {float} m10 the fifth element of the matrix + * @param {float} m11 the sixth element of the matrix + * @param {float} m12 the seventh element of the matrix + * @param {float} m13 the eight element of the matrix + * @param {float} m20 the nineth element of the matrix + * @param {float} m21 the tenth element of the matrix + * @param {float} m22 the eleventh element of the matrix + * @param {float} m23 the twelveth element of the matrix + * @param {float} m30 the thirteenth element of the matrix + * @param {float} m31 the fourtheenth element of the matrix + * @param {float} m32 the fivetheenth element of the matrix + * @param {float} m33 the sixteenth element of the matrix + */ + set: function() { + if (arguments.length === 16) { + this.elements = Array.prototype.slice.call(arguments); + } else if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { + this.elements = arguments[0].array(); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + this.elements = arguments[0].slice(); + } + }, + /** + * @member PMatrix3D + * The get() function returns a copy of this PMatrix3D. + * + * @return {PMatrix3D} a copy of this PMatrix3D + */ + get: function() { + var outgoing = new PMatrix3D(); + outgoing.set(this.elements); + return outgoing; + }, + /** + * @member PMatrix3D + * The reset() function sets this PMatrix3D to the identity matrix. + */ + reset: function() { + this.elements = [1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1]; + }, + /** + * @member PMatrix3D + * The array() function returns a copy of the element values. + * @addon + * + * @return {float[]} returns a copy of the element values + */ + array: function array() { + return this.elements.slice(); + }, + /** + * @member PMatrix3D + * The translate() function translates this matrix by moving the current coordinates to the location specified by tx, ty, and tz. + * + * @param {float} tx the x-axis coordinate to move to + * @param {float} ty the y-axis coordinate to move to + * @param {float} tz the z-axis coordinate to move to + */ + translate: function(tx, ty, tz) { + if (tz === undef) { + tz = 0; + } + + this.elements[3] += tx * this.elements[0] + ty * this.elements[1] + tz * this.elements[2]; + this.elements[7] += tx * this.elements[4] + ty * this.elements[5] + tz * this.elements[6]; + this.elements[11] += tx * this.elements[8] + ty * this.elements[9] + tz * this.elements[10]; + this.elements[15] += tx * this.elements[12] + ty * this.elements[13] + tz * this.elements[14]; + }, + /** + * @member PMatrix3D + * The transpose() function transpose this matrix. + */ + transpose: function() { + var temp = this.elements[4]; + this.elements[4] = this.elements[1]; + this.elements[1] = temp; + + temp = this.elements[8]; + this.elements[8] = this.elements[2]; + this.elements[2] = temp; + + temp = this.elements[6]; + this.elements[6] = this.elements[9]; + this.elements[9] = temp; + + temp = this.elements[3]; + this.elements[3] = this.elements[12]; + this.elements[12] = temp; + + temp = this.elements[7]; + this.elements[7] = this.elements[13]; + this.elements[13] = temp; + + temp = this.elements[11]; + this.elements[11] = this.elements[14]; + this.elements[14] = temp; + }, + /** + * @member PMatrix3D + * The mult() function multiplied this matrix. + * If two array elements are passed in the function will multiply a two element vector against this matrix. + * If target is null or not length four, a new float array will be returned. + * The values for vec and target can be the same (though that's less efficient). + * If two PVectors are passed in the function multiply the x and y coordinates of a PVector against this matrix. + * + * @param {PVector} source, target the PVectors used to multiply this matrix + * @param {float[]} source, target the arrays used to multiply this matrix + * + * @return {PVector|float[]} returns a PVector or an array representing the new matrix + */ + mult: function(source, target) { + var x, y, z, w; + if (source instanceof PVector) { + x = source.x; + y = source.y; + z = source.z; + w = 1; + if (!target) { + target = new PVector(); + } + } else if (source instanceof Array) { + x = source[0]; + y = source[1]; + z = source[2]; + w = source[3] || 1; + + if ( !target || (target.length !== 3 && target.length !== 4) ) { + target = [0, 0, 0]; + } + } + + if (target instanceof Array) { + if (target.length === 3) { + target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; + target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; + target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; + } else if (target.length === 4) { + target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w; + target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w; + target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w; + target[3] = this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w; + } + } + if (target instanceof PVector) { + target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; + target.y = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; + target.z = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; + } + return target; + }, + /** + * @member PMatrix3D + * The preApply() function applies another matrix to the left of this one. Note that either a PMatrix3D or elements of a matrix can be passed in. + * + * @param {PMatrix3D} matrix the matrix to apply this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the second element of the matrix + * @param {float} m02 the third element of the matrix + * @param {float} m03 the fourth element of the matrix + * @param {float} m10 the fifth element of the matrix + * @param {float} m11 the sixth element of the matrix + * @param {float} m12 the seventh element of the matrix + * @param {float} m13 the eight element of the matrix + * @param {float} m20 the nineth element of the matrix + * @param {float} m21 the tenth element of the matrix + * @param {float} m22 the eleventh element of the matrix + * @param {float} m23 the twelveth element of the matrix + * @param {float} m30 the thirteenth element of the matrix + * @param {float} m31 the fourtheenth element of the matrix + * @param {float} m32 the fivetheenth element of the matrix + * @param {float} m33 the sixteenth element of the matrix + */ + preApply: function() { + var source; + if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { + source = arguments[0].array(); + } else if (arguments.length === 16) { + source = Array.prototype.slice.call(arguments); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + source = arguments[0]; + } + + var result = [0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0]; + var e = 0; + for (var row = 0; row < 4; row++) { + for (var col = 0; col < 4; col++, e++) { + result[e] += this.elements[col + 0] * source[row * 4 + 0] + this.elements[col + 4] * + source[row * 4 + 1] + this.elements[col + 8] * source[row * 4 + 2] + + this.elements[col + 12] * source[row * 4 + 3]; + } + } + this.elements = result.slice(); + }, + /** + * @member PMatrix3D + * The apply() function multiplies the current matrix by the one specified through the parameters. Note that either a PMatrix3D or a list of floats can be passed in. + * + * @param {PMatrix3D} matrix the matrix to apply this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the second element of the matrix + * @param {float} m02 the third element of the matrix + * @param {float} m03 the fourth element of the matrix + * @param {float} m10 the fifth element of the matrix + * @param {float} m11 the sixth element of the matrix + * @param {float} m12 the seventh element of the matrix + * @param {float} m13 the eight element of the matrix + * @param {float} m20 the nineth element of the matrix + * @param {float} m21 the tenth element of the matrix + * @param {float} m22 the eleventh element of the matrix + * @param {float} m23 the twelveth element of the matrix + * @param {float} m30 the thirteenth element of the matrix + * @param {float} m31 the fourtheenth element of the matrix + * @param {float} m32 the fivetheenth element of the matrix + * @param {float} m33 the sixteenth element of the matrix + */ + apply: function() { + var source; + if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { + source = arguments[0].array(); + } else if (arguments.length === 16) { + source = Array.prototype.slice.call(arguments); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + source = arguments[0]; + } + + var result = [0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0]; + var e = 0; + for (var row = 0; row < 4; row++) { + for (var col = 0; col < 4; col++, e++) { + result[e] += this.elements[row * 4 + 0] * source[col + 0] + this.elements[row * 4 + 1] * + source[col + 4] + this.elements[row * 4 + 2] * source[col + 8] + + this.elements[row * 4 + 3] * source[col + 12]; + } + } + this.elements = result.slice(); + }, + /** + * @member PMatrix3D + * The rotate() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotate: function(angle, v0, v1, v2) { + if (!v1) { + this.rotateZ(angle); + } else { + // TODO should make sure this vector is normalized + var c = Math.cos(angle); + var s = Math.sin(angle); + var t = 1.0 - c; + + this.apply((t * v0 * v0) + c, + (t * v0 * v1) - (s * v2), + (t * v0 * v2) + (s * v1), + 0, + (t * v0 * v1) + (s * v2), + (t * v1 * v1) + c, + (t * v1 * v2) - (s * v0), + 0, + (t * v0 * v2) - (s * v1), + (t * v1 * v2) + (s * v0), + (t * v2 * v2) + c, + 0, + 0, 0, 0, 1); + } + }, + /** + * @member PMatrix3D + * The invApply() function applies the inverted matrix to this matrix. + * + * @param {float} m00 the first element of the matrix + * @param {float} m01 the second element of the matrix + * @param {float} m02 the third element of the matrix + * @param {float} m03 the fourth element of the matrix + * @param {float} m10 the fifth element of the matrix + * @param {float} m11 the sixth element of the matrix + * @param {float} m12 the seventh element of the matrix + * @param {float} m13 the eight element of the matrix + * @param {float} m20 the nineth element of the matrix + * @param {float} m21 the tenth element of the matrix + * @param {float} m22 the eleventh element of the matrix + * @param {float} m23 the twelveth element of the matrix + * @param {float} m30 the thirteenth element of the matrix + * @param {float} m31 the fourtheenth element of the matrix + * @param {float} m32 the fivetheenth element of the matrix + * @param {float} m33 the sixteenth element of the matrix + * + * @return {boolean} returns true if the operation was successful. + */ + invApply: function() { + if (inverseCopy === undef) { + inverseCopy = new PMatrix3D(); + } + var a = arguments; + inverseCopy.set(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], + a[9], a[10], a[11], a[12], a[13], a[14], a[15]); + + if (!inverseCopy.invert()) { + return false; + } + this.preApply(inverseCopy); + return true; + }, + /** + * @member PMatrix3D + * The rotateZ() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotateX: function(angle) { + var c = Math.cos(angle); + var s = Math.sin(angle); + this.apply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]); + }, + /** + * @member PMatrix3D + * The rotateY() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotateY: function(angle) { + var c = Math.cos(angle); + var s = Math.sin(angle); + this.apply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]); + }, + /** + * @member PMatrix3D + * The rotateZ() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotateZ: function(angle) { + var c = Math.cos(angle); + var s = Math.sin(angle); + this.apply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); + }, + /** + * @member PMatrix3D + * The scale() function increases or decreases the size of a matrix by expanding and contracting vertices. When only one parameter is specified scale will occur in all dimensions. + * This is equivalent to a three parameter call. + * + * @param {float} sx the amount to scale on the x-axis + * @param {float} sy the amount to scale on the y-axis + * @param {float} sz the amount to scale on the z-axis + */ + scale: function(sx, sy, sz) { + if (sx && !sy && !sz) { + sy = sz = sx; + } else if (sx && sy && !sz) { + sz = 1; + } + + if (sx && sy && sz) { + this.elements[0] *= sx; + this.elements[1] *= sy; + this.elements[2] *= sz; + this.elements[4] *= sx; + this.elements[5] *= sy; + this.elements[6] *= sz; + this.elements[8] *= sx; + this.elements[9] *= sy; + this.elements[10] *= sz; + this.elements[12] *= sx; + this.elements[13] *= sy; + this.elements[14] *= sz; + } + }, + /** + * @member PMatrix3D + * The skewX() function skews the matrix along the x-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> 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 <b>radians()</b> function. + * + * @param {float} angle angle of skew specified in radians + */ + skewY: function(angle) { + var t = Math.tan(angle); + this.apply(1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + }, + /** + * @member PMatrix3D + * The shearX() function shears the matrix along the x-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. + * + * @param {float} angle angle of shear specified in radians + */ + shearX: function(angle) { + var t = Math.tan(angle); + this.apply(1, t, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + }, + /** + * @member PMatrix3D + * The shearY() function shears the matrix along the y-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. + * + * @param {float} angle angle of shear specified in radians + */ + shearY: function(angle) { + var t = Math.tan(angle); + this.apply(1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + }, + multX: function(x, y, z, w) { + if (!z) { + return this.elements[0] * x + this.elements[1] * y + this.elements[3]; + } + if (!w) { + return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; + } + return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w; + }, + multY: function(x, y, z, w) { + if (!z) { + return this.elements[4] * x + this.elements[5] * y + this.elements[7]; + } + if (!w) { + return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; + } + return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w; + }, + multZ: function(x, y, z, w) { + if (!w) { + return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; + } + return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w; + }, + multW: function(x, y, z, w) { + if (!w) { + return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15]; + } + return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w; + }, + /** + * @member PMatrix3D + * The invert() function inverts this matrix + * + * @return {boolean} true if successful + */ + invert: function() { + var fA0 = this.elements[0] * this.elements[5] - this.elements[1] * this.elements[4]; + var fA1 = this.elements[0] * this.elements[6] - this.elements[2] * this.elements[4]; + var fA2 = this.elements[0] * this.elements[7] - this.elements[3] * this.elements[4]; + var fA3 = this.elements[1] * this.elements[6] - this.elements[2] * this.elements[5]; + var fA4 = this.elements[1] * this.elements[7] - this.elements[3] * this.elements[5]; + var fA5 = this.elements[2] * this.elements[7] - this.elements[3] * this.elements[6]; + var fB0 = this.elements[8] * this.elements[13] - this.elements[9] * this.elements[12]; + var fB1 = this.elements[8] * this.elements[14] - this.elements[10] * this.elements[12]; + var fB2 = this.elements[8] * this.elements[15] - this.elements[11] * this.elements[12]; + var fB3 = this.elements[9] * this.elements[14] - this.elements[10] * this.elements[13]; + var fB4 = this.elements[9] * this.elements[15] - this.elements[11] * this.elements[13]; + var fB5 = this.elements[10] * this.elements[15] - this.elements[11] * this.elements[14]; + + // Determinant + var fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; + + // Account for a very small value + // return false if not successful. + if (Math.abs(fDet) <= 1e-9) { + return false; + } + + var kInv = []; + kInv[0] = +this.elements[5] * fB5 - this.elements[6] * fB4 + this.elements[7] * fB3; + kInv[4] = -this.elements[4] * fB5 + this.elements[6] * fB2 - this.elements[7] * fB1; + kInv[8] = +this.elements[4] * fB4 - this.elements[5] * fB2 + this.elements[7] * fB0; + kInv[12] = -this.elements[4] * fB3 + this.elements[5] * fB1 - this.elements[6] * fB0; + kInv[1] = -this.elements[1] * fB5 + this.elements[2] * fB4 - this.elements[3] * fB3; + kInv[5] = +this.elements[0] * fB5 - this.elements[2] * fB2 + this.elements[3] * fB1; + kInv[9] = -this.elements[0] * fB4 + this.elements[1] * fB2 - this.elements[3] * fB0; + kInv[13] = +this.elements[0] * fB3 - this.elements[1] * fB1 + this.elements[2] * fB0; + kInv[2] = +this.elements[13] * fA5 - this.elements[14] * fA4 + this.elements[15] * fA3; + kInv[6] = -this.elements[12] * fA5 + this.elements[14] * fA2 - this.elements[15] * fA1; + kInv[10] = +this.elements[12] * fA4 - this.elements[13] * fA2 + this.elements[15] * fA0; + kInv[14] = -this.elements[12] * fA3 + this.elements[13] * fA1 - this.elements[14] * fA0; + kInv[3] = -this.elements[9] * fA5 + this.elements[10] * fA4 - this.elements[11] * fA3; + kInv[7] = +this.elements[8] * fA5 - this.elements[10] * fA2 + this.elements[11] * fA1; + kInv[11] = -this.elements[8] * fA4 + this.elements[9] * fA2 - this.elements[11] * fA0; + kInv[15] = +this.elements[8] * fA3 - this.elements[9] * fA1 + this.elements[10] * fA0; + + // Inverse using Determinant + var fInvDet = 1.0 / fDet; + kInv[0] *= fInvDet; + kInv[1] *= fInvDet; + kInv[2] *= fInvDet; + kInv[3] *= fInvDet; + kInv[4] *= fInvDet; + kInv[5] *= fInvDet; + kInv[6] *= fInvDet; + kInv[7] *= fInvDet; + kInv[8] *= fInvDet; + kInv[9] *= fInvDet; + kInv[10] *= fInvDet; + kInv[11] *= fInvDet; + kInv[12] *= fInvDet; + kInv[13] *= fInvDet; + kInv[14] *= fInvDet; + kInv[15] *= fInvDet; + + this.elements = kInv.slice(); + return true; + }, + toString: function() { + var str = ""; + for (var i = 0; i < 15; i++) { + str += this.elements[i] + ", "; + } + str += this.elements[15]; + return str; + }, + /** + * @member PMatrix3D + * The print() function prints out the elements of this matrix + */ + print: function() { + var digits = printMatrixHelper(this.elements); + + var output = "" + p.nfs(this.elements[0], digits, 4) + " " + p.nfs(this.elements[1], digits, 4) + + " " + p.nfs(this.elements[2], digits, 4) + " " + p.nfs(this.elements[3], digits, 4) + + "\n" + p.nfs(this.elements[4], digits, 4) + " " + p.nfs(this.elements[5], digits, 4) + + " " + p.nfs(this.elements[6], digits, 4) + " " + p.nfs(this.elements[7], digits, 4) + + "\n" + p.nfs(this.elements[8], digits, 4) + " " + p.nfs(this.elements[9], digits, 4) + + " " + p.nfs(this.elements[10], digits, 4) + " " + p.nfs(this.elements[11], digits, 4) + + "\n" + p.nfs(this.elements[12], digits, 4) + " " + p.nfs(this.elements[13], digits, 4) + + " " + p.nfs(this.elements[14], digits, 4) + " " + p.nfs(this.elements[15], digits, 4) + "\n\n"; + p.println(output); + }, + invTranslate: function(tx, ty, tz) { + this.preApply(1, 0, 0, -tx, 0, 1, 0, -ty, 0, 0, 1, -tz, 0, 0, 0, 1); + }, + invRotateX: function(angle) { + var c = Math.cos(-angle); + var s = Math.sin(-angle); + this.preApply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]); + }, + invRotateY: function(angle) { + var c = Math.cos(-angle); + var s = Math.sin(-angle); + this.preApply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]); + }, + invRotateZ: function(angle) { + var c = Math.cos(-angle); + var s = Math.sin(-angle); + this.preApply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); + }, + invScale: function(x, y, z) { + this.preApply([1 / x, 0, 0, 0, 0, 1 / y, 0, 0, 0, 0, 1 / z, 0, 0, 0, 0, 1]); + } + }; + + return PMatrix3D; +}; +},{}],16:[function(require,module,exports){ +module.exports = function(options) { + var PConstants = options.PConstants, + PMatrix2D = options.PMatrix2D, + PMatrix3D = options.PMatrix3D; + + /** + * Datatype for storing shapes. Processing can currently load and display SVG (Scalable Vector Graphics) shapes. + * Before a shape is used, it must be loaded with the <b>loadShape()</b> function. The <b>shape()</b> function is used to draw the shape to the display window. + * The <b>PShape</b> object contain a group of methods, linked below, that can operate on the shape data. + * <br><br>The <b>loadShape()</b> method supports SVG files created with Inkscape and Adobe Illustrator. + * It is not a full SVG implementation, but offers some straightforward support for handling vector data. + * + * @param {int} family the shape type, one of GROUP, PRIMITIVE, PATH, or GEOMETRY + * + * @see #shape() + * @see #loadShape() + * @see #shapeMode() + */ + var PShape = function(family) { + this.family = family || PConstants.GROUP; + this.visible = true; + this.style = true; + this.children = []; + this.nameTable = []; + this.params = []; + this.name = ""; + this.image = null; //type PImage + this.matrix = null; + this.kind = null; + this.close = null; + this.width = null; + this.height = null; + this.parent = null; + }; + /** + * PShape methods + * missing: findChild(), apply(), contains(), findChild(), getPrimitive(), getParams(), getVertex() , getVertexCount(), + * getVertexCode() , getVertexCodes() , getVertexCodeCount(), getVertexX(), getVertexY(), getVertexZ() + */ + PShape.prototype = { + /** + * @member PShape + * The isVisible() function returns a boolean value "true" if the image is set to be visible, "false" if not. This is modified with the <b>setVisible()</b> parameter. + * <br><br>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 <b>visible</b> parameter. + * <br><br>The visibility of a shape is usually controlled by whatever program created the SVG file. + * For instance, this parameter is controlled by showing or hiding the shape in the layers palette in Adobe Illustrator. + * + * @param {boolean} visible "false" makes the shape invisible and "true" makes it visible + */ + setVisible: function (visible){ + this.visible = visible; + }, + /** + * @member PShape + * The disableStyle() function disables the shape's style data and uses Processing's current styles. Styles include attributes such as colors, stroke weight, and stroke joints. + * Overrides this shape's style information and uses PGraphics styles and colors. Identical to ignoreStyles(true). Also disables styles for all child shapes. + */ + disableStyle: function(){ + this.style = false; + for(var i = 0, j=this.children.length; i<j; i++) { + this.children[i].disableStyle(); + } + }, + /** + * @member PShape + * The enableStyle() function enables the shape's style data and ignores Processing's current styles. Styles include attributes such as colors, stroke weight, and stroke joints. + */ + enableStyle: function(){ + this.style = true; + for(var i = 0, j=this.children.length; i<j; i++) { + this.children[i].enableStyle(); + } + }, + /** + * @member PShape + * The getFamily function returns the shape type + * + * @return {int} the shape type, one of GROUP, PRIMITIVE, PATH, or GEOMETRY + */ + getFamily: function(){ + return this.family; + }, + /** + * @member PShape + * The getWidth() function gets the width of the drawing area (not necessarily the shape boundary). + */ + getWidth: function(){ + return this.width; + }, + /** + * @member PShape + * The getHeight() function gets the height of the drawing area (not necessarily the shape boundary). + */ + getHeight: function(){ + return this.height; + }, + /** + * @member PShape + * The setName() function sets the name of the shape + * + * @param {String} name the name of the shape + */ + setName: function(name){ + this.name = name; + }, + /** + * @member PShape + * The getName() function returns the name of the shape + * + * @return {String} the name of the shape + */ + getName: function(){ + return this.name; + }, + /** + * @member PShape + * Called by the following (the shape() command adds the g) + * PShape s = loadShapes("blah.svg"); + * shape(s); + */ + draw: function(renderContext) { + if(!renderContext) { + throw "render context missing for draw() in PShape"; + } + if (this.visible) { + this.pre(renderContext); + this.drawImpl(renderContext); + this.post(renderContext); + } + }, + /** + * @member PShape + * the drawImpl() function draws the SVG document. + */ + drawImpl: function(renderContext) { + if (this.family === PConstants.GROUP) { + this.drawGroup(renderContext); + } else if (this.family === PConstants.PRIMITIVE) { + this.drawPrimitive(renderContext); + } else if (this.family === PConstants.GEOMETRY) { + this.drawGeometry(renderContext); + } else if (this.family === PConstants.PATH) { + this.drawPath(renderContext); + } + }, + /** + * @member PShape + * The drawPath() function draws the <path> part of the SVG document. + */ + drawPath: function(renderContext) { + var i, j; + if (this.vertices.length === 0) { return; } + renderContext.beginShape(); + if (this.vertexCodes.length === 0) { // each point is a simple vertex + if (this.vertices[0].length === 2) { // drawing 2D vertices + for (i = 0, j = this.vertices.length; i < j; i++) { + renderContext.vertex(this.vertices[i][0], this.vertices[i][1]); + } + } else { // drawing 3D vertices + for (i = 0, j = this.vertices.length; i < j; i++) { + renderContext.vertex(this.vertices[i][0], + this.vertices[i][1], + this.vertices[i][2]); + } + } + } else { // coded set of vertices + var index = 0; + if (this.vertices[0].length === 2) { // drawing a 2D path + for (i = 0, j = this.vertexCodes.length; i < j; i++) { + if (this.vertexCodes[i] === PConstants.VERTEX) { + renderContext.vertex(this.vertices[index][0], this.vertices[index][1], this.vertices[index].moveTo); + renderContext.breakShape = false; + index++; + } else if (this.vertexCodes[i] === PConstants.BEZIER_VERTEX) { + renderContext.bezierVertex(this.vertices[index+0][0], + this.vertices[index+0][1], + this.vertices[index+1][0], + this.vertices[index+1][1], + this.vertices[index+2][0], + this.vertices[index+2][1]); + index += 3; + } else if (this.vertexCodes[i] === PConstants.CURVE_VERTEX) { + renderContext.curveVertex(this.vertices[index][0], + this.vertices[index][1]); + index++; + } else if (this.vertexCodes[i] === PConstants.BREAK) { + renderContext.breakShape = true; + } + } + } else { // drawing a 3D path + for (i = 0, j = this.vertexCodes.length; i < j; i++) { + if (this.vertexCodes[i] === PConstants.VERTEX) { + renderContext.vertex(this.vertices[index][0], + this.vertices[index][1], + this.vertices[index][2]); + if (this.vertices[index].moveTo === true) { + vertArray[vertArray.length-1].moveTo = true; + } else if (this.vertices[index].moveTo === false) { + vertArray[vertArray.length-1].moveTo = false; + } + renderContext.breakShape = false; + } else if (this.vertexCodes[i] === PConstants.BEZIER_VERTEX) { + renderContext.bezierVertex(this.vertices[index+0][0], + this.vertices[index+0][1], + this.vertices[index+0][2], + this.vertices[index+1][0], + this.vertices[index+1][1], + this.vertices[index+1][2], + this.vertices[index+2][0], + this.vertices[index+2][1], + this.vertices[index+2][2]); + index += 3; + } else if (this.vertexCodes[i] === PConstants.CURVE_VERTEX) { + renderContext.curveVertex(this.vertices[index][0], + this.vertices[index][1], + this.vertices[index][2]); + index++; + } else if (this.vertexCodes[i] === PConstants.BREAK) { + renderContext.breakShape = true; + } + } + } + } + renderContext.endShape(this.close ? PConstants.CLOSE : PConstants.OPEN); + }, + /** + * @member PShape + * The drawGeometry() function draws the geometry part of the SVG document. + */ + drawGeometry: function(renderContext) { + var i, j; + renderContext.beginShape(this.kind); + if (this.style) { + for (i = 0, j = this.vertices.length; i < j; i++) { + renderContext.vertex(this.vertices[i]); + } + } else { + for (i = 0, j = this.vertices.length; i < j; i++) { + var vert = this.vertices[i]; + if (vert[2] === 0) { + renderContext.vertex(vert[0], vert[1]); + } else { + renderContext.vertex(vert[0], vert[1], vert[2]); + } + } + } + renderContext.endShape(); + }, + /** + * @member PShape + * The drawGroup() function draws the <g> part of the SVG document. + */ + drawGroup: function(renderContext) { + for (var i = 0, j = this.children.length; i < j; i++) { + this.children[i].draw(renderContext); + } + }, + /** + * @member PShape + * The drawPrimitive() function draws SVG document shape elements. These can be point, line, triangle, quad, rect, ellipse, arc, box, or sphere. + */ + drawPrimitive: function(renderContext) { + if (this.kind === PConstants.POINT) { + renderContext.point(this.params[0], this.params[1]); + } else if (this.kind === PConstants.LINE) { + if (this.params.length === 4) { // 2D + renderContext.line(this.params[0], this.params[1], + this.params[2], this.params[3]); + } else { // 3D + renderContext.line(this.params[0], this.params[1], this.params[2], + this.params[3], this.params[4], this.params[5]); + } + } else if (this.kind === PConstants.TRIANGLE) { + renderContext.triangle(this.params[0], this.params[1], + this.params[2], this.params[3], + this.params[4], this.params[5]); + } else if (this.kind === PConstants.QUAD) { + renderContext.quad(this.params[0], this.params[1], + this.params[2], this.params[3], + this.params[4], this.params[5], + this.params[6], this.params[7]); + } else if (this.kind === PConstants.RECT) { + if (this.image !== null) { + var imMode = imageModeConvert; + renderContext.imageMode(PConstants.CORNER); + renderContext.image(this.image, + this.params[0], + this.params[1], + this.params[2], + this.params[3]); + imageModeConvert = imMode; + } else { + var rcMode = renderContext.curRectMode; + renderContext.rectMode(PConstants.CORNER); + renderContext.rect(this.params[0], + this.params[1], + this.params[2], + this.params[3]); + renderContext.curRectMode = rcMode; + } + } else if (this.kind === PConstants.ELLIPSE) { + var elMode = renderContext.curEllipseMode; + renderContext.ellipseMode(PConstants.CORNER); + renderContext.ellipse(this.params[0], + this.params[1], + this.params[2], + this.params[3]); + renderContext.curEllipseMode = elMode; + } else if (this.kind === PConstants.ARC) { + var eMode = curEllipseMode; + renderContext.ellipseMode(PConstants.CORNER); + renderContext.arc(this.params[0], + this.params[1], + this.params[2], + this.params[3], + this.params[4], + this.params[5]); + curEllipseMode = eMode; + } else if (this.kind === PConstants.BOX) { + if (this.params.length === 1) { + renderContext.box(this.params[0]); + } else { + renderContext.box(this.params[0], this.params[1], this.params[2]); + } + } else if (this.kind === PConstants.SPHERE) { + renderContext.sphere(this.params[0]); + } + }, + /** + * @member PShape + * The pre() function performs the preparations before the SVG is drawn. This includes doing transformations and storing previous styles. + */ + pre: function(renderContext) { + if (this.matrix) { + renderContext.pushMatrix(); + renderContext.transform(this.matrix); + } + if (this.style) { + renderContext.pushStyle(); + this.styles(renderContext); + } + }, + /** + * @member PShape + * The post() function performs the necessary actions after the SVG is drawn. This includes removing transformations and removing added styles. + */ + post: function(renderContext) { + if (this.matrix) { + renderContext.popMatrix(); + } + if (this.style) { + renderContext.popStyle(); + } + }, + /** + * @member PShape + * The styles() function changes the Processing's current styles + */ + styles: function(renderContext) { + if (this.stroke) { + renderContext.stroke(this.strokeColor); + renderContext.strokeWeight(this.strokeWeight); + renderContext.strokeCap(this.strokeCap); + renderContext.strokeJoin(this.strokeJoin); + } else { + renderContext.noStroke(); + } + + if (this.fill) { + renderContext.fill(this.fillColor); + + } else { + renderContext.noFill(); + } + }, + /** + * @member PShape + * The getChild() function extracts a child shape from a parent shape. Specify the name of the shape with the <b>target</b> parameter or the + * layer position of the shape to get with the <b>index</b> parameter. + * The shape is returned as a <b>PShape</b> object, or <b>null</b> is returned if there is an error. + * + * @param {String} target the name of the shape to get + * @param {int} index the layer position of the shape to get + * + * @return {PShape} returns a child element of a shape as a PShape object or null if there is an error + */ + getChild: function(child) { + var i, j; + if (typeof child === 'number') { + return this.children[child]; + } + var found; + if(child === "" || this.name === child){ + return this; + } + if(this.nameTable.length > 0) { + for(i = 0, j = this.nameTable.length; i < j || found; i++) { + if(this.nameTable[i].getName === child) { + found = this.nameTable[i]; + break; + } + } + if (found) { return found; } + } + for(i = 0, j = this.children.length; i < j; i++) { + found = this.children[i].getChild(child); + if(found) { return found; } + } + return null; + }, + /** + * @member PShape + * The getChildCount() returns the number of children + * + * @return {int} returns a count of children + */ + getChildCount: function () { + return this.children.length; + }, + /** + * @member PShape + * The addChild() adds a child to the PShape. + * + * @param {PShape} child the child to add + */ + addChild: function( child ) { + this.children.push(child); + child.parent = this; + if (child.getName() !== null) { + this.addName(child.getName(), child); + } + }, + /** + * @member PShape + * The addName() functions adds a shape to the name lookup table. + * + * @param {String} name the name to be added + * @param {PShape} shape the shape + */ + addName: function(name, shape) { + if (this.parent !== null) { + this.parent.addName( name, shape ); + } else { + this.nameTable.push( [name, shape] ); + } + }, + /** + * @member PShape + * The translate() function specifies an amount to displace the shape. The <b>x</b> parameter specifies left/right translation, the <b>y</b> parameter specifies up/down translation, and the <b>z</b> parameter specifies translations toward/away from the screen. + * Subsequent calls to the method accumulates the effect. For example, calling <b>translate(50, 0)</b> and then <b>translate(20, 0)</b> is the same as <b>translate(70, 0)</b>. + * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. + * <br><br>Using this method with the <b>z</b> 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 <em>at least</em> the specified number of dimensions. + * + * @param {int} dimensions the specified number of dimensions + */ + checkMatrix: function(dimensions) { + if(this.matrix === null) { + if(dimensions === 2) { + this.matrix = new PMatrix2D(); + } else { + this.matrix = new PMatrix3D(); + } + }else if(dimensions === 3 && this.matrix instanceof PMatrix2D) { + this.matrix = new PMatrix3D(); + } + }, + /** + * @member PShape + * The rotateX() function rotates a shape around the x-axis the amount specified by the <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. + * <br><br>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 <b>rotateX(HALF_PI)</b> and then <b>rotateX(HALF_PI)</b> is the same as <b>rotateX(PI)</b>. + * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. + * <br><br>This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the <b>size()</b> 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 <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. + * <br><br>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 <b>rotateY(HALF_PI)</b> and then <b>rotateY(HALF_PI)</b> is the same as <b>rotateY(PI)</b>. + * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. + * <br><br>This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the <b>size()</b> 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 <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. + * <br><br>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 <b>rotateZ(HALF_PI)</b> and then <b>rotateZ(HALF_PI)</b> is the same as <b>rotateZ(PI)</b>. + * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. + * <br><br>This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the <b>size()</b> 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 <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. + * <br><br>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 <b>rotate(HALF_PI)</b> and then <b>rotate(HALF_PI)</b> is the same as <b>rotate(PI)</b>. + * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> 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 <b>scale(2.0)</b> increases the dimension of a shape by 200%. + * Subsequent calls to the method multiply the effect. For example, calling <b>scale(2.0)</b> and then <b>scale(1.5)</b> is the same as <b>scale(3.0)</b>. + * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. + * <br><br>Using this fuction with the <b>z</b> parameter requires passing P3D or OPENGL into the size() parameter. + * + * @param {float}s percentage to scale the object + * @param {float}x percentage to scale the object in the x-axis + * @param {float}y percentage to scale the object in the y-axis + * @param {float}z percentage to scale the object in the z-axis + * + * @see PMatrix2D#scale + * @see PMatrix3D#scale + */ + scale: function() { + if(arguments.length === 2) { + this.checkMatrix(2); + this.matrix.scale(arguments[0], arguments[1]); + } else if (arguments.length === 3) { + this.checkMatrix(2); + this.matrix.scale(arguments[0], arguments[1], arguments[2]); + } else { + this.checkMatrix(2); + this.matrix.scale(arguments[0]); + } + }, + /** + * @member PShape + * The resetMatrix() function resets the matrix + * + * @see PMatrix2D#reset + * @see PMatrix3D#reset + */ + resetMatrix: function() { + this.checkMatrix(2); + this.matrix.reset(); + }, + /** + * @member PShape + * The applyMatrix() function multiplies this matrix by another matrix of type PMatrix3D or PMatrix2D. + * Individual elements can also be provided + * + * @param {PMatrix3D|PMatrix2D} matrix the matrix to multiply by + * + * @see PMatrix2D#apply + * @see PMatrix3D#apply + */ + applyMatrix: function(matrix) { + if (arguments.length === 1) { + this.applyMatrix(matrix.elements[0], + matrix.elements[1], 0, + matrix.elements[2], + matrix.elements[3], + matrix.elements[4], 0, + matrix.elements[5], + 0, 0, 1, 0, + 0, 0, 0, 1); + } else if (arguments.length === 6) { + this.checkMatrix(2); + this.matrix.apply(arguments[0], arguments[1], arguments[2], 0, + arguments[3], arguments[4], arguments[5], 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + + } else if (arguments.length === 16) { + this.checkMatrix(3); + this.matrix.apply(arguments[0], + arguments[1], + arguments[2], + arguments[3], + arguments[4], + arguments[5], + arguments[6], + arguments[7], + arguments[8], + arguments[9], + arguments[10], + arguments[11], + arguments[12], + arguments[13], + arguments[14], + arguments[15]); + } + } + }; + + return PShape; +}; +},{}],17:[function(require,module,exports){ +/** + * SVG stands for Scalable Vector Graphics, a portable graphics format. It is + * a vector format so it allows for infinite resolution and relatively small + * file sizes. Most modern media software can view SVG files, including Adobe + * products, Firefox, etc. Illustrator and Inkscape can edit SVG files. + * + * @param {PApplet} parent typically use "this" + * @param {String} filename name of the SVG file to load + * @param {XMLElement} xml an XMLElement element + * @param {PShapeSVG} parent the parent PShapeSVG + * + * @see PShape + */ +module.exports = function(options) { + var CommonFunctions = options.CommonFunctions, + PConstants = options.PConstants, + PShape = options.PShape, + XMLElement = options.XMLElement, + colors = options.colors; + + var PShapeSVG = function() { + PShape.call(this); // PShape is the base class. + if (arguments.length === 1) { // xml element coming in + this.element = arguments[0]; + + // set values to their defaults according to the SVG spec + this.vertexCodes = []; + this.vertices = []; + this.opacity = 1; + + this.stroke = false; + this.strokeColor = PConstants.ALPHA_MASK; + this.strokeWeight = 1; + this.strokeCap = PConstants.SQUARE; // BUTT in svg spec + this.strokeJoin = PConstants.MITER; + this.strokeGradient = null; + this.strokeGradientPaint = null; + this.strokeName = null; + this.strokeOpacity = 1; + + this.fill = true; + this.fillColor = PConstants.ALPHA_MASK; + this.fillGradient = null; + this.fillGradientPaint = null; + this.fillName = null; + this.fillOpacity = 1; + + if (this.element.getName() !== "svg") { + throw("root is not <svg>, it's <" + this.element.getName() + ">"); + } + } + else if (arguments.length === 2) { + if (typeof arguments[1] === 'string') { + if (arguments[1].indexOf(".svg") > -1) { //its a filename + this.element = new XMLElement(true, arguments[1]); + // set values to their defaults according to the SVG spec + this.vertexCodes = []; + this.vertices = []; + this.opacity = 1; + + this.stroke = false; + this.strokeColor = PConstants.ALPHA_MASK; + this.strokeWeight = 1; + this.strokeCap = PConstants.SQUARE; // BUTT in svg spec + this.strokeJoin = PConstants.MITER; + this.strokeGradient = ""; + this.strokeGradientPaint = ""; + this.strokeName = ""; + this.strokeOpacity = 1; + + this.fill = true; + this.fillColor = PConstants.ALPHA_MASK; + this.fillGradient = null; + this.fillGradientPaint = null; + this.fillOpacity = 1; + + } + } else { // XMLElement + if (arguments[0]) { // PShapeSVG + this.element = arguments[1]; + this.vertexCodes = arguments[0].vertexCodes.slice(); + this.vertices = arguments[0].vertices.slice(); + + this.stroke = arguments[0].stroke; + this.strokeColor = arguments[0].strokeColor; + this.strokeWeight = arguments[0].strokeWeight; + this.strokeCap = arguments[0].strokeCap; + this.strokeJoin = arguments[0].strokeJoin; + this.strokeGradient = arguments[0].strokeGradient; + this.strokeGradientPaint = arguments[0].strokeGradientPaint; + this.strokeName = arguments[0].strokeName; + + this.fill = arguments[0].fill; + this.fillColor = arguments[0].fillColor; + this.fillGradient = arguments[0].fillGradient; + this.fillGradientPaint = arguments[0].fillGradientPaint; + this.fillName = arguments[0].fillName; + this.strokeOpacity = arguments[0].strokeOpacity; + this.fillOpacity = arguments[0].fillOpacity; + this.opacity = arguments[0].opacity; + } + } + } + + this.name = this.element.getStringAttribute("id"); + var displayStr = this.element.getStringAttribute("display", "inline"); + this.visible = displayStr !== "none"; + var str = this.element.getAttribute("transform"); + if (str) { + this.matrix = this.parseMatrix(str); + } + // not proper parsing of the viewBox, but will cover us for cases where + // the width and height of the object is not specified + var viewBoxStr = this.element.getStringAttribute("viewBox"); + if ( viewBoxStr !== null ) { + var viewBox = viewBoxStr.split(" "); + this.width = viewBox[2]; + this.height = viewBox[3]; + } + + // TODO if viewbox is not same as width/height, then use it to scale + // the original objects. for now, viewbox only used when width/height + // are empty values (which by the spec means w/h of "100%" + var unitWidth = this.element.getStringAttribute("width"); + var unitHeight = this.element.getStringAttribute("height"); + if (unitWidth !== null) { + this.width = this.parseUnitSize(unitWidth); + this.height = this.parseUnitSize(unitHeight); + } else { + if ((this.width === 0) || (this.height === 0)) { + // For the spec, the default is 100% and 100%. For purposes + // here, insert a dummy value because this is prolly just a + // font or something for which the w/h doesn't matter. + this.width = 1; + this.height = 1; + + //show warning + throw("The width and/or height is not " + + "readable in the <svg> tag of this file."); + } + } + this.parseColors(this.element); + this.parseChildren(this.element); + + }; + /** + * PShapeSVG methods are inherited from the PShape prototype + */ + PShapeSVG.prototype = new PShape(); + /** + * @member PShapeSVG + * The parseMatrix() function parses the specified SVG matrix into a PMatrix2D. Note that PMatrix2D + * is rotated relative to the SVG definition, so parameters are rearranged + * here. More about the transformation matrices in + * <a href="http://www.w3.org/TR/SVG/coords.html#TransformAttribute">this section</a> + * of the SVG documentation. + * + * @param {String} str text of the matrix param. + * + * @return {PMatrix2D} a PMatrix2D + */ + PShapeSVG.prototype.parseMatrix = (function() { + function getCoords(s) { + var m = []; + s.replace(/\((.*?)\)/, (function() { + return function(all, params) { + // get the coordinates that can be separated by spaces or a comma + m = params.replace(/,+/g, " ").split(/\s+/); + }; + }())); + return m; + } + + return function(str) { + this.checkMatrix(2); + var pieces = []; + str.replace(/\s*(\w+)\((.*?)\)/g, function(all) { + // get a list of transform definitions + pieces.push(CommonFunctions.trim(all)); + }); + if (pieces.length === 0) { + return null; + } + + for (var i = 0, j = pieces.length; i < j; i++) { + var m = getCoords(pieces[i]); + + if (pieces[i].indexOf("matrix") !== -1) { + this.matrix.set(m[0], m[2], m[4], m[1], m[3], m[5]); + } else if (pieces[i].indexOf("translate") !== -1) { + var tx = m[0]; + var ty = (m.length === 2) ? m[1] : 0; + this.matrix.translate(tx,ty); + } else if (pieces[i].indexOf("scale") !== -1) { + var sx = m[0]; + var sy = (m.length === 2) ? m[1] : m[0]; + this.matrix.scale(sx,sy); + } else if (pieces[i].indexOf("rotate") !== -1) { + var angle = m[0]; + if (m.length === 1) { + this.matrix.rotate(CommonFunctions.radians(angle)); + } else if (m.length === 3) { + this.matrix.translate(m[1], m[2]); + this.matrix.rotate(CommonFunctions.radians(m[0])); + this.matrix.translate(-m[1], -m[2]); + } + } else if (pieces[i].indexOf("skewX") !== -1) { + this.matrix.skewX(parseFloat(m[0])); + } else if (pieces[i].indexOf("skewY") !== -1) { + this.matrix.skewY(m[0]); + } else if (pieces[i].indexOf("shearX") !== -1) { + this.matrix.shearX(m[0]); + } else if (pieces[i].indexOf("shearY") !== -1) { + this.matrix.shearY(m[0]); + } + } + return this.matrix; + }; + }()); + + /** + * @member PShapeSVG + * The parseChildren() function parses the specified XMLElement + * + * @param {XMLElement}element the XMLElement to parse + */ + PShapeSVG.prototype.parseChildren = function(element) { + var newelement = element.getChildren(); + var base = new PShape(); + var i, j; + for (i = 0, j = newelement.length; i < j; i++) { + var kid = this.parseChild(newelement[i]); + if (kid) { + base.addChild(kid); + } + } + for (i = 0, j = base.children.length; i < j; i++) { + this.children.push(base.children[i]); + } + }; + /** + * @member PShapeSVG + * The getName() function returns the name + * + * @return {String} the name + */ + PShapeSVG.prototype.getName = function() { + return this.name; + }; + /** + * @member PShapeSVG + * The parseChild() function parses a child XML element. + * + * @param {XMLElement} elem the element to parse + * + * @return {PShape} the newly created PShape + */ + PShapeSVG.prototype.parseChild = function( elem ) { + var name = elem.getName(); + var shape; + if (name === "g") { + shape = new PShapeSVG(this, elem); + } else if (name === "defs") { + // generally this will contain gradient info, so may + // as well just throw it into a group element for parsing + shape = new PShapeSVG(this, elem); + } else if (name === "line") { + shape = new PShapeSVG(this, elem); + shape.parseLine(); + } else if (name === "circle") { + shape = new PShapeSVG(this, elem); + shape.parseEllipse(true); + } else if (name === "ellipse") { + shape = new PShapeSVG(this, elem); + shape.parseEllipse(false); + } else if (name === "rect") { + shape = new PShapeSVG(this, elem); + shape.parseRect(); + } else if (name === "polygon") { + shape = new PShapeSVG(this, elem); + shape.parsePoly(true); + } else if (name === "polyline") { + shape = new PShapeSVG(this, elem); + shape.parsePoly(false); + } else if (name === "path") { + shape = new PShapeSVG(this, elem); + shape.parsePath(); + } else if (name === "radialGradient") { + //return new RadialGradient(this, elem); + unimplemented('PShapeSVG.prototype.parseChild, name = radialGradient'); + } else if (name === "linearGradient") { + //return new LinearGradient(this, elem); + unimplemented('PShapeSVG.prototype.parseChild, name = linearGradient'); + } else if (name === "text") { + unimplemented('PShapeSVG.prototype.parseChild, name = text'); + } else if (name === "filter") { + unimplemented('PShapeSVG.prototype.parseChild, name = filter'); + } else if (name === "mask") { + unimplemented('PShapeSVG.prototype.parseChild, name = mask'); + } else { + // ignoring + } + return shape; + }; + /** + * @member PShapeSVG + * The parsePath() function parses the <path> element of the svg file + * A path is defined by including a path element which contains a d="(path data)" attribute, where the d attribute contains + * the moveto, line, curve (both cubic and quadratic Beziers), arc and closepath instructions. + **/ + PShapeSVG.prototype.parsePath = function() { + this.family = PConstants.PATH; + this.kind = 0; + var pathDataChars = []; + var c; + //change multiple spaces and commas to single space + var pathData = CommonFunctions.trim(this.element.getStringAttribute("d").replace(/[\s,]+/g,' ')); + if (pathData === null) { + return; + } + pathData = pathData.split(''); + var cx = 0, + cy = 0, + ctrlX = 0, + ctrlY = 0, + ctrlX1 = 0, + ctrlX2 = 0, + ctrlY1 = 0, + ctrlY2 = 0, + endX = 0, + endY = 0, + ppx = 0, + ppy = 0, + px = 0, + py = 0, + i = 0, + valOf = 0; + var str = ""; + var tmpArray = []; + var flag = false; + var lastInstruction; + var command; + var j, k; + while (i< pathData.length) { + valOf = pathData[i].charCodeAt(0); + if ((valOf >= 65 && valOf <= 90) || (valOf >= 97 && valOf <= 122)) { + // if it's a letter + // populate the tmpArray with coordinates + j = i; + i++; + if (i < pathData.length) { // don't go over boundary of array + tmpArray = []; + valOf = pathData[i].charCodeAt(0); + while (!((valOf >= 65 && valOf <= 90) || + (valOf >= 97 && valOf <= 100) || + (valOf >= 102 && valOf <= 122)) && flag === false) { // if its NOT a letter + if (valOf === 32) { //if its a space and the str isn't empty + // sometimes you get a space after the letter + if (str !== "") { + tmpArray.push(parseFloat(str)); + str = ""; + } + i++; + } else if (valOf === 45) { //if it's a - + // allow for 'e' notation in numbers, e.g. 2.10e-9 + if (pathData[i-1].charCodeAt(0) === 101) { + str += pathData[i].toString(); + i++; + } else { + // sometimes no space separator after (ex: 104.535-16.322) + if (str !== "") { + tmpArray.push(parseFloat(str)); + } + str = pathData[i].toString(); + i++; + } + } else { + str += pathData[i].toString(); + i++; + } + if (i === pathData.length) { // don't go over boundary of array + flag = true; + } else { + valOf = pathData[i].charCodeAt(0); + } + } + } + if (str !== "") { + tmpArray.push(parseFloat(str)); + str = ""; + } + command = pathData[j]; + valOf = command.charCodeAt(0); + if (valOf === 77) { // M - move to (absolute) + if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { + // need one+ pairs of co-ordinates + cx = tmpArray[0]; + cy = tmpArray[1]; + this.parsePathMoveto(cx, cy); + if (tmpArray.length > 2) { + for (j = 2, k = tmpArray.length; j < k; j+=2) { + // absolute line to + cx = tmpArray[j]; + cy = tmpArray[j+1]; + this.parsePathLineto(cx,cy); + } + } + } + } else if (valOf === 109) { // m - move to (relative) + if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { + // need one+ pairs of co-ordinates + cx += tmpArray[0]; + cy += tmpArray[1]; + this.parsePathMoveto(cx,cy); + if (tmpArray.length > 2) { + for (j = 2, k = tmpArray.length; j < k; j+=2) { + // relative line to + cx += tmpArray[j]; + cy += tmpArray[j + 1]; + this.parsePathLineto(cx,cy); + } + } + } + } else if (valOf === 76) { // L - lineto (absolute) + if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { + // need one+ pairs of co-ordinates + for (j = 0, k = tmpArray.length; j < k; j+=2) { + cx = tmpArray[j]; + cy = tmpArray[j + 1]; + this.parsePathLineto(cx,cy); + } + } + } else if (valOf === 108) { // l - lineto (relative) + if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { + // need one+ pairs of co-ordinates + for (j = 0, k = tmpArray.length; j < k; j+=2) { + cx += tmpArray[j]; + cy += tmpArray[j+1]; + this.parsePathLineto(cx,cy); + } + } + } else if (valOf === 72) { // H - horizontal lineto (absolute) + for (j = 0, k = tmpArray.length; j < k; j++) { + // multiple x co-ordinates can be provided + cx = tmpArray[j]; + this.parsePathLineto(cx, cy); + } + } else if (valOf === 104) { // h - horizontal lineto (relative) + for (j = 0, k = tmpArray.length; j < k; j++) { + // multiple x co-ordinates can be provided + cx += tmpArray[j]; + this.parsePathLineto(cx, cy); + } + } else if (valOf === 86) { // V - vertical lineto (absolute) + for (j = 0, k = tmpArray.length; j < k; j++) { + // multiple y co-ordinates can be provided + cy = tmpArray[j]; + this.parsePathLineto(cx, cy); + } + } else if (valOf === 118) { // v - vertical lineto (relative) + for (j = 0, k = tmpArray.length; j < k; j++) { + // multiple y co-ordinates can be provided + cy += tmpArray[j]; + this.parsePathLineto(cx, cy); + } + } else if (valOf === 67) { // C - curve to (absolute) + if (tmpArray.length >= 6 && tmpArray.length % 6 === 0) { + // need one+ multiples of 6 co-ordinates + for (j = 0, k = tmpArray.length; j < k; j+=6) { + ctrlX1 = tmpArray[j]; + ctrlY1 = tmpArray[j + 1]; + ctrlX2 = tmpArray[j + 2]; + ctrlY2 = tmpArray[j + 3]; + endX = tmpArray[j + 4]; + endY = tmpArray[j + 5]; + this.parsePathCurveto(ctrlX1, + ctrlY1, + ctrlX2, + ctrlY2, + endX, + endY); + cx = endX; + cy = endY; + } + } + } else if (valOf === 99) { // c - curve to (relative) + if (tmpArray.length >= 6 && tmpArray.length % 6 === 0) { + // need one+ multiples of 6 co-ordinates + for (j = 0, k = tmpArray.length; j < k; j+=6) { + ctrlX1 = cx + tmpArray[j]; + ctrlY1 = cy + tmpArray[j + 1]; + ctrlX2 = cx + tmpArray[j + 2]; + ctrlY2 = cy + tmpArray[j + 3]; + endX = cx + tmpArray[j + 4]; + endY = cy + tmpArray[j + 5]; + this.parsePathCurveto(ctrlX1, + ctrlY1, + ctrlX2, + ctrlY2, + endX, + endY); + cx = endX; + cy = endY; + } + } + } else if (valOf === 83) { // S - curve to shorthand (absolute) + if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { + // need one+ multiples of 4 co-ordinates + for (j = 0, k = tmpArray.length; j < k; j+=4) { + if (lastInstruction.toLowerCase() === "c" || + lastInstruction.toLowerCase() === "s") { + ppx = this.vertices[ this.vertices.length-2 ][0]; + ppy = this.vertices[ this.vertices.length-2 ][1]; + px = this.vertices[ this.vertices.length-1 ][0]; + py = this.vertices[ this.vertices.length-1 ][1]; + ctrlX1 = px + (px - ppx); + ctrlY1 = py + (py - ppy); + } else { + //If there is no previous curve, + //the current point will be used as the first control point. + ctrlX1 = this.vertices[this.vertices.length-1][0]; + ctrlY1 = this.vertices[this.vertices.length-1][1]; + } + ctrlX2 = tmpArray[j]; + ctrlY2 = tmpArray[j + 1]; + endX = tmpArray[j + 2]; + endY = tmpArray[j + 3]; + this.parsePathCurveto(ctrlX1, + ctrlY1, + ctrlX2, + ctrlY2, + endX, + endY); + cx = endX; + cy = endY; + } + } + } else if (valOf === 115) { // s - curve to shorthand (relative) + if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { + // need one+ multiples of 4 co-ordinates + for (j = 0, k = tmpArray.length; j < k; j+=4) { + if (lastInstruction.toLowerCase() === "c" || + lastInstruction.toLowerCase() === "s") { + ppx = this.vertices[this.vertices.length-2][0]; + ppy = this.vertices[this.vertices.length-2][1]; + px = this.vertices[this.vertices.length-1][0]; + py = this.vertices[this.vertices.length-1][1]; + ctrlX1 = px + (px - ppx); + ctrlY1 = py + (py - ppy); + } else { + //If there is no previous curve, + //the current point will be used as the first control point. + ctrlX1 = this.vertices[this.vertices.length-1][0]; + ctrlY1 = this.vertices[this.vertices.length-1][1]; + } + ctrlX2 = cx + tmpArray[j]; + ctrlY2 = cy + tmpArray[j + 1]; + endX = cx + tmpArray[j + 2]; + endY = cy + tmpArray[j + 3]; + this.parsePathCurveto(ctrlX1, + ctrlY1, + ctrlX2, + ctrlY2, + endX, + endY); + cx = endX; + cy = endY; + } + } + } else if (valOf === 81) { // Q - quadratic curve to (absolute) + if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { + // need one+ multiples of 4 co-ordinates + for (j = 0, k = tmpArray.length; j < k; j+=4) { + ctrlX = tmpArray[j]; + ctrlY = tmpArray[j + 1]; + endX = tmpArray[j + 2]; + endY = tmpArray[j + 3]; + this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); + cx = endX; + cy = endY; + } + } + } else if (valOf === 113) { // q - quadratic curve to (relative) + if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { + // need one+ multiples of 4 co-ordinates + for (j = 0, k = tmpArray.length; j < k; j+=4) { + ctrlX = cx + tmpArray[j]; + ctrlY = cy + tmpArray[j + 1]; + endX = cx + tmpArray[j + 2]; + endY = cy + tmpArray[j + 3]; + this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); + cx = endX; + cy = endY; + } + } + } else if (valOf === 84) { + // T - quadratic curve to shorthand (absolute) + if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { + // need one+ pairs of co-ordinates + for (j = 0, k = tmpArray.length; j < k; j+=2) { + if (lastInstruction.toLowerCase() === "q" || + lastInstruction.toLowerCase() === "t") { + ppx = this.vertices[this.vertices.length-2][0]; + ppy = this.vertices[this.vertices.length-2][1]; + px = this.vertices[this.vertices.length-1][0]; + py = this.vertices[this.vertices.length-1][1]; + ctrlX = px + (px - ppx); + ctrlY = py + (py - ppy); + } else { + // If there is no previous command or if the previous command + // was not a Q, q, T or t, assume the control point is + // coincident with the current point. + ctrlX = cx; + ctrlY = cy; + } + endX = tmpArray[j]; + endY = tmpArray[j + 1]; + this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); + cx = endX; + cy = endY; + } + } + } else if (valOf === 116) { + // t - quadratic curve to shorthand (relative) + if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { + // need one+ pairs of co-ordinates + for (j = 0, k = tmpArray.length; j < k; j+=2) { + if (lastInstruction.toLowerCase() === "q" || + lastInstruction.toLowerCase() === "t") { + ppx = this.vertices[this.vertices.length-2][0]; + ppy = this.vertices[this.vertices.length-2][1]; + px = this.vertices[this.vertices.length-1][0]; + py = this.vertices[this.vertices.length-1][1]; + ctrlX = px + (px - ppx); + ctrlY = py + (py - ppy); + } else { + // If there is no previous command or if the previous command + // was not a Q, q, T or t, assume the control point is + // coincident with the current point. + ctrlX = cx; + ctrlY = cy; + } + endX = cx + tmpArray[j]; + endY = cy + tmpArray[j + 1]; + this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); + cx = endX; + cy = endY; + } + } + } else if (valOf === 90 || valOf === 122) { // Z or z (these do the same thing) + this.close = true; + } + lastInstruction = command.toString(); + } else { i++;} + } + }; + /** + * @member PShapeSVG + * PShapeSVG.parsePath() helper function + * + * @see PShapeSVG#parsePath + */ + PShapeSVG.prototype.parsePathQuadto = function(x1, y1, cx, cy, x2, y2) { + if (this.vertices.length > 0) { + this.parsePathCode(PConstants.BEZIER_VERTEX); + // x1/y1 already covered by last moveto, lineto, or curveto + this.parsePathVertex(x1 + ((cx-x1)*2/3), y1 + ((cy-y1)*2/3)); + this.parsePathVertex(x2 + ((cx-x2)*2/3), y2 + ((cy-y2)*2/3)); + this.parsePathVertex(x2, y2); + } else { + throw ("Path must start with M/m"); + } + }; + /** + * @member PShapeSVG + * PShapeSVG.parsePath() helper function + * + * @see PShapeSVG#parsePath + */ + PShapeSVG.prototype.parsePathCurveto = function(x1, y1, x2, y2, x3, y3) { + if (this.vertices.length > 0) { + this.parsePathCode(PConstants.BEZIER_VERTEX ); + this.parsePathVertex(x1, y1); + this.parsePathVertex(x2, y2); + this.parsePathVertex(x3, y3); + } else { + throw ("Path must start with M/m"); + } + }; + /** + * @member PShapeSVG + * PShapeSVG.parsePath() helper function + * + * @see PShapeSVG#parsePath + */ + PShapeSVG.prototype.parsePathLineto = function(px, py) { + if (this.vertices.length > 0) { + this.parsePathCode(PConstants.VERTEX); + this.parsePathVertex(px, py); + // add property to distinguish between curContext.moveTo + // or curContext.lineTo + this.vertices[this.vertices.length-1].moveTo = false; + } else { + throw ("Path must start with M/m"); + } + }; + + PShapeSVG.prototype.parsePathMoveto = function(px, py) { + if (this.vertices.length > 0) { + this.parsePathCode(PConstants.BREAK); + } + this.parsePathCode(PConstants.VERTEX); + this.parsePathVertex(px, py); + // add property to distinguish between curContext.moveTo + // or curContext.lineTo + this.vertices[this.vertices.length-1].moveTo = true; + }; + /** + * @member PShapeSVG + * PShapeSVG.parsePath() helper function + * + * @see PShapeSVG#parsePath + */ + PShapeSVG.prototype.parsePathVertex = function(x, y) { + var verts = []; + verts[0] = x; + verts[1] = y; + this.vertices.push(verts); + }; + /** + * @member PShapeSVG + * PShapeSVG.parsePath() helper function + * + * @see PShapeSVG#parsePath + */ + PShapeSVG.prototype.parsePathCode = function(what) { + this.vertexCodes.push(what); + }; + /** + * @member PShapeSVG + * The parsePoly() function parses a polyline or polygon from an SVG file. + * + * @param {boolean}val true if shape is closed (polygon), false if not (polyline) + */ + PShapeSVG.prototype.parsePoly = function(val) { + this.family = PConstants.PATH; + this.close = val; + var pointsAttr = CommonFunctions.trim(this.element.getStringAttribute("points").replace(/[,\s]+/g,' ')); + if (pointsAttr !== null) { + //split into array + var pointsBuffer = pointsAttr.split(" "); + if (pointsBuffer.length % 2 === 0) { + for (var i = 0, j = pointsBuffer.length; i < j; i++) { + var verts = []; + verts[0] = pointsBuffer[i]; + verts[1] = pointsBuffer[++i]; + this.vertices.push(verts); + } + } else { + throw("Error parsing polygon points: odd number of coordinates provided"); + } + } + }; + /** + * @member PShapeSVG + * The parseRect() function parses a rect from an SVG file. + */ + PShapeSVG.prototype.parseRect = function() { + this.kind = PConstants.RECT; + this.family = PConstants.PRIMITIVE; + this.params = []; + this.params[0] = this.element.getFloatAttribute("x"); + this.params[1] = this.element.getFloatAttribute("y"); + this.params[2] = this.element.getFloatAttribute("width"); + this.params[3] = this.element.getFloatAttribute("height"); + if (this.params[2] < 0 || this.params[3] < 0) { + throw("svg error: negative width or height found while parsing <rect>"); + } + }; + /** + * @member PShapeSVG + * The parseEllipse() function handles parsing ellipse and circle tags. + * + * @param {boolean}val true if this is a circle and not an ellipse + */ + PShapeSVG.prototype.parseEllipse = function(val) { + this.kind = PConstants.ELLIPSE; + this.family = PConstants.PRIMITIVE; + this.params = []; + + this.params[0] = this.element.getFloatAttribute("cx") | 0 ; + this.params[1] = this.element.getFloatAttribute("cy") | 0; + + var rx, ry; + if (val) { + rx = ry = this.element.getFloatAttribute("r"); + if (rx < 0) { + throw("svg error: negative radius found while parsing <circle>"); + } + } else { + rx = this.element.getFloatAttribute("rx"); + ry = this.element.getFloatAttribute("ry"); + if (rx < 0 || ry < 0) { + throw("svg error: negative x-axis radius or y-axis radius found while parsing <ellipse>"); + } + } + this.params[0] -= rx; + this.params[1] -= ry; + + this.params[2] = rx*2; + this.params[3] = ry*2; + }; + /** + * @member PShapeSVG + * The parseLine() function handles parsing line tags. + * + * @param {boolean}val true if this is a circle and not an ellipse + */ + PShapeSVG.prototype.parseLine = function() { + this.kind = PConstants.LINE; + this.family = PConstants.PRIMITIVE; + this.params = []; + this.params[0] = this.element.getFloatAttribute("x1"); + this.params[1] = this.element.getFloatAttribute("y1"); + this.params[2] = this.element.getFloatAttribute("x2"); + this.params[3] = this.element.getFloatAttribute("y2"); + }; + /** + * @member PShapeSVG + * The parseColors() function handles parsing the opacity, strijem stroke-width, stroke-linejoin,stroke-linecap, fill, and style attributes + * + * @param {XMLElement}element the element of which attributes to parse + */ + PShapeSVG.prototype.parseColors = function(element) { + if (element.hasAttribute("opacity")) { + this.setOpacity(element.getAttribute("opacity")); + } + if (element.hasAttribute("stroke")) { + this.setStroke(element.getAttribute("stroke")); + } + if (element.hasAttribute("stroke-width")) { + // if NaN (i.e. if it's 'inherit') then default + // back to the inherit setting + this.setStrokeWeight(element.getAttribute("stroke-width")); + } + if (element.hasAttribute("stroke-linejoin") ) { + this.setStrokeJoin(element.getAttribute("stroke-linejoin")); + } + if (element.hasAttribute("stroke-linecap")) { + this.setStrokeCap(element.getStringAttribute("stroke-linecap")); + } + // fill defaults to black (though stroke defaults to "none") + // http://www.w3.org/TR/SVG/painting.html#FillProperties + if (element.hasAttribute("fill")) { + this.setFill(element.getStringAttribute("fill")); + } + if (element.hasAttribute("style")) { + var styleText = element.getStringAttribute("style"); + var styleTokens = styleText.toString().split( ";" ); + + for (var i = 0, j = styleTokens.length; i < j; i++) { + var tokens = CommonFunctions.trim(styleTokens[i].split( ":" )); + if (tokens[0] === "fill") { + this.setFill(tokens[1]); + } else if (tokens[0] === "fill-opacity") { + this.setFillOpacity(tokens[1]); + } else if (tokens[0] === "stroke") { + this.setStroke(tokens[1]); + } else if (tokens[0] === "stroke-width") { + this.setStrokeWeight(tokens[1]); + } else if (tokens[0] === "stroke-linecap") { + this.setStrokeCap(tokens[1]); + } else if (tokens[0] === "stroke-linejoin") { + this.setStrokeJoin(tokens[1]); + } else if (tokens[0] === "stroke-opacity") { + this.setStrokeOpacity(tokens[1]); + } else if (tokens[0] === "opacity") { + this.setOpacity(tokens[1]); + } // Other attributes are not yet implemented + } + } + }; + /** + * @member PShapeSVG + * PShapeSVG.parseColors() helper function + * + * @param {String} opacityText the value of fillOpacity + * + * @see PShapeSVG#parseColors + */ + PShapeSVG.prototype.setFillOpacity = function(opacityText) { + this.fillOpacity = parseFloat(opacityText); + this.fillColor = this.fillOpacity * 255 << 24 | + this.fillColor & 0xFFFFFF; + }; + /** + * @member PShapeSVG + * PShapeSVG.parseColors() helper function + * + * @param {String} fillText the value of fill + * + * @see PShapeSVG#parseColors + */ + PShapeSVG.prototype.setFill = function (fillText) { + var opacityMask = this.fillColor & 0xFF000000; + if (fillText === "none") { + this.fill = false; + } else if (fillText.indexOf("#") === 0) { + this.fill = true; + if (fillText.length === 4) { + // convert #00F to #0000FF + fillText = fillText.replace(/#(.)(.)(.)/,"#$1$1$2$2$3$3"); + } + this.fillColor = opacityMask | + (parseInt(fillText.substring(1), 16 )) & + 0xFFFFFF; + } else if (fillText.indexOf("rgb") === 0) { + this.fill = true; + this.fillColor = opacityMask | this.parseRGB(fillText); + } else if (fillText.indexOf("url(#") === 0) { + this.fillName = fillText.substring(5, fillText.length - 1 ); + } else if (colors[fillText]) { + this.fill = true; + this.fillColor = opacityMask | + (parseInt(colors[fillText].substring(1), 16)) & + 0xFFFFFF; + } + }; + /** + * @member PShapeSVG + * PShapeSVG.parseColors() helper function + * + * @param {String} opacity the value of opacity + * + * @see PShapeSVG#parseColors + */ + PShapeSVG.prototype.setOpacity = function(opacity) { + this.strokeColor = parseFloat(opacity) * 255 << 24 | + this.strokeColor & 0xFFFFFF; + this.fillColor = parseFloat(opacity) * 255 << 24 | + this.fillColor & 0xFFFFFF; + }; + /** + * @member PShapeSVG + * PShapeSVG.parseColors() helper function + * + * @param {String} strokeText the value to set stroke to + * + * @see PShapeSVG#parseColors + */ + PShapeSVG.prototype.setStroke = function(strokeText) { + var opacityMask = this.strokeColor & 0xFF000000; + if (strokeText === "none") { + this.stroke = false; + } else if (strokeText.charAt( 0 ) === "#") { + this.stroke = true; + if (strokeText.length === 4) { + // convert #00F to #0000FF + strokeText = strokeText.replace(/#(.)(.)(.)/,"#$1$1$2$2$3$3"); + } + this.strokeColor = opacityMask | + (parseInt( strokeText.substring( 1 ), 16 )) & + 0xFFFFFF; + } else if (strokeText.indexOf( "rgb" ) === 0 ) { + this.stroke = true; + this.strokeColor = opacityMask | this.parseRGB(strokeText); + } else if (strokeText.indexOf( "url(#" ) === 0) { + this.strokeName = strokeText.substring(5, strokeText.length - 1); + } else if (colors[strokeText]) { + this.stroke = true; + this.strokeColor = opacityMask | + (parseInt(colors[strokeText].substring(1), 16)) & + 0xFFFFFF; + } + }; + /** + * @member PShapeSVG + * PShapeSVG.parseColors() helper function + * + * @param {String} weight the value to set strokeWeight to + * + * @see PShapeSVG#parseColors + */ + PShapeSVG.prototype.setStrokeWeight = function(weight) { + this.strokeWeight = this.parseUnitSize(weight); + }; + /** + * @member PShapeSVG + * PShapeSVG.parseColors() helper function + * + * @param {String} linejoin the value to set strokeJoin to + * + * @see PShapeSVG#parseColors + */ + PShapeSVG.prototype.setStrokeJoin = function(linejoin) { + if (linejoin === "miter") { + this.strokeJoin = PConstants.MITER; + + } else if (linejoin === "round") { + this.strokeJoin = PConstants.ROUND; + + } else if (linejoin === "bevel") { + this.strokeJoin = PConstants.BEVEL; + } + }; + /** + * @member PShapeSVG + * PShapeSVG.parseColors() helper function + * + * @param {String} linecap the value to set strokeCap to + * + * @see PShapeSVG#parseColors + */ + PShapeSVG.prototype.setStrokeCap = function (linecap) { + if (linecap === "butt") { + this.strokeCap = PConstants.SQUARE; + + } else if (linecap === "round") { + this.strokeCap = PConstants.ROUND; + + } else if (linecap === "square") { + this.strokeCap = PConstants.PROJECT; + } + }; + /** + * @member PShapeSVG + * PShapeSVG.parseColors() helper function + * + * @param {String} opacityText the value to set stroke opacity to + * + * @see PShapeSVG#parseColors + */ + PShapeSVG.prototype.setStrokeOpacity = function (opacityText) { + this.strokeOpacity = parseFloat(opacityText); + this.strokeColor = this.strokeOpacity * 255 << 24 | + this.strokeColor & + 0xFFFFFF; + }; + /** + * @member PShapeSVG + * The parseRGB() function parses an rbg() color string and returns a color int + * + * @param {String} color the color to parse in rbg() format + * + * @return {int} the equivalent color int + */ + PShapeSVG.prototype.parseRGB = function(color) { + var sub = color.substring(color.indexOf('(') + 1, color.indexOf(')')); + var values = sub.split(", "); + return (values[0] << 16) | (values[1] << 8) | (values[2]); + }; + /** + * @member PShapeSVG + * The parseUnitSize() function parse a size that may have a suffix for its units. + * Ignoring cases where this could also be a percentage. + * The <A HREF="http://www.w3.org/TR/SVG/coords.html#Units">units</A> spec: + * <UL> + * <LI>"1pt" equals "1.25px" (and therefore 1.25 user units) + * <LI>"1pc" equals "15px" (and therefore 15 user units) + * <LI>"1mm" would be "3.543307px" (3.543307 user units) + * <LI>"1cm" equals "35.43307px" (and therefore 35.43307 user units) + * <LI>"1in" equals "90px" (and therefore 90 user units) + * </UL> + */ + PShapeSVG.prototype.parseUnitSize = function (text) { + var len = text.length - 2; + if (len < 0) { return text; } + if (text.indexOf("pt") === len) { + return parseFloat(text.substring(0, len)) * 1.25; + } + if (text.indexOf("pc") === len) { + return parseFloat( text.substring( 0, len)) * 15; + } + if (text.indexOf("mm") === len) { + return parseFloat( text.substring(0, len)) * 3.543307; + } + if (text.indexOf("cm") === len) { + return parseFloat(text.substring(0, len)) * 35.43307; + } + if (text.indexOf("in") === len) { + return parseFloat(text.substring(0, len)) * 90; + } + if (text.indexOf("px") === len) { + return parseFloat(text.substring(0, len)); + } + return parseFloat(text); + }; + + return PShapeSVG; +}; + +},{}],18:[function(require,module,exports){ +module.exports = function(options, undef) { + var PConstants = options.PConstants; + + function PVector(x, y, z) { + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + } + + PVector.fromAngle = function(angle, v) { + if (v === undef || v === null) { + v = new PVector(); + } + v.x = Math.cos(angle); + v.y = Math.sin(angle); + return v; + }; + + PVector.random2D = function(v) { + return PVector.fromAngle(Math.random() * PConstants.TWO_PI, v); + }; + + PVector.random3D = function(v) { + var angle = Math.random() * PConstants.TWO_PI; + var vz = Math.random() * 2 - 1; + var mult = Math.sqrt(1 - vz * vz); + var vx = mult * Math.cos(angle); + var vy = mult * Math.sin(angle); + if (v === undef || v === null) { + v = new PVector(vx, vy, vz); + } else { + v.set(vx, vy, vz); + } + return v; + }; + + PVector.dist = function(v1, v2) { + return v1.dist(v2); + }; + + PVector.dot = function(v1, v2) { + return v1.dot(v2); + }; + + PVector.cross = function(v1, v2) { + return v1.cross(v2); + }; + + PVector.sub = function(v1, v2) { + return new PVector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + }; + + PVector.angleBetween = function(v1, v2) { + return Math.acos(v1.dot(v2) / Math.sqrt(v1.magSq() * v2.magSq())); + }; + + PVector.lerp = function(v1, v2, amt) { + // non-static lerp mutates object, but this version returns a new vector + var retval = new PVector(v1.x, v1.y, v1.z); + retval.lerp(v2, amt); + return retval; + }; + + // Common vector operations for PVector + PVector.prototype = { + set: function(v, y, z) { + if (arguments.length === 1) { + this.set(v.x || v[0] || 0, + v.y || v[1] || 0, + v.z || v[2] || 0); + } else { + this.x = v; + this.y = y; + this.z = z; + } + }, + get: function() { + return new PVector(this.x, this.y, this.z); + }, + mag: function() { + var x = this.x, + y = this.y, + z = this.z; + return Math.sqrt(x * x + y * y + z * z); + }, + magSq: function() { + var x = this.x, + y = this.y, + z = this.z; + return (x * x + y * y + z * z); + }, + setMag: function(v_or_len, len) { + if (len === undef) { + len = v_or_len; + this.normalize(); + this.mult(len); + } else { + var v = v_or_len; + v.normalize(); + v.mult(len); + return v; + } + }, + add: function(v, y, z) { + if (arguments.length === 1) { + this.x += v.x; + this.y += v.y; + this.z += v.z; + } else if (arguments.length === 2) { + // 2D Vector + this.x += v; + this.y += y; + } else { + this.x += v; + this.y += y; + this.z += z; + } + }, + sub: function(v, y, z) { + if (arguments.length === 1) { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + } else if (arguments.length === 2) { + // 2D Vector + this.x -= v; + this.y -= y; + } else { + this.x -= v; + this.y -= y; + this.z -= z; + } + }, + mult: function(v) { + if (typeof v === 'number') { + this.x *= v; + this.y *= v; + this.z *= v; + } else { + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; + } + }, + div: function(v) { + if (typeof v === 'number') { + this.x /= v; + this.y /= v; + this.z /= v; + } else { + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + } + }, + rotate: function(angle) { + var prev_x = this.x; + var c = Math.cos(angle); + var s = Math.sin(angle); + this.x = c * this.x - s * this.y; + this.y = s * prev_x + c * this.y; + }, + dist: function(v) { + var dx = this.x - v.x, + dy = this.y - v.y, + dz = this.z - v.z; + return Math.sqrt(dx * dx + dy * dy + dz * dz); + }, + dot: function(v, y, z) { + if (arguments.length === 1) { + return (this.x * v.x + this.y * v.y + this.z * v.z); + } + return (this.x * v + this.y * y + this.z * z); + }, + cross: function(v) { + var x = this.x, + y = this.y, + z = this.z; + return new PVector(y * v.z - v.y * z, + z * v.x - v.z * x, + x * v.y - v.x * y); + }, + lerp: function(v_or_x, amt_or_y, z, amt) { + var lerp_val = function(start, stop, amt) { + return start + (stop - start) * amt; + }; + var x, y; + if (arguments.length === 2) { + // given vector and amt + amt = amt_or_y; + x = v_or_x.x; + y = v_or_x.y; + z = v_or_x.z; + } else { + // given x, y, z and amt + x = v_or_x; + y = amt_or_y; + } + this.x = lerp_val(this.x, x, amt); + this.y = lerp_val(this.y, y, amt); + this.z = lerp_val(this.z, z, amt); + }, + normalize: function() { + var m = this.mag(); + if (m > 0) { + this.div(m); + } + }, + limit: function(high) { + if (this.mag() > high) { + this.normalize(); + this.mult(high); + } + }, + heading: function() { + return (-Math.atan2(-this.y, this.x)); + }, + heading2D: function() { + return this.heading(); + }, + toString: function() { + return "[" + this.x + ", " + this.y + ", " + this.z + "]"; + }, + array: function() { + return [this.x, this.y, this.z]; + } + }; + + function createPVectorMethod(method) { + return function(v1, v2) { + var v = v1.get(); + v[method](v2); + return v; + }; + } + + for (var method in PVector.prototype) { + if (PVector.prototype.hasOwnProperty(method) && !PVector.hasOwnProperty(method)) { + PVector[method] = createPVectorMethod(method); + } + } + + return PVector; +}; + +},{}],19:[function(require,module,exports){ +/** + * XMLAttribute is an attribute of a XML element. + * + * @param {String} fname the full name of the attribute + * @param {String} n the short name of the attribute + * @param {String} namespace the namespace URI of the attribute + * @param {String} v the value of the attribute + * @param {String }t the type of the attribute + * + * @see XMLElement + */ +module.exports = function() { + + var XMLAttribute = function (fname, n, nameSpace, v, t){ + this.fullName = fname || ""; + this.name = n || ""; + this.namespace = nameSpace || ""; + this.value = v; + this.type = t; + }; + + XMLAttribute.prototype = { + /** + * @member XMLAttribute + * The getName() function returns the short name of the attribute + * + * @return {String} the short name of the attribute + */ + getName: function() { + return this.name; + }, + /** + * @member XMLAttribute + * The getFullName() function returns the full name of the attribute + * + * @return {String} the full name of the attribute + */ + getFullName: function() { + return this.fullName; + }, + /** + * @member XMLAttribute + * The getNamespace() function returns the namespace of the attribute + * + * @return {String} the namespace of the attribute + */ + getNamespace: function() { + return this.namespace; + }, + /** + * @member XMLAttribute + * The getValue() function returns the value of the attribute + * + * @return {String} the value of the attribute + */ + getValue: function() { + return this.value; + }, + /** + * @member XMLAttribute + * The getValue() function returns the type of the attribute + * + * @return {String} the type of the attribute + */ + getType: function() { + return this.type; + }, + /** + * @member XMLAttribute + * The setValue() function sets the value of the attribute + * + * @param {String} newval the new value + */ + setValue: function(newval) { + this.value = newval; + } + }; + + return XMLAttribute; +}; + +},{}],20:[function(require,module,exports){ +/** + * XMLElement is a representation of an XML object. The object is able to parse XML code + * + * @param {PApplet} parent typically use "this" + * @param {String} filename name of the XML/SVG file to load + * @param {String} xml the xml/svg string + * @param {String} fullname the full name of the element + * @param {String} namespace the namespace of the URI + * @param {String} systemID the system ID of the XML data where the element starts + * @param {Integer }lineNr the line in the XML data where the element starts + */ +module.exports = function(options, undef) { + + var Browser = options.Browser, + ajax = Browser.ajax, + window = Browser.window, + XMLHttpRequest = window.XMLHttpRequest, + DOMParser = window.DOMParser, + XMLAttribute = options. XMLAttribute; + + var XMLElement = function(selector, uri, sysid, line) { + this.attributes = []; + this.children = []; + this.fullName = null; + this.name = null; + this.namespace = ""; + this.content = null; + this.parent = null; + this.lineNr = ""; + this.systemID = ""; + this.type = "ELEMENT"; + + if (selector) { + if (typeof selector === "string") { + if (uri === undef && selector.indexOf("<") > -1) { + // load XML from text string + this.parse(selector); + } else { + // XMLElement(fullname, namespace, sysid, line) format + this.fullName = selector; + this.namespace = uri; + this.systemId = sysid; + this.lineNr = line; + } + } else { + // XMLElement(this, file uri) format + this.parse(uri, true); + } + } + }; + /** + * XMLElement methods + * missing: enumerateAttributeNames(), enumerateChildren(), + * NOTE: parse does not work when a url is passed in + */ + XMLElement.prototype = { + /** + * @member XMLElement + * The parse() function retrieves the file via ajax() and uses DOMParser() + * parseFromString method to make an XML document + * @addon + * + * @param {String} filename name of the XML/SVG file to load + * + * @throws ExceptionType Error loading document + * + * @see XMLElement#parseChildrenRecursive + */ + parse: function(textstring, stringIsURI) { + var xmlDoc; + try { + if (stringIsURI) { + textstring = ajax(textstring); + } + xmlDoc = new DOMParser().parseFromString(textstring, "text/xml"); + var elements = xmlDoc.documentElement; + if (elements) { + this.parseChildrenRecursive(null, elements); + } else { + throw ("Error loading document"); + } + return this; + } catch(e) { + throw(e); + } + }, + /** + * @member XMLElement + * Internal helper function for parse(). + * Loops through the + * @addon + * + * @param {XMLElement} parent the parent node + * @param {XML document childNodes} elementpath the remaining nodes that need parsing + * + * @return {XMLElement} the new element and its children elements + */ + parseChildrenRecursive: function (parent, elementpath){ + var xmlelement, + xmlattribute, + tmpattrib, + l, m, + child; + if (!parent) { // this element is the root element + this.fullName = elementpath.localName; + this.name = elementpath.nodeName; + xmlelement = this; + } else { // this element has a parent + xmlelement = new XMLElement(elementpath.nodeName); + xmlelement.parent = parent; + } + + // if this is a text node, return a PCData element (parsed character data) + if (elementpath.nodeType === 3 && elementpath.textContent !== "") { + return this.createPCDataElement(elementpath.textContent); + } + + // if this is a CDATA node, return a CData element (unparsed character data) + if (elementpath.nodeType === 4) { + return this.createCDataElement(elementpath.textContent); + } + + // bind all attributes, if there are any + if (elementpath.attributes) { + for (l = 0, m = elementpath.attributes.length; l < m; l++) { + tmpattrib = elementpath.attributes[l]; + xmlattribute = new XMLAttribute(tmpattrib.getname, + tmpattrib.nodeName, + tmpattrib.namespaceURI, + tmpattrib.nodeValue, + tmpattrib.nodeType); + xmlelement.attributes.push(xmlattribute); + } + } + + // bind all children, if there are any + if (elementpath.childNodes) { + for (l = 0, m = elementpath.childNodes.length; l < m; l++) { + var node = elementpath.childNodes[l]; + child = xmlelement.parseChildrenRecursive(xmlelement, node); + if (child !== null) { + xmlelement.children.push(child); + } + } + } + + return xmlelement; + }, + /** + * @member XMLElement + * The createElement() function Creates an empty element + * + * @param {String} fullName the full name of the element + * @param {String} namespace the namespace URI + * @param {String} systemID the system ID of the XML data where the element starts + * @param {int} lineNr the line in the XML data where the element starts + */ + createElement: function (fullname, namespaceuri, sysid, line) { + if (sysid === undef) { + return new XMLElement(fullname, namespaceuri); + } + return new XMLElement(fullname, namespaceuri, sysid, line); + }, + /** + * @member XMLElement + * The createPCDataElement() function creates an element to be used for #PCDATA content. + * Because Processing discards whitespace TEXT nodes, this method will not build an element + * if the passed content is empty after trimming for whitespace. + * + * @return {XMLElement} new "pcdata" XMLElement, or null if content consists only of whitespace + */ + createPCDataElement: function (content, isCDATA) { + if (content.replace(/^\s+$/g,"") === "") { + return null; + } + var pcdata = new XMLElement(); + pcdata.type = "TEXT"; + pcdata.content = content; + return pcdata; + }, + /** + * @member XMLElement + * The createCDataElement() function creates an element to be used for CDATA content. + * + * @return {XMLElement} new "cdata" XMLElement, or null if content consists only of whitespace + */ + createCDataElement: function (content) { + var cdata = this.createPCDataElement(content); + if (cdata === null) { + return null; + } + + cdata.type = "CDATA"; + var htmlentities = {"<": "<", ">": ">", "'": "'", '"': """}, + entity; + for (entity in htmlentities) { + if (!Object.hasOwnProperty(htmlentities,entity)) { + content = content.replace(new RegExp(entity, "g"), htmlentities[entity]); + } + } + cdata.cdata = content; + return cdata; + }, + /** + * @member XMLElement + * The hasAttribute() function returns whether an attribute exists + * + * @param {String} name name of the attribute + * @param {String} namespace the namespace URI of the attribute + * + * @return {boolean} true if the attribute exists + */ + hasAttribute: function () { + if (arguments.length === 1) { + return this.getAttribute(arguments[0]) !== null; + } + if (arguments.length === 2) { + return this.getAttribute(arguments[0],arguments[1]) !== null; + } + }, + /** + * @member XMLElement + * The equals() function checks to see if the XMLElement being passed in equals another XMLElement + * + * @param {XMLElement} rawElement the element to compare to + * + * @return {boolean} true if the element equals another element + */ + equals: function(other) { + if (!(other instanceof XMLElement)) { + return false; + } + var i, j; + if (this.fullName !== other.fullName) { return false; } + if (this.attributes.length !== other.getAttributeCount()) { return false; } + // attributes may be ordered differently + if (this.attributes.length !== other.attributes.length) { return false; } + var attr_name, attr_ns, attr_value, attr_type, attr_other; + for (i = 0, j = this.attributes.length; i < j; i++) { + attr_name = this.attributes[i].getName(); + attr_ns = this.attributes[i].getNamespace(); + attr_other = other.findAttribute(attr_name, attr_ns); + if (attr_other === null) { return false; } + if (this.attributes[i].getValue() !== attr_other.getValue()) { return false; } + if (this.attributes[i].getType() !== attr_other.getType()) { return false; } + } + // children must be ordered identically + if (this.children.length !== other.getChildCount()) { return false; } + if (this.children.length>0) { + var child1, child2; + for (i = 0, j = this.children.length; i < j; i++) { + child1 = this.getChild(i); + child2 = other.getChild(i); + if (!child1.equals(child2)) { return false; } + } + return true; + } + return (this.content === other.content); + }, + /** + * @member XMLElement + * The getContent() function returns the content of an element. If there is no such content, null is returned + * + * @return {String} the (possibly null) content + */ + getContent: function(){ + if (this.type === "TEXT" || this.type === "CDATA") { + return this.content; + } + var children = this.children; + if (children.length === 1 && (children[0].type === "TEXT" || children[0].type === "CDATA")) { + return children[0].content; + } + return null; + }, + /** + * @member XMLElement + * The getAttribute() function returns the value of an attribute + * + * @param {String} name the non-null full name of the attribute + * @param {String} namespace the namespace URI, which may be null + * @param {String} defaultValue the default value of the attribute + * + * @return {String} the value, or defaultValue if the attribute does not exist + */ + getAttribute: function (){ + var attribute; + if (arguments.length === 2) { + attribute = this.findAttribute(arguments[0]); + if (attribute) { + return attribute.getValue(); + } + return arguments[1]; + } else if (arguments.length === 1) { + attribute = this.findAttribute(arguments[0]); + if (attribute) { + return attribute.getValue(); + } + return null; + } else if (arguments.length === 3) { + attribute = this.findAttribute(arguments[0],arguments[1]); + if (attribute) { + return attribute.getValue(); + } + return arguments[2]; + } + }, + /** + * @member XMLElement + * The getStringAttribute() function returns the string attribute of the element + * If the <b>defaultValue</b> parameter is used and the attribute doesn't exist, the <b>defaultValue</b> value is returned. + * When calling the function without the <b>defaultValue</b> parameter, if the attribute doesn't exist, the value 0 is returned. + * + * @param name the name of the attribute + * @param defaultValue value returned if the attribute is not found + * + * @return {String} the value, or defaultValue if the attribute does not exist + */ + getStringAttribute: function() { + if (arguments.length === 1) { + return this.getAttribute(arguments[0]); + } + if (arguments.length === 2) { + return this.getAttribute(arguments[0], arguments[1]); + } + return this.getAttribute(arguments[0], arguments[1],arguments[2]); + }, + /** + * Processing 1.5 XML API wrapper for the generic String + * attribute getter. This may only take one argument. + */ + getString: function(attributeName) { + return this.getStringAttribute(attributeName); + }, + /** + * @member XMLElement + * The getFloatAttribute() function returns the float attribute of the element. + * If the <b>defaultValue</b> parameter is used and the attribute doesn't exist, the <b>defaultValue</b> value is returned. + * When calling the function without the <b>defaultValue</b> parameter, if the attribute doesn't exist, the value 0 is returned. + * + * @param name the name of the attribute + * @param defaultValue value returned if the attribute is not found + * + * @return {float} the value, or defaultValue if the attribute does not exist + */ + getFloatAttribute: function() { + if (arguments.length === 1 ) { + return parseFloat(this.getAttribute(arguments[0], 0)); + } + if (arguments.length === 2 ) { + return this.getAttribute(arguments[0], arguments[1]); + } + return this.getAttribute(arguments[0], arguments[1],arguments[2]); + }, + /** + * Processing 1.5 XML API wrapper for the generic float + * attribute getter. This may only take one argument. + */ + getFloat: function(attributeName) { + return this.getFloatAttribute(attributeName); + }, + /** + * @member XMLElement + * The getIntAttribute() function returns the integer attribute of the element. + * If the <b>defaultValue</b> parameter is used and the attribute doesn't exist, the <b>defaultValue</b> value is returned. + * When calling the function without the <b>defaultValue</b> parameter, if the attribute doesn't exist, the value 0 is returned. + * + * @param name the name of the attribute + * @param defaultValue value returned if the attribute is not found + * + * @return {int} the value, or defaultValue if the attribute does not exist + */ + getIntAttribute: function () { + if (arguments.length === 1) { + return this.getAttribute( arguments[0], 0 ); + } + if (arguments.length === 2) { + return this.getAttribute(arguments[0], arguments[1]); + } + return this.getAttribute(arguments[0], arguments[1],arguments[2]); + }, + /** + * Processing 1.5 XML API wrapper for the generic int + * attribute getter. This may only take one argument. + */ + getInt: function(attributeName) { + return this.getIntAttribute(attributeName); + }, + /** + * @member XMLElement + * The hasChildren() function returns whether the element has children. + * + * @return {boolean} true if the element has children. + */ + hasChildren: function () { + return this.children.length > 0 ; + }, + /** + * @member XMLElement + * The addChild() function adds a child element + * + * @param {XMLElement} child the non-null child to add. + */ + addChild: function (child) { + if (child !== null) { + child.parent = this; + this.children.push(child); + } + }, + /** + * @member XMLElement + * The insertChild() function inserts a child element at the index provided + * + * @param {XMLElement} child the non-null child to add. + * @param {int} index where to put the child. + */ + insertChild: function (child, index) { + if (child) { + if ((child.getLocalName() === null) && (! this.hasChildren())) { + var lastChild = this.children[this.children.length -1]; + if (lastChild.getLocalName() === null) { + lastChild.setContent(lastChild.getContent() + child.getContent()); + return; + } + } + child.parent = this; + this.children.splice(index,0,child); + } + }, + /** + * @member XMLElement + * The getChild() returns the child XMLElement as specified by the <b>index</b> parameter. + * The value of the <b>index</b> parameter must be less than the total number of children to avoid going out of the array storing the child elements. + * When the <b>path</b> parameter is specified, then it will return all children that match that path. The path is a series of elements and sub-elements, separated by slashes. + * + * @param {int} index where to put the child. + * @param {String} path path to a particular element + * + * @return {XMLElement} the element + */ + getChild: function (selector) { + if (typeof selector === "number") { + return this.children[selector]; + } + if (selector.indexOf('/') !== -1) { + // path traversal is required + return this.getChildRecursive(selector.split("/"), 0); + } + var kid, kidName; + for (var i = 0, j = this.getChildCount(); i < j; i++) { + kid = this.getChild(i); + kidName = kid.getName(); + if (kidName !== null && kidName === selector) { + return kid; + } + } + return null; + }, + /** + * @member XMLElement + * The getChildren() returns all of the children as an XMLElement array. + * When the <b>path</b> parameter is specified, then it will return all children that match that path. + * The path is a series of elements and sub-elements, separated by slashes. + * + * @param {String} path element name or path/to/element + * + * @return {XMLElement} array of child elements that match + * + * @see XMLElement#getChildCount() + * @see XMLElement#getChild() + */ + getChildren: function(){ + if (arguments.length === 1) { + if (typeof arguments[0] === "number") { + return this.getChild( arguments[0]); + } + if (arguments[0].indexOf('/') !== -1) { // path was given + return this.getChildrenRecursive( arguments[0].split("/"), 0); + } + var matches = []; + var kid, kidName; + for (var i = 0, j = this.getChildCount(); i < j; i++) { + kid = this.getChild(i); + kidName = kid.getName(); + if (kidName !== null && kidName === arguments[0]) { + matches.push(kid); + } + } + return matches; + } + return this.children; + }, + /** + * @member XMLElement + * The getChildCount() returns the number of children for the element. + * + * @return {int} the count + * + * @see XMLElement#getChild() + * @see XMLElement#getChildren() + */ + getChildCount: function() { + return this.children.length; + }, + /** + * @member XMLElement + * Internal helper function for getChild(). + * + * @param {String[]} items result of splitting the query on slashes + * @param {int} offset where in the items[] array we're currently looking + * + * @return {XMLElement} matching element or null if no match + */ + getChildRecursive: function (items, offset) { + // terminating clause: we are the requested candidate + if (offset === items.length) { + return this; + } + // continuation clause + var kid, kidName, matchName = items[offset]; + for(var i = 0, j = this.getChildCount(); i < j; i++) { + kid = this.getChild(i); + kidName = kid.getName(); + if (kidName !== null && kidName === matchName) { + return kid.getChildRecursive(items, offset+1); + } + } + return null; + }, + /** + * @member XMLElement + * Internal helper function for getChildren(). + * + * @param {String[]} items result of splitting the query on slashes + * @param {int} offset where in the items[] array we're currently looking + * + * @return {XMLElement[]} matching elements or empty array if no match + */ + getChildrenRecursive: function (items, offset) { + if (offset === items.length-1) { + return this.getChildren(items[offset]); + } + var matches = this.getChildren(items[offset]); + var kidMatches = []; + for (var i = 0; i < matches.length; i++) { + kidMatches = kidMatches.concat(matches[i].getChildrenRecursive(items, offset+1)); + } + return kidMatches; + }, + /** + * @member XMLElement + * The isLeaf() function returns whether the element is a leaf element. + * + * @return {boolean} true if the element has no children. + */ + isLeaf: function() { + return !this.hasChildren(); + }, + /** + * @member XMLElement + * The listChildren() function put the names of all children into an array. Same as looping through + * each child and calling getName() on each XMLElement. + * + * @return {String[]} a list of element names. + */ + listChildren: function() { + var arr = []; + for (var i = 0, j = this.children.length; i < j; i++) { + arr.push( this.getChild(i).getName()); + } + return arr; + }, + /** + * @member XMLElement + * The removeAttribute() function removes an attribute + * + * @param {String} name the non-null name of the attribute. + * @param {String} namespace the namespace URI of the attribute, which may be null. + */ + removeAttribute: function (name , namespace) { + this.namespace = namespace || ""; + for (var i = 0, j = this.attributes.length; i < j; i++) { + if (this.attributes[i].getName() === name && this.attributes[i].getNamespace() === this.namespace) { + this.attributes.splice(i, 1); + break; + } + } + }, + /** + * @member XMLElement + * The removeChild() removes a child element. + * + * @param {XMLElement} child the the non-null child to be renoved + */ + removeChild: function(child) { + if (child) { + for (var i = 0, j = this.children.length; i < j; i++) { + if (this.children[i].equals(child)) { + this.children.splice(i, 1); + break; + } + } + } + }, + /** + * @member XMLElement + * The removeChildAtIndex() removes the child located at a certain index + * + * @param {int} index the index of the child, where the first child has index 0 + */ + removeChildAtIndex: function(index) { + if (this.children.length > index) { //make sure its not outofbounds + this.children.splice(index, 1); + } + }, + /** + * @member XMLElement + * The findAttribute() function searches an attribute + * + * @param {String} name fullName the non-null full name of the attribute + * @param {String} namespace the name space, which may be null + * + * @return {XMLAttribute} the attribute, or null if the attribute does not exist. + */ + findAttribute: function (name, namespace) { + this.namespace = namespace || ""; + for (var i = 0, j = this.attributes.length; i < j; i++) { + if (this.attributes[i].getName() === name && this.attributes[i].getNamespace() === this.namespace) { + return this.attributes[i]; + } + } + return null; + }, + /** + * @member XMLElement + * The setAttribute() function sets an attribute. + * + * @param {String} name the non-null full name of the attribute + * @param {String} namespace the non-null value of the attribute + */ + setAttribute: function() { + var attr; + if (arguments.length === 3) { + var index = arguments[0].indexOf(':'); + var name = arguments[0].substring(index + 1); + attr = this.findAttribute(name, arguments[1]); + if (attr) { + attr.setValue(arguments[2]); + } else { + attr = new XMLAttribute(arguments[0], name, arguments[1], arguments[2], "CDATA"); + this.attributes.push(attr); + } + } else { + attr = this.findAttribute(arguments[0]); + if (attr) { + attr.setValue(arguments[1]); + } else { + attr = new XMLAttribute(arguments[0], arguments[0], null, arguments[1], "CDATA"); + this.attributes.push(attr); + } + } + }, + /** + * Processing 1.5 XML API wrapper for the generic String + * attribute setter. This must take two arguments. + */ + setString: function(attribute, value) { + this.setAttribute(attribute, value); + }, + /** + * Processing 1.5 XML API wrapper for the generic int + * attribute setter. This must take two arguments. + */ + setInt: function(attribute, value) { + this.setAttribute(attribute, value); + }, + /** + * Processing 1.5 XML API wrapper for the generic float + * attribute setter. This must take two arguments. + */ + setFloat: function(attribute, value) { + this.setAttribute(attribute, value); + }, + /** + * @member XMLElement + * The setContent() function sets the #PCDATA content. It is an error to call this method with a + * non-null value if there are child objects. + * + * @param {String} content the (possibly null) content + */ + setContent: function(content) { + if (this.children.length > 0) { + Processing.debug("Tried to set content for XMLElement with children"); } + this.content = content; + }, + /** + * @member XMLElement + * The setName() function sets the full name. This method also sets the short name and clears the + * namespace URI. + * + * @param {String} name the non-null name + * @param {String} namespace the namespace URI, which may be null. + */ + setName: function() { + if (arguments.length === 1) { + this.name = arguments[0]; + this.fullName = arguments[0]; + this.namespace = null; + } else { + var index = arguments[0].indexOf(':'); + if ((arguments[1] === null) || (index < 0)) { + this.name = arguments[0]; + } else { + this.name = arguments[0].substring(index + 1); + } + this.fullName = arguments[0]; + this.namespace = arguments[1]; + } + }, + /** + * @member XMLElement + * The getName() function returns the full name (i.e. the name including an eventual namespace + * prefix) of the element. + * + * @return {String} the name, or null if the element only contains #PCDATA. + */ + getName: function() { + return this.fullName; + }, + /** + * @member XMLElement + * The getLocalName() function returns the local name (i.e. the name excluding an eventual namespace + * prefix) of the element. + * + * @return {String} the name, or null if the element only contains #PCDATA. + */ + getLocalName: function() { + return this.name; + }, + /** + * @member XMLElement + * The getAttributeCount() function returns the number of attributes for the node + * that this XMLElement represents. + * + * @return {int} the number of attributes in this XMLElement + */ + getAttributeCount: function() { + return this.attributes.length; + }, + /** + * @member XMLElement + * The toString() function returns the XML definition of an XMLElement. + * + * @return {String} the XML definition of this XMLElement + */ + toString: function() { + // shortcut for text and cdata nodes + if (this.type === "TEXT") { + return this.content || ""; + } + + if (this.type === "CDATA") { + return this.cdata || ""; + } + + // real XMLElements + var tagstring = this.fullName; + var xmlstring = "<" + tagstring; + var a,c; + + // serialize the attributes to XML string + for (a = 0; a<this.attributes.length; a++) { + var attr = this.attributes[a]; + xmlstring += " " + attr.getName() + "=" + '"' + attr.getValue() + '"'; + } + + // serialize all children to XML string + if (this.children.length === 0) { + if (this.content === "" || this.content === null || this.content === undefined) { + xmlstring += "/>"; + } else { + xmlstring += ">" + this.content + "</"+tagstring+">"; + } + } else { + xmlstring += ">"; + for (c = 0; c<this.children.length; c++) { + xmlstring += this.children[c].toString(); + } + xmlstring += "</" + tagstring + ">"; + } + return xmlstring; + } + }; + + /** + * static Processing 1.5 XML API wrapper for the + * parse method. This may only take one argument. + */ + XMLElement.parse = function(xmlstring) { + var element = new XMLElement(); + element.parse(xmlstring); + return element; + }; + + return XMLElement; +}; + +},{}],21:[function(require,module,exports){ +/** + * web colors, by name + */ +module.exports = { + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aqua: "#00ffff", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + black: "#000000", + blanchedalmond: "#ffebcd", + blue: "#0000ff", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + fuchsia: "#ff00ff", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + gray: "#808080", + green: "#008000", + greenyellow: "#adff2f", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgrey: "#d3d3d3", + lightgreen: "#90ee90", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + lime: "#00ff00", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + maroon: "#800000", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370d8", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + navy: "#000080", + oldlace: "#fdf5e6", + olive: "#808000", + olivedrab: "#6b8e23", + orange: "#ffa500", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#d87093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + purple: "#800080", + red: "#ff0000", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + silver: "#c0c0c0", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + teal: "#008080", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + white: "#ffffff", + whitesmoke: "#f5f5f5", + yellow: "#ffff00", + yellowgreen: "#9acd32" + }; + +},{}],22:[function(require,module,exports){ +module.exports = function(virtHashCode, virtEquals, undef) { + + return function withProxyFunctions(p, removeFirstArgument) { + /** + * The contains(string) function returns true if the string passed in the parameter + * is a substring of this string. It returns false if the string passed + * in the parameter is not a substring of this string. + * + * @param {String} The string to look for in the current string + * + * @return {boolean} returns true if this string contains + * the string passed as parameter. returns false, otherwise. + * + */ + p.__contains = function (subject, subStr) { + if (typeof subject !== "string") { + return subject.contains.apply(subject, removeFirstArgument(arguments)); + } + //Parameter is not null AND + //The type of the parameter is the same as this object (string) + //The javascript function that finds a substring returns 0 or higher + return ( + (subject !== null) && + (subStr !== null) && + (typeof subStr === "string") && + (subject.indexOf(subStr) > -1) + ); + }; + + /** + * The __replaceAll() function searches all matches between a substring (or regular expression) and a string, + * and replaces the matched substring with a new substring + * + * @param {String} subject a substring + * @param {String} regex a substring or a regular expression + * @param {String} replace the string to replace the found value + * + * @return {String} returns result + * + * @see #match + */ + p.__replaceAll = function(subject, regex, replacement) { + if (typeof subject !== "string") { + return subject.replaceAll.apply(subject, removeFirstArgument(arguments)); + } + + return subject.replace(new RegExp(regex, "g"), replacement); + }; + + /** + * The __replaceFirst() function searches first matche between a substring (or regular expression) and a string, + * and replaces the matched substring with a new substring + * + * @param {String} subject a substring + * @param {String} regex a substring or a regular expression + * @param {String} replace the string to replace the found value + * + * @return {String} returns result + * + * @see #match + */ + p.__replaceFirst = function(subject, regex, replacement) { + if (typeof subject !== "string") { + return subject.replaceFirst.apply(subject, removeFirstArgument(arguments)); + } + + return subject.replace(new RegExp(regex, ""), replacement); + }; + + /** + * The __replace() function searches all matches between a substring and a string, + * and replaces the matched substring with a new substring + * + * @param {String} subject a substring + * @param {String} what a substring to find + * @param {String} replacement the string to replace the found value + * + * @return {String} returns result + */ + p.__replace = function(subject, what, replacement) { + if (typeof subject !== "string") { + return subject.replace.apply(subject, removeFirstArgument(arguments)); + } + if (what instanceof RegExp) { + return subject.replace(what, replacement); + } + + if (typeof what !== "string") { + what = what.toString(); + } + if (what === "") { + return subject; + } + + var i = subject.indexOf(what); + if (i < 0) { + return subject; + } + + var j = 0, result = ""; + do { + result += subject.substring(j, i) + replacement; + j = i + what.length; + } while ( (i = subject.indexOf(what, j)) >= 0); + return result + subject.substring(j); + }; + + /** + * The __equals() function compares two strings (or objects) to see if they are the same. + * This method is necessary because it's not possible to compare strings using the equality operator (==). + * Returns true if the strings are the same and false if they are not. + * + * @param {String} subject a string used for comparison + * @param {String} other a string used for comparison with + * + * @return {boolean} true is the strings are the same false otherwise + */ + p.__equals = function(subject, other) { + if (subject.equals instanceof Function) { + return subject.equals.apply(subject, removeFirstArgument(arguments)); + } + + return virtEquals(subject, other); + }; + + /** + * The __equalsIgnoreCase() function compares two strings to see if they are the same. + * Returns true if the strings are the same, either when forced to all lower case or + * all upper case. + * + * @param {String} subject a string used for comparison + * @param {String} other a string used for comparison with + * + * @return {boolean} true is the strings are the same, ignoring case. false otherwise + */ + p.__equalsIgnoreCase = function(subject, other) { + if (typeof subject !== "string") { + return subject.equalsIgnoreCase.apply(subject, removeFirstArgument(arguments)); + } + + return subject.toLowerCase() === other.toLowerCase(); + }; + + /** + * The __toCharArray() function splits the string into a char array. + * + * @param {String} subject The string + * + * @return {Char[]} a char array + */ + p.__toCharArray = function(subject) { + if (typeof subject !== "string") { + return subject.toCharArray.apply(subject, removeFirstArgument(arguments)); + } + + var chars = []; + for (var i = 0, len = subject.length; i < len; ++i) { + chars[i] = new Char(subject.charAt(i)); + } + return chars; + }; + + /** + * The __split() function splits a string using the regex delimiter + * specified. If limit is specified, the resultant array will have number + * of elements equal to or less than the limit. + * + * @param {String} subject string to be split + * @param {String} regexp regex string used to split the subject + * @param {int} limit max number of tokens to be returned + * + * @return {String[]} an array of tokens from the split string + */ + p.__split = function(subject, regex, limit) { + if (typeof subject !== "string") { + return subject.split.apply(subject, removeFirstArgument(arguments)); + } + + var pattern = new RegExp(regex); + + // If limit is not specified, use JavaScript's built-in String.split. + if ((limit === undef) || (limit < 1)) { + return subject.split(pattern); + } + + // If limit is specified, JavaScript's built-in String.split has a + // different behaviour than Java's. A Java-compatible implementation is + // provided here. + var result = [], currSubject = subject, pos; + while (((pos = currSubject.search(pattern)) !== -1) && (result.length < (limit - 1))) { + var match = pattern.exec(currSubject).toString(); + result.push(currSubject.substring(0, pos)); + currSubject = currSubject.substring(pos + match.length); + } + if ((pos !== -1) || (currSubject !== "")) { + result.push(currSubject); + } + return result; + }; + + /** + * The codePointAt() function returns the unicode value of the character at a given index of a string. + * + * @param {int} idx the index of the character + * + * @return {String} code the String containing the unicode value of the character + */ + p.__codePointAt = function(subject, idx) { + var code = subject.charCodeAt(idx), + hi, + low; + if (0xD800 <= code && code <= 0xDBFF) { + hi = code; + low = subject.charCodeAt(idx + 1); + return ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; + } + return code; + }; + + /** + * The matches() function checks whether or not a string matches a given regular expression. + * + * @param {String} str the String on which the match is tested + * @param {String} regexp the regexp for which a match is tested + * + * @return {boolean} true if the string fits the regexp, false otherwise + */ + p.__matches = function(str, regexp) { + return (new RegExp(regexp)).test(str); + }; + + /** + * The startsWith() function tests if a string starts with the specified prefix. If the prefix + * is the empty String or equal to the subject String, startsWith() will also return true. + * + * @param {String} prefix the String used to compare against the start of the subject String. + * @param {int} toffset (optional) an offset into the subject String where searching should begin. + * + * @return {boolean} true if the subject String starts with the prefix. + */ + p.__startsWith = function(subject, prefix, toffset) { + if (typeof subject !== "string") { + return subject.startsWith.apply(subject, removeFirstArgument(arguments)); + } + + toffset = toffset || 0; + if (toffset < 0 || toffset > subject.length) { + return false; + } + return (prefix === '' || prefix === subject) ? true : (subject.indexOf(prefix) === toffset); + }; + + /** + * The endsWith() function tests if a string ends with the specified suffix. If the suffix + * is the empty String, endsWith() will also return true. + * + * @param {String} suffix the String used to compare against the end of the subject String. + * + * @return {boolean} true if the subject String starts with the prefix. + */ + p.__endsWith = function(subject, suffix) { + if (typeof subject !== "string") { + return subject.endsWith.apply(subject, removeFirstArgument(arguments)); + } + + var suffixLen = suffix ? suffix.length : 0; + return (suffix === '' || suffix === subject) ? true : + (subject.indexOf(suffix) === subject.length - suffixLen); + }; + + /** + * The returns hash code of the. + * + * @param {Object} subject The string + * + * @return {int} a hash code + */ + p.__hashCode = function(subject) { + if (subject.hashCode instanceof Function) { + return subject.hashCode.apply(subject, removeFirstArgument(arguments)); + } + return virtHashCode(subject); + }; + + /** + * The __printStackTrace() prints stack trace to the console. + * + * @param {Exception} subject The error + */ + p.__printStackTrace = function(subject) { + p.println("Exception: " + subject.toString() ); + }; + }; + +}; + +},{}],23:[function(require,module,exports){ +/** + * For many "math" functions, we can delegate + * to the Math object. For others, we can't. + */ +module.exports = function withMath(p, undef) { + var internalRandomGenerator = function() { return Math.random(); }; + + /** + * Calculates the absolute value (magnitude) of a number. The absolute value of a number is always positive. + * + * @param {int|float} value int or float + * + * @returns {int|float} + */ + p.abs = Math.abs; + + /** + * Calculates the closest int value that is greater than or equal to the value of the parameter. + * For example, ceil(9.03) returns the value 10. + * + * @param {float} value float + * + * @returns {int} + * + * @see floor + * @see round + */ + p.ceil = Math.ceil; + + /** + * Returns Euler's number e (2.71828...) raised to the power of the value parameter. + * + * @param {int|float} value int or float: the exponent to raise e to + * + * @returns {float} + */ + p.exp = Math.exp; + + /** + * Calculates the closest int value that is less than or equal to the value of the parameter. + * + * @param {int|float} value the value to floor + * + * @returns {int|float} + * + * @see ceil + * @see round + */ + p.floor = Math.floor; + + /** + * Calculates the natural logarithm (the base-e logarithm) of a number. This function + * expects the values greater than 0.0. + * + * @param {int|float} value int or float: number must be greater then 0.0 + * + * @returns {float} + */ + p.log = Math.log; + + /** + * Facilitates exponential expressions. The pow() function is an efficient way of + * multiplying numbers by themselves (or their reciprocal) in large quantities. + * For example, pow(3, 5) is equivalent to the expression 3*3*3*3*3 and pow(3, -5) + * is equivalent to 1 / 3*3*3*3*3. + * + * @param {int|float} num base of the exponential expression + * @param {int|float} exponent power of which to raise the base + * + * @returns {float} + * + * @see sqrt + */ + p.pow = Math.pow; + + /** + * Calculates the integer closest to the value parameter. For example, round(9.2) returns the value 9. + * + * @param {float} value number to round + * + * @returns {int} + * + * @see floor + * @see ceil + */ + p.round = Math.round; + /** + * Calculates the square root of a number. The square root of a number is always positive, + * even though there may be a valid negative root. The square root s of number a is such + * that s*s = a. It is the opposite of squaring. + * + * @param {float} value int or float, non negative + * + * @returns {float} + * + * @see pow + * @see sq + */ + + p.sqrt = Math.sqrt; + + // Trigonometry + /** + * The inverse of cos(), returns the arc cosine of a value. This function expects the + * values in the range of -1 to 1 and values are returned in the range 0 to PI (3.1415927). + * + * @param {float} value the value whose arc cosine is to be returned + * + * @returns {float} + * + * @see cos + * @see asin + * @see atan + */ + p.acos = Math.acos; + + /** + * The inverse of sin(), returns the arc sine of a value. This function expects the values + * in the range of -1 to 1 and values are returned in the range -PI/2 to PI/2. + * + * @param {float} value the value whose arc sine is to be returned + * + * @returns {float} + * + * @see sin + * @see acos + * @see atan + */ + p.asin = Math.asin; + + /** + * The inverse of tan(), returns the arc tangent of a value. This function expects the values + * in the range of -Infinity to Infinity (exclusive) and values are returned in the range -PI/2 to PI/2 . + * + * @param {float} value -Infinity to Infinity (exclusive) + * + * @returns {float} + * + * @see tan + * @see asin + * @see acos + */ + p.atan = Math.atan; + + /** + * Calculates the angle (in radians) from a specified point to the coordinate origin as measured from + * the positive x-axis. Values are returned as a float in the range from PI to -PI. The atan2() function + * is most often used for orienting geometry to the position of the cursor. Note: The y-coordinate of the + * point is the first parameter and the x-coordinate is the second due the the structure of calculating the tangent. + * + * @param {float} y y-coordinate of the point + * @param {float} x x-coordinate of the point + * + * @returns {float} + * + * @see tan + */ + p.atan2 = Math.atan2; + + /** + * Calculates the cosine of an angle. This function expects the values of the angle parameter to be provided + * in radians (values from 0 to PI*2). Values are returned in the range -1 to 1. + * + * @param {float} value an angle in radians + * + * @returns {float} + * + * @see tan + * @see sin + */ + p.cos = Math.cos; + + /** + * Calculates the sine of an angle. This function expects the values of the angle parameter to be provided in + * radians (values from 0 to 6.28). Values are returned in the range -1 to 1. + * + * @param {float} value an angle in radians + * + * @returns {float} + * + * @see cos + * @see radians + */ + p.sin = Math.sin; + + /** + * Calculates the ratio of the sine and cosine of an angle. This function expects the values of the angle + * parameter to be provided in radians (values from 0 to PI*2). Values are returned in the range infinity to -infinity. + * + * @param {float} value an angle in radians + * + * @returns {float} + * + * @see cos + * @see sin + * @see radians + */ + p.tan = Math.tan; + + /** + * Constrains a value to not exceed a maximum and minimum value. + * + * @param {int|float} value the value to constrain + * @param {int|float} value minimum limit + * @param {int|float} value maximum limit + * + * @returns {int|float} + * + * @see max + * @see min + */ + p.constrain = function(aNumber, aMin, aMax) { + return aNumber > aMax ? aMax : aNumber < aMin ? aMin : aNumber; + }; + + /** + * Calculates the distance between two points. + * + * @param {int|float} x1 int or float: x-coordinate of the first point + * @param {int|float} y1 int or float: y-coordinate of the first point + * @param {int|float} z1 int or float: z-coordinate of the first point + * @param {int|float} x2 int or float: x-coordinate of the second point + * @param {int|float} y2 int or float: y-coordinate of the second point + * @param {int|float} z2 int or float: z-coordinate of the second point + * + * @returns {float} + */ + p.dist = function() { + var dx, dy, dz; + if (arguments.length === 4) { + dx = arguments[0] - arguments[2]; + dy = arguments[1] - arguments[3]; + return Math.sqrt(dx * dx + dy * dy); + } + if (arguments.length === 6) { + dx = arguments[0] - arguments[3]; + dy = arguments[1] - arguments[4]; + dz = arguments[2] - arguments[5]; + return Math.sqrt(dx * dx + dy * dy + dz * dz); + } + }; + + /** + * Calculates a number between two numbers at a specific increment. The amt parameter is the + * amount to interpolate between the two values where 0.0 equal to the first point, 0.1 is very + * near the first point, 0.5 is half-way in between, etc. The lerp function is convenient for + * creating motion along a straight path and for drawing dotted lines. + * + * @param {int|float} value1 float or int: first value + * @param {int|float} value2 float or int: second value + * @param {int|float} amt float: between 0.0 and 1.0 + * + * @returns {float} + * + * @see curvePoint + * @see bezierPoint + */ + p.lerp = function(value1, value2, amt) { + return ((value2 - value1) * amt) + value1; + }; + + /** + * Calculates the magnitude (or length) of a vector. A vector is a direction in space commonly + * used in computer graphics and linear algebra. Because it has no "start" position, the magnitude + * of a vector can be thought of as the distance from coordinate (0,0) to its (x,y) value. + * Therefore, mag() is a shortcut for writing "dist(0, 0, x, y)". + * + * @param {int|float} a float or int: first value + * @param {int|float} b float or int: second value + * @param {int|float} c float or int: third value + * + * @returns {float} + * + * @see dist + */ + p.mag = function(a, b, c) { + if (c) { + return Math.sqrt(a * a + b * b + c * c); + } + + return Math.sqrt(a * a + b * b); + }; + + /** + * Re-maps a number from one range to another. In the example above, the number '25' is converted from + * a value in the range 0..100 into a value that ranges from the left edge (0) to the right edge (width) of the screen. + * Numbers outside the range are not clamped to 0 and 1, because out-of-range values are often intentional and useful. + * + * @param {float} value The incoming value to be converted + * @param {float} istart Lower bound of the value's current range + * @param {float} istop Upper bound of the value's current range + * @param {float} ostart Lower bound of the value's target range + * @param {float} ostop Upper bound of the value's target range + * + * @returns {float} + * + * @see norm + * @see lerp + */ + p.map = function(value, istart, istop, ostart, ostop) { + return ostart + (ostop - ostart) * ((value - istart) / (istop - istart)); + }; + + /** + * Determines the largest value in a sequence of numbers. + * + * @param {int|float} value1 int or float + * @param {int|float} value2 int or float + * @param {int|float} value3 int or float + * @param {int|float} array int or float array + * + * @returns {int|float} + * + * @see min + */ + p.max = function() { + if (arguments.length === 2) { + return arguments[0] < arguments[1] ? arguments[1] : arguments[0]; + } + var numbers = arguments.length === 1 ? arguments[0] : arguments; // if single argument, array is used + if (! ("length" in numbers && numbers.length > 0)) { + throw "Non-empty array is expected"; + } + var max = numbers[0], + count = numbers.length; + for (var i = 1; i < count; ++i) { + if (max < numbers[i]) { + max = numbers[i]; + } + } + return max; + }; + + /** + * Determines the smallest value in a sequence of numbers. + * + * @param {int|float} value1 int or float + * @param {int|float} value2 int or float + * @param {int|float} value3 int or float + * @param {int|float} array int or float array + * + * @returns {int|float} + * + * @see max + */ + p.min = function() { + if (arguments.length === 2) { + return arguments[0] < arguments[1] ? arguments[0] : arguments[1]; + } + var numbers = arguments.length === 1 ? arguments[0] : arguments; // if single argument, array is used + if (! ("length" in numbers && numbers.length > 0)) { + throw "Non-empty array is expected"; + } + var min = numbers[0], + count = numbers.length; + for (var i = 1; i < count; ++i) { + if (min > numbers[i]) { + min = numbers[i]; + } + } + return min; + }; + + /** + * Normalizes a number from another range into a value between 0 and 1. + * Identical to map(value, low, high, 0, 1); + * Numbers outside the range are not clamped to 0 and 1, because out-of-range + * values are often intentional and useful. + * + * @param {float} aNumber The incoming value to be converted + * @param {float} low Lower bound of the value's current range + * @param {float} high Upper bound of the value's current range + * + * @returns {float} + * + * @see map + * @see lerp + */ + p.norm = function(aNumber, low, high) { + return (aNumber - low) / (high - low); + }; + + /** + * Squares a number (multiplies a number by itself). The result is always a positive number, + * as multiplying two negative numbers always yields a positive result. For example, -1 * -1 = 1. + * + * @param {float} value int or float + * + * @returns {float} + * + * @see sqrt + */ + p.sq = function(aNumber) { + return aNumber * aNumber; + }; + + /** + * Converts a radian measurement to its corresponding value in degrees. Radians and degrees are two ways of + * measuring the same thing. There are 360 degrees in a circle and 2*PI radians in a circle. For example, + * 90 degrees = PI/2 = 1.5707964. All trigonometric methods in Processing require their parameters to be specified in radians. + * + * @param {int|float} value an angle in radians + * + * @returns {float} + * + * @see radians + */ + p.degrees = function(aAngle) { + return (aAngle * 180) / Math.PI; + }; + + /** + * Generates random numbers. Each time the random() function is called, it returns an unexpected value within + * the specified range. If one parameter is passed to the function it will return a float between zero and the + * value of the high parameter. The function call random(5) returns values between 0 and 5 (starting at zero, + * up to but not including 5). If two parameters are passed, it will return a float with a value between the + * parameters. The function call random(-5, 10.2) returns values starting at -5 up to (but not including) 10.2. + * To convert a floating-point random number to an integer, use the int() function. + * + * @param {int|float} value1 if one parameter is used, the top end to random from, if two params the low end + * @param {int|float} value2 the top end of the random range + * + * @returns {float} + * + * @see randomSeed + * @see noise + */ + p.random = function() { + if(arguments.length === 0) { + return internalRandomGenerator(); + } + if(arguments.length === 1) { + return internalRandomGenerator() * arguments[0]; + } + var aMin = arguments[0], aMax = arguments[1]; + return internalRandomGenerator() * (aMax - aMin) + aMin; + }; + + // Pseudo-random generator + function Marsaglia(i1, i2) { + // from http://www.math.uni-bielefeld.de/~sillke/ALGORITHMS/random/marsaglia-c + var z=i1 || 362436069, w= i2 || 521288629; + var intGenerator = function() { + z=(36969*(z&65535)+(z>>>16)) & 0xFFFFFFFF; + w=(18000*(w&65535)+(w>>>16)) & 0xFFFFFFFF; + return (((z&0xFFFF)<<16) | (w&0xFFFF)) & 0xFFFFFFFF; + }; + + this.doubleGenerator = function() { + var i = intGenerator() / 4294967296; + return i < 0 ? 1 + i : i; + }; + this.intGenerator = intGenerator; + } + + Marsaglia.createRandomized = function() { + var now = new Date(); + return new Marsaglia((now / 60000) & 0xFFFFFFFF, now & 0xFFFFFFFF); + }; + + /** + * Sets the seed value for random(). By default, random() produces different results each time the + * program is run. Set the value parameter to a constant to return the same pseudo-random numbers + * each time the software is run. + * + * @param {int|float} seed int + * + * @see random + * @see noise + * @see noiseSeed + */ + p.randomSeed = function(seed) { + internalRandomGenerator = (new Marsaglia(seed, (seed<<16)+(seed>>16))).doubleGenerator; + this.haveNextNextGaussian = false; + }; + + /** + * Returns a float from a random series of numbers having a mean of 0 and standard deviation of 1. Each time + * the randomGaussian() function is called, it returns a number fitting a Gaussian, or normal, distribution. + * There is theoretically no minimum or maximum value that randomGaussian() might return. Rather, there is just a + * very low probability that values far from the mean will be returned; and a higher probability that numbers + * near the mean will be returned. + * + * @returns {float} + * + * @see random + * @see noise + */ + p.randomGaussian = function() { + if (this.haveNextNextGaussian) { + this.haveNextNextGaussian = false; + return this.nextNextGaussian; + } + var v1, v2, s; + do { + v1 = 2 * internalRandomGenerator() - 1; // between -1.0 and 1.0 + v2 = 2 * internalRandomGenerator() - 1; // between -1.0 and 1.0 + s = v1 * v1 + v2 * v2; + } + while (s >= 1 || s === 0); + + var multiplier = Math.sqrt(-2 * Math.log(s) / s); + this.nextNextGaussian = v2 * multiplier; + this.haveNextNextGaussian = true; + + return v1 * multiplier; + }; + + // Noise functions and helpers + function PerlinNoise(seed) { + var rnd = seed !== undef ? new Marsaglia(seed, (seed<<16)+(seed>>16)) : Marsaglia.createRandomized(); + var i, j; + // http://www.noisemachine.com/talk1/17b.html + // http://mrl.nyu.edu/~perlin/noise/ + // generate permutation + var perm = new Uint8Array(512); + for(i=0;i<256;++i) { perm[i] = i; } + for(i=0;i<256;++i) { + // NOTE: we can only do this because we've made sure the Marsaglia generator + // gives us numbers where the last byte in a pseudo-random number is + // still pseudo-random. If no 2nd argument is passed in the constructor, + // that is no longer the case and this pair swap will always run identically. + var t = perm[j = rnd.intGenerator() & 0xFF]; + perm[j] = perm[i]; + perm[i] = t; + } + // copy to avoid taking mod in perm[0]; + for(i=0;i<256;++i) { perm[i + 256] = perm[i]; } + + function grad3d(i,x,y,z) { + var h = i & 15; // convert into 12 gradient directions + var u = h<8 ? x : y, + v = h<4 ? y : h===12||h===14 ? x : z; + return ((h&1) === 0 ? u : -u) + ((h&2) === 0 ? v : -v); + } + + function grad2d(i,x,y) { + var v = (i & 1) === 0 ? x : y; + return (i&2) === 0 ? -v : v; + } + + function grad1d(i,x) { + return (i&1) === 0 ? -x : x; + } + + function lerp(t,a,b) { return a + t * (b - a); } + + this.noise3d = function(x, y, z) { + var X = Math.floor(x)&255, Y = Math.floor(y)&255, Z = Math.floor(z)&255; + x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z); + var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y, fz = (3-2*z)*z*z; + var p0 = perm[X]+Y, p00 = perm[p0] + Z, p01 = perm[p0 + 1] + Z, + p1 = perm[X + 1] + Y, p10 = perm[p1] + Z, p11 = perm[p1 + 1] + Z; + return lerp(fz, + lerp(fy, lerp(fx, grad3d(perm[p00], x, y, z), grad3d(perm[p10], x-1, y, z)), + lerp(fx, grad3d(perm[p01], x, y-1, z), grad3d(perm[p11], x-1, y-1,z))), + lerp(fy, lerp(fx, grad3d(perm[p00 + 1], x, y, z-1), grad3d(perm[p10 + 1], x-1, y, z-1)), + lerp(fx, grad3d(perm[p01 + 1], x, y-1, z-1), grad3d(perm[p11 + 1], x-1, y-1,z-1)))); + }; + + this.noise2d = function(x, y) { + var X = Math.floor(x)&255, Y = Math.floor(y)&255; + x -= Math.floor(x); y -= Math.floor(y); + var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y; + var p0 = perm[X]+Y, p1 = perm[X + 1] + Y; + return lerp(fy, + lerp(fx, grad2d(perm[p0], x, y), grad2d(perm[p1], x-1, y)), + lerp(fx, grad2d(perm[p0 + 1], x, y-1), grad2d(perm[p1 + 1], x-1, y-1))); + }; + + this.noise1d = function(x) { + var X = Math.floor(x)&255; + x -= Math.floor(x); + var fx = (3-2*x)*x*x; + return lerp(fx, grad1d(perm[X], x), grad1d(perm[X+1], x-1)); + }; + } + + // processing defaults + var noiseProfile = { generator: undef, octaves: 4, fallout: 0.5, seed: undef}; + + /** + * Returns the Perlin noise value at specified coordinates. Perlin noise is a random sequence + * generator producing a more natural ordered, harmonic succession of numbers compared to the + * standard random() function. It was invented by Ken Perlin in the 1980s and been used since + * in graphical applications to produce procedural textures, natural motion, shapes, terrains etc. + * The main difference to the random() function is that Perlin noise is defined in an infinite + * n-dimensional space where each pair of coordinates corresponds to a fixed semi-random value + * (fixed only for the lifespan of the program). The resulting value will always be between 0.0 + * and 1.0. Processing can compute 1D, 2D and 3D noise, depending on the number of coordinates + * given. The noise value can be animated by moving through the noise space as demonstrated in + * the example above. The 2nd and 3rd dimension can also be interpreted as time. + * The actual noise is structured similar to an audio signal, in respect to the function's use + * of frequencies. Similar to the concept of harmonics in physics, perlin noise is computed over + * several octaves which are added together for the final result. + * Another way to adjust the character of the resulting sequence is the scale of the input + * coordinates. As the function works within an infinite space the value of the coordinates + * doesn't matter as such, only the distance between successive coordinates does (eg. when using + * noise() within a loop). As a general rule the smaller the difference between coordinates, the + * smoother the resulting noise sequence will be. Steps of 0.005-0.03 work best for most applications, + * but this will differ depending on use. + * + * @param {float} x x coordinate in noise space + * @param {float} y y coordinate in noise space + * @param {float} z z coordinate in noise space + * + * @returns {float} + * + * @see random + * @see noiseDetail + */ + p.noise = function(x, y, z) { + if(noiseProfile.generator === undef) { + // caching + noiseProfile.generator = new PerlinNoise(noiseProfile.seed); + } + var generator = noiseProfile.generator; + var effect = 1, k = 1, sum = 0; + for(var i=0; i<noiseProfile.octaves; ++i) { + effect *= noiseProfile.fallout; + switch (arguments.length) { + case 1: + sum += effect * (1 + generator.noise1d(k*x))/2; break; + case 2: + sum += effect * (1 + generator.noise2d(k*x, k*y))/2; break; + case 3: + sum += effect * (1 + generator.noise3d(k*x, k*y, k*z))/2; break; + } + k *= 2; + } + return sum; + }; + + /** + * Adjusts the character and level of detail produced by the Perlin noise function. + * Similar to harmonics in physics, noise is computed over several octaves. Lower octaves + * contribute more to the output signal and as such define the overal intensity of the noise, + * whereas higher octaves create finer grained details in the noise sequence. By default, + * noise is computed over 4 octaves with each octave contributing exactly half than its + * predecessor, starting at 50% strength for the 1st octave. This falloff amount can be + * changed by adding an additional function parameter. Eg. a falloff factor of 0.75 means + * each octave will now have 75% impact (25% less) of the previous lower octave. Any value + * between 0.0 and 1.0 is valid, however note that values greater than 0.5 might result in + * greater than 1.0 values returned by noise(). By changing these parameters, the signal + * created by the noise() function can be adapted to fit very specific needs and characteristics. + * + * @param {int} octaves number of octaves to be used by the noise() function + * @param {float} falloff falloff factor for each octave + * + * @see noise + */ + p.noiseDetail = function(octaves, fallout) { + noiseProfile.octaves = octaves; + if(fallout !== undef) { + noiseProfile.fallout = fallout; + } + }; + + /** + * Sets the seed value for noise(). By default, noise() produces different results each + * time the program is run. Set the value parameter to a constant to return the same + * pseudo-random numbers each time the software is run. + * + * @param {int} seed int + * + * @returns {float} + * + * @see random + * @see radomSeed + * @see noise + * @see noiseDetail + */ + p.noiseSeed = function(seed) { + noiseProfile.seed = seed; + noiseProfile.generator = undef; + }; +}; + +},{}],24:[function(require,module,exports){ +/** + * Common functions traditionally on "p" that should be class functions + * that get bound to "p" when an instance is actually built, instead. + */ +module.exports = (function commonFunctions(undef) { + + var CommonFunctions = { + /** + * Remove whitespace characters from the beginning and ending + * of a String or a String array. Works like String.trim() but includes the + * unicode nbsp character as well. If an array is passed in the function will return a new array not effecting the array passed in. + * + * @param {String} str the string to trim + * @param {String[]} str the string array to trim + * + * @return {String|String[]} retrurns a string or an array will removed whitespaces + */ + trim: function(str) { + if (str instanceof Array) { + var arr = []; + for (var i = 0; i < str.length; i++) { + arr.push(str[i].replace(/^\s*/, '').replace(/\s*$/, '').replace(/\r*$/, '')); + } + return arr; + } + return str.replace(/^\s*/, '').replace(/\s*$/, '').replace(/\r*$/, ''); + }, + + /** + * Converts a degree measurement to its corresponding value in radians. Radians and degrees are two ways of + * measuring the same thing. There are 360 degrees in a circle and 2*PI radians in a circle. For example, + * 90 degrees = PI/2 = 1.5707964. All trigonometric methods in Processing require their parameters to be specified in radians. + * + * @param {int|float} value an angle in radians + * + * @returns {float} + * + * @see degrees + */ + radians: function(aAngle) { + return (aAngle / 180) * Math.PI; + }, + + /** + * Number-to-String formatting function. Prepends "plus" or "minus" depending + * on whether the value is positive or negative, respectively, after padding + * the value with zeroes on the left and right, the number of zeroes used dictated + * by the values 'leftDigits' and 'rightDigits'. 'value' cannot be an array. + * + * @param {int|float} value the number to format + * @param {String} plus the prefix for positive numbers + * @param {String} minus the prefix for negative numbers + * @param {int} left number of digits to the left of the decimal point + * @param {int} right number of digits to the right of the decimal point + * @param {String} group string delimited for groups, such as the comma in "1,000" + * + * @returns {String or String[]} + * + * @see nfCore + */ + nfCoreScalar: function (value, plus, minus, leftDigits, rightDigits, group) { + var sign = (value < 0) ? minus : plus; + var autoDetectDecimals = rightDigits === 0; + var rightDigitsOfDefault = (rightDigits === undef || rightDigits < 0) ? 0 : rightDigits; + + var absValue = Math.abs(value); + if (autoDetectDecimals) { + rightDigitsOfDefault = 1; + absValue *= 10; + while (Math.abs(Math.round(absValue) - absValue) > 1e-6 && rightDigitsOfDefault < 7) { + ++rightDigitsOfDefault; + absValue *= 10; + } + } else if (rightDigitsOfDefault !== 0) { + absValue *= Math.pow(10, rightDigitsOfDefault); + } + + // Using Java's default rounding policy HALF_EVEN. This policy is based + // on the idea that 0.5 values round to the nearest even number, and + // everything else is rounded normally. + var number, doubled = absValue * 2; + if (Math.floor(absValue) === absValue) { + number = absValue; + } else if (Math.floor(doubled) === doubled) { + var floored = Math.floor(absValue); + number = floored + (floored % 2); + } else { + number = Math.round(absValue); + } + + var buffer = ""; + var totalDigits = leftDigits + rightDigitsOfDefault; + while (totalDigits > 0 || number > 0) { + totalDigits--; + buffer = "" + (number % 10) + buffer; + number = Math.floor(number / 10); + } + if (group !== undef) { + var i = buffer.length - 3 - rightDigitsOfDefault; + while(i > 0) { + buffer = buffer.substring(0,i) + group + buffer.substring(i); + i-=3; + } + } + if (rightDigitsOfDefault > 0) { + return sign + buffer.substring(0, buffer.length - rightDigitsOfDefault) + + "." + buffer.substring(buffer.length - rightDigitsOfDefault, buffer.length); + } + return sign + buffer; + }, + + /** + * Number-to-String formatting function. Prepends "plus" or "minus" depending + * on whether the value is positive or negative, respectively, after padding + * the value with zeroes on the left and right, the number of zeroes used dictated + * by the values 'leftDigits' and 'rightDigits'. 'value' can be an array; + * if the input is an array, each value in it is formatted separately, and + * an array with formatted values is returned. + * + * @param {int|int[]|float|float[]} value the number(s) to format + * @param {String} plus the prefix for positive numbers + * @param {String} minus the prefix for negative numbers + * @param {int} left number of digits to the left of the decimal point + * @param {int} right number of digits to the right of the decimal point + * @param {String} group string delimited for groups, such as the comma in "1,000" + * + * @returns {String or String[]} + * + * @see nfCoreScalar + */ + nfCore: function(value, plus, minus, leftDigits, rightDigits, group) { + if (value instanceof Array) { + var arr = []; + for (var i = 0, len = value.length; i < len; i++) { + arr.push(CommonFunctions.nfCoreScalar(value[i], plus, minus, leftDigits, rightDigits, group)); + } + return arr; + } + return CommonFunctions.nfCoreScalar(value, plus, minus, leftDigits, rightDigits, group); + }, + + /** + * Utility function for formatting numbers into strings. There are two versions, one for + * formatting floats and one for formatting ints. The values for the digits, left, and + * right parameters should always be positive integers. + * As shown in the above example, nf() is used to add zeros to the left and/or right + * of a number. This is typically for aligning a list of numbers. To remove digits from + * a floating-point number, use the int(), ceil(), floor(), or round() functions. + * + * @param {int|int[]|float|float[]} value the number(s) to format + * @param {int} left number of digits to the left of the decimal point + * @param {int} right number of digits to the right of the decimal point + * + * @returns {String or String[]} + * + * @see nfs + * @see nfp + * @see nfc + */ + nf: function(value, leftDigits, rightDigits) { + return CommonFunctions.nfCore(value, "", "-", leftDigits, rightDigits); + }, + + /** + * Utility function for formatting numbers into strings. Similar to nf() but leaves a blank space in front + * of positive numbers so they align with negative numbers in spite of the minus symbol. There are two + * versions, one for formatting floats and one for formatting ints. The values for the digits, left, + * and right parameters should always be positive integers. + * + * @param {int|int[]|float|float[]} value the number(s) to format + * @param {int} left number of digits to the left of the decimal point + * @param {int} right number of digits to the right of the decimal point + * + * @returns {String or String[]} + * + * @see nf + * @see nfp + * @see nfc + */ + nfs: function(value, leftDigits, rightDigits) { + return CommonFunctions.nfCore(value, " ", "-", leftDigits, rightDigits); + }, + + /** + * Utility function for formatting numbers into strings. Similar to nf() but puts a "+" in front of + * positive numbers and a "-" in front of negative numbers. There are two versions, one for formatting + * floats and one for formatting ints. The values for the digits, left, and right parameters should + * always be positive integers. + * + * @param {int|int[]|float|float[]} value the number(s) to format + * @param {int} left number of digits to the left of the decimal point + * @param {int} right number of digits to the right of the decimal point + * + * @returns {String or String[]} + * + * @see nfs + * @see nf + * @see nfc + */ + nfp: function(value, leftDigits, rightDigits) { + return CommonFunctions.nfCore(value, "+", "-", leftDigits, rightDigits); + }, + + /** + * Utility function for formatting numbers into strings and placing appropriate commas to mark + * units of 1000. There are two versions, one for formatting ints and one for formatting an array + * of ints. The value for the digits parameter should always be a positive integer. + * + * @param {int|int[]|float|float[]} value the number(s) to format + * @param {int} left number of digits to the left of the decimal point + * @param {int} right number of digits to the right of the decimal point + * + * @returns {String or String[]} + * + * @see nf + * @see nfs + * @see nfp + */ + nfc: function(value, rightDigits) { + return CommonFunctions.nfCore(value, "", "-", 0, rightDigits, ","); + }, + + // used to bind all common functions to "p" + withCommonFunctions: function withCommonFunctions(p) { + ["trim", "radians", "nf", "nfs", "nfp", "nfc"].forEach(function(f){ + p[f] = CommonFunctions[f]; + }); + } + }; + + return CommonFunctions; +}()); + +},{}],25:[function(require,module,exports){ +/** + * Touch and Mouse event handling + */ +module.exports = function withTouch(p, curElement, attachEventHandler, document, PConstants, undef) { + + /** + * Determine the location of the (mouse) pointer. + */ + function calculateOffset(curElement, event) { + var element = curElement, + offsetX = 0, + offsetY = 0; + + p.pmouseX = p.mouseX; + p.pmouseY = p.mouseY; + + // Find element offset + if (element.offsetParent) { + do { + offsetX += element.offsetLeft; + offsetY += element.offsetTop; + } while (!!(element = element.offsetParent)); + } + + // Find Scroll offset + element = curElement; + do { + offsetX -= element.scrollLeft || 0; + offsetY -= element.scrollTop || 0; + } while (!!(element = element.parentNode)); + + // Get padding and border style widths for mouse offsets + var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop; + if (document.defaultView && document.defaultView.getComputedStyle) { + stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(curElement, null).paddingLeft, 10) || 0; + stylePaddingTop = parseInt(document.defaultView.getComputedStyle(curElement, null).paddingTop, 10) || 0; + styleBorderLeft = parseInt(document.defaultView.getComputedStyle(curElement, null).borderLeftWidth, 10) || 0; + styleBorderTop = parseInt(document.defaultView.getComputedStyle(curElement, null).borderTopWidth, 10) || 0; + } + + // Add padding and border style widths to offset + offsetX += stylePaddingLeft; + offsetY += stylePaddingTop; + + offsetX += styleBorderLeft; + offsetY += styleBorderTop; + + // Take into account any scrolling done + offsetX += window.pageXOffset; + offsetY += window.pageYOffset; + + return {'X':offsetX,'Y':offsetY}; + } + + // simple relative position + function updateMousePosition(curElement, event) { + var offset = calculateOffset(curElement, event); + // Dropping support for IE clientX and clientY, switching to pageX and pageY + // so we don't have to calculate scroll offset. + // Removed in ticket #184. See rev: 2f106d1c7017fed92d045ba918db47d28e5c16f4 + p.mouseX = event.pageX - offset.X; + p.mouseY = event.pageY - offset.Y; + } + + /** + * Return a TouchEvent with canvas-specific x/y co-ordinates + */ + function addTouchEventOffset(t) { + var offset = calculateOffset(t.changedTouches[0].target, t.changedTouches[0]), + i; + + for (i = 0; i < t.touches.length; i++) { + var touch = t.touches[i]; + touch.offsetX = touch.pageX - offset.X; + touch.offsetY = touch.pageY - offset.Y; + } + for (i = 0; i < t.targetTouches.length; i++) { + var targetTouch = t.targetTouches[i]; + targetTouch.offsetX = targetTouch.pageX - offset.X; + targetTouch.offsetY = targetTouch.pageY - offset.Y; + } + for (i = 0; i < t.changedTouches.length; i++) { + var changedTouch = t.changedTouches[i]; + changedTouch.offsetX = changedTouch.pageX - offset.X; + changedTouch.offsetY = changedTouch.pageY - offset.Y; + } + + return t; + } + + /** + * Touch event support. + */ + attachEventHandler(curElement, "touchstart", function (t) { + // Removes unwanted behaviour of the canvas when touching canvas + curElement.setAttribute("style","-webkit-user-select: none"); + curElement.setAttribute("onclick","void(0)"); + curElement.setAttribute("style","-webkit-tap-highlight-color:rgba(0,0,0,0)"); + // Loop though eventHandlers and remove mouse listeners + for (var i=0, ehl=eventHandlers.length; i<ehl; i++) { + var type = eventHandlers[i].type; + // Have this function remove itself from the eventHandlers list too + if (type === "mouseout" || type === "mousemove" || + type === "mousedown" || type === "mouseup" || + type === "DOMMouseScroll" || type === "mousewheel" || type === "touchstart") { + detachEventHandler(eventHandlers[i]); + } + } + + // If there are any native touch events defined in the sketch, connect all of them + // Otherwise, connect all of the emulated mouse events + if (p.touchStart !== undef || p.touchMove !== undef || + p.touchEnd !== undef || p.touchCancel !== undef) { + attachEventHandler(curElement, "touchstart", function(t) { + if (p.touchStart !== undef) { + t = addTouchEventOffset(t); + p.touchStart(t); + } + }); + + attachEventHandler(curElement, "touchmove", function(t) { + if (p.touchMove !== undef) { + t.preventDefault(); // Stop the viewport from scrolling + t = addTouchEventOffset(t); + p.touchMove(t); + } + }); + + attachEventHandler(curElement, "touchend", function(t) { + if (p.touchEnd !== undef) { + t = addTouchEventOffset(t); + p.touchEnd(t); + } + }); + + attachEventHandler(curElement, "touchcancel", function(t) { + if (p.touchCancel !== undef) { + t = addTouchEventOffset(t); + p.touchCancel(t); + } + }); + + } else { + // Emulated touch start/mouse down event + attachEventHandler(curElement, "touchstart", function(e) { + updateMousePosition(curElement, e.touches[0]); + + p.__mousePressed = true; + p.mouseDragging = false; + p.mouseButton = PConstants.LEFT; + + if (typeof p.mousePressed === "function") { + p.mousePressed(); + } + }); + + // Emulated touch move/mouse move event + attachEventHandler(curElement, "touchmove", function(e) { + e.preventDefault(); + updateMousePosition(curElement, e.touches[0]); + + if (typeof p.mouseMoved === "function" && !p.__mousePressed) { + p.mouseMoved(); + } + if (typeof p.mouseDragged === "function" && p.__mousePressed) { + p.mouseDragged(); + p.mouseDragging = true; + } + }); + + // Emulated touch up/mouse up event + attachEventHandler(curElement, "touchend", function(e) { + p.__mousePressed = false; + + if (typeof p.mouseClicked === "function" && !p.mouseDragging) { + p.mouseClicked(); + } + + if (typeof p.mouseReleased === "function") { + p.mouseReleased(); + } + }); + } + + // Refire the touch start event we consumed in this function + curElement.dispatchEvent(t); + }); + + /** + * Context menu toggles. Most often you will not want the + * browser's context menu to show on a right click, but + * sometimes, you do, so we add two unofficial functions + * that can be used to trigger context menu behaviour. + */ + (function() { + var enabled = true, + contextMenu = function(e) { + e.preventDefault(); + e.stopPropagation(); + }; + + p.disableContextMenu = function() { + if (!enabled) { + return; + } + attachEventHandler(curElement, 'contextmenu', contextMenu); + enabled = false; + }; + + p.enableContextMenu = function() { + if (enabled) { + return; + } + detachEventHandler({elem: curElement, type: 'contextmenu', fn: contextMenu}); + enabled = true; + }; + }()); + + /** + * Mouse moved or dragged + */ + attachEventHandler(curElement, "mousemove", function(e) { + updateMousePosition(curElement, e); + if (typeof p.mouseMoved === "function" && !p.__mousePressed) { + p.mouseMoved(); + } + if (typeof p.mouseDragged === "function" && p.__mousePressed) { + p.mouseDragged(); + p.mouseDragging = true; + } + }); + + /** + * Unofficial mouse-out handling + */ + attachEventHandler(curElement, "mouseout", function(e) { + if (typeof p.mouseOut === "function") { + p.mouseOut(); + } + }); + + /** + * Mouse over + */ + attachEventHandler(curElement, "mouseover", function(e) { + updateMousePosition(curElement, e); + if (typeof p.mouseOver === "function") { + p.mouseOver(); + } + }); + + /** + * Disable browser's default handling for click-drag of a canvas. + */ + curElement.onmousedown = function () { + // make sure focus happens, but nothing else + curElement.focus(); + return false; + }; + + /** + * Mouse pressed or drag + */ + attachEventHandler(curElement, "mousedown", function(e) { + p.__mousePressed = true; + p.mouseDragging = false; + switch (e.which) { + case 1: + p.mouseButton = PConstants.LEFT; + break; + case 2: + p.mouseButton = PConstants.CENTER; + break; + case 3: + p.mouseButton = PConstants.RIGHT; + break; + } + + if (typeof p.mousePressed === "function") { + p.mousePressed(); + } + }); + + /** + * Mouse clicked or released + */ + attachEventHandler(curElement, "mouseup", function(e) { + p.__mousePressed = false; + + if (typeof p.mouseClicked === "function" && !p.mouseDragging) { + p.mouseClicked(); + } + + if (typeof p.mouseReleased === "function") { + p.mouseReleased(); + } + }); + + /** + * Unofficial scroll wheel handling. + */ + var mouseWheelHandler = function(e) { + var delta = 0; + + if (e.wheelDelta) { + delta = e.wheelDelta / 120; + if (window.opera) { + delta = -delta; + } + } else if (e.detail) { + delta = -e.detail / 3; + } + + p.mouseScroll = delta; + + if (delta && typeof p.mouseScrolled === 'function') { + p.mouseScrolled(); + } + }; + + // Support Gecko and non-Gecko scroll events + attachEventHandler(document, 'DOMMouseScroll', mouseWheelHandler); + attachEventHandler(document, 'mousewheel', mouseWheelHandler); + +}; + +},{}],26:[function(require,module,exports){ +/** + * The parser for turning Processing syntax into Pjs JavaScript. + * This code is not trivial; unless you know what you're doing, + * you shouldn't be changing things in here =) + */ +module.exports = function setupParser(Processing, options) { + + var defaultScope = options.defaultScope, + PConstants = defaultScope.PConstants, + aFunctions = options.aFunctions, + Browser = options.Browser, + document = Browser.document, + undef; + + // Processing global methods and constants for the parser + function getGlobalMembers() { + // The names array contains the names of everything that is inside "p." + // When something new is added to "p." it must also be added to this list. + var names = [ /* this code is generated by jsglobals.js */ + "abs", "acos", "alpha", "ambient", "ambientLight", "append", "applyMatrix", + "arc", "arrayCopy", "asin", "atan", "atan2", "background", "beginCamera", + "beginDraw", "beginShape", "bezier", "bezierDetail", "bezierPoint", + "bezierTangent", "bezierVertex", "binary", "blend", "blendColor", + "blit_resize", "blue", "box", "breakShape", "brightness", + "camera", "ceil", "Character", "color", "colorMode", + "concat", "constrain", "copy", "cos", "createFont", + "createGraphics", "createImage", "cursor", "curve", "curveDetail", + "curvePoint", "curveTangent", "curveTightness", "curveVertex", "day", + "degrees", "directionalLight", "disableContextMenu", + "dist", "draw", "ellipse", "ellipseMode", "emissive", "enableContextMenu", + "endCamera", "endDraw", "endShape", "exit", "exp", "expand", "externals", + "fill", "filter", "floor", "focused", "frameCount", "frameRate", "frustum", + "get", "glyphLook", "glyphTable", "green", "height", "hex", "hint", "hour", + "hue", "image", "imageMode", "intersect", "join", "key", + "keyCode", "keyPressed", "keyReleased", "keyTyped", "lerp", "lerpColor", + "lightFalloff", "lights", "lightSpecular", "line", "link", "loadBytes", + "loadFont", "loadGlyphs", "loadImage", "loadPixels", "loadShape", "loadXML", + "loadStrings", "log", "loop", "mag", "map", "match", "matchAll", "max", + "millis", "min", "minute", "mix", "modelX", "modelY", "modelZ", "modes", + "month", "mouseButton", "mouseClicked", "mouseDragged", "mouseMoved", + "mouseOut", "mouseOver", "mousePressed", "mouseReleased", "mouseScroll", + "mouseScrolled", "mouseX", "mouseY", "name", "nf", "nfc", "nfp", "nfs", + "noCursor", "noFill", "noise", "noiseDetail", "noiseSeed", "noLights", + "noLoop", "norm", "normal", "noSmooth", "noStroke", "noTint", "ortho", + "param", "parseBoolean", "parseByte", "parseChar", "parseFloat", + "parseInt", "parseXML", "peg", "perspective", "PImage", "pixels", + "PMatrix2D", "PMatrix3D", "PMatrixStack", "pmouseX", "pmouseY", "point", + "pointLight", "popMatrix", "popStyle", "pow", "print", "printCamera", + "println", "printMatrix", "printProjection", "PShape", "PShapeSVG", + "pushMatrix", "pushStyle", "quad", "radians", "random", "randomGaussian", + "randomSeed", "rect", "rectMode", "red", "redraw", "requestImage", + "resetMatrix", "reverse", "rotate", "rotateX", "rotateY", "rotateZ", + "round", "saturation", "save", "saveFrame", "saveStrings", "scale", + "screenX", "screenY", "screenZ", "second", "set", "setup", "shape", + "shapeMode", "shared", "shearX", "shearY", "shininess", "shorten", "sin", "size", "smooth", + "sort", "specular", "sphere", "sphereDetail", "splice", "split", + "splitTokens", "spotLight", "sq", "sqrt", "status", "str", "stroke", + "strokeCap", "strokeJoin", "strokeWeight", "subset", "tan", "text", + "textAlign", "textAscent", "textDescent", "textFont", "textLeading", + "textMode", "textSize", "texture", "textureMode", "textWidth", "tint", "toImageData", + "touchCancel", "touchEnd", "touchMove", "touchStart", "translate", "transform", + "triangle", "trim", "unbinary", "unhex", "updatePixels", "use3DContext", + "vertex", "width", "XMLElement", "XML", "year", "__contains", "__equals", + "__equalsIgnoreCase", "__frameRate", "__hashCode", "__int_cast", + "__instanceof", "__keyPressed", "__mousePressed", "__printStackTrace", + "__replace", "__replaceAll", "__replaceFirst", "__toCharArray", "__split", + "__codePointAt", "__startsWith", "__endsWith", "__matches"]; + + // custom functions and properties are added here + if(aFunctions) { + Object.keys(aFunctions).forEach(function(name) { + names.push(name); + }); + } + + // custom libraries that were attached to Processing + var members = {}; + var i, l; + for (i = 0, l = names.length; i < l ; ++i) { + members[names[i]] = null; + } + for (var lib in Processing.lib) { + if (Processing.lib.hasOwnProperty(lib)) { + if (Processing.lib[lib].exports) { + var exportedNames = Processing.lib[lib].exports; + for (i = 0, l = exportedNames.length; i < l; ++i) { + members[exportedNames[i]] = null; + } + } + } + } + return members; + } + + /* + + Parser converts Java-like syntax into JavaScript. + Creates an Abstract Syntax Tree -- "Light AST" from the Java-like code. + + It is an object tree. The root object is created from the AstRoot class, which contains statements. + + A statement object can be of type: AstForStatement, AstCatchStatement, AstPrefixStatement, AstMethod, AstClass, + AstInterface, AstFunction, AstStatementBlock and AstLabel. + + AstPrefixStatement can be a statement of type: if, switch, while, with, do, else, finally, return, throw, try, break, and continue. + + These object's toString function returns the JavaScript code for the statement. + + Any processing calls need "processing." prepended to them. + + Similarly, calls from inside classes need "$this_1.", prepended to them, + with 1 being the depth level for inner classes. + This includes members passed down from inheritance. + + The resulting code is then eval'd and run. + + */ + + function parseProcessing(code) { + var globalMembers = getGlobalMembers(); + + // masks parentheses, brackets and braces with '"A5"' + // where A is the bracket type, and 5 is the index in an array containing all brackets split into atoms + // 'while(true){}' -> 'while"B1""A2"' + // parentheses() = B, brackets[] = C and braces{} = A + function splitToAtoms(code) { + var atoms = []; + var items = code.split(/([\{\[\(\)\]\}])/); + var result = items[0]; + + var stack = []; + for(var i=1; i < items.length; i += 2) { + var item = items[i]; + if(item === '[' || item === '{' || item === '(') { + stack.push(result); result = item; + } else if(item === ']' || item === '}' || item === ')') { + var kind = item === '}' ? 'A' : item === ')' ? 'B' : 'C'; + var index = atoms.length; atoms.push(result + item); + result = stack.pop() + '"' + kind + (index + 1) + '"'; + } + result += items[i + 1]; + } + atoms.unshift(result); + return atoms; + } + + // replaces strings and regexs keyed by index with an array of strings + function injectStrings(code, strings) { + return code.replace(/'(\d+)'/g, function(all, index) { + var val = strings[index]; + if(val.charAt(0) === "/") { + return val; + } + return (/^'((?:[^'\\\n])|(?:\\.[0-9A-Fa-f]*))'$/).test(val) ? "(new $p.Character(" + val + "))" : val; + }); + } + + // trims off leading and trailing spaces + // returns an object. object.left, object.middle, object.right, object.untrim + function trimSpaces(string) { + var m1 = /^\s*/.exec(string), result; + if(m1[0].length === string.length) { + result = {left: m1[0], middle: "", right: ""}; + } else { + var m2 = /\s*$/.exec(string); + result = {left: m1[0], middle: string.substring(m1[0].length, m2.index), right: m2[0]}; + } + result.untrim = function(t) { return this.left + t + this.right; }; + return result; + } + + // simple trim of leading and trailing spaces + function trim(string) { + return string.replace(/^\s+/,'').replace(/\s+$/,''); + } + + function appendToLookupTable(table, array) { + for(var i=0,l=array.length;i<l;++i) { + table[array[i]] = null; + } + return table; + } + + function isLookupTableEmpty(table) { + for(var i in table) { + if(table.hasOwnProperty(i)) { + return false; + } + } + return true; + } + + function getAtomIndex(templ) { return templ.substring(2, templ.length - 1); } + + // remove carriage returns "\r" + var codeWoExtraCr = code.replace(/\r\n?|\n\r/g, "\n"); + + // masks strings and regexs with "'5'", where 5 is the index in an array containing all strings and regexs + // also removes all comments + var strings = []; + var codeWoStrings = codeWoExtraCr.replace(/("(?:[^"\\\n]|\\.)*")|('(?:[^'\\\n]|\\.)*')|(([\[\(=|&!\^:?]\s*)(\/(?![*\/])(?:[^\/\\\n]|\\.)*\/[gim]*)\b)|(\/\/[^\n]*\n)|(\/\*(?:(?!\*\/)(?:.|\n))*\*\/)/g, + function(all, quoted, aposed, regexCtx, prefix, regex, singleComment, comment) { + var index; + if(quoted || aposed) { // replace strings + index = strings.length; strings.push(all); + return "'" + index + "'"; + } + if(regexCtx) { // replace RegExps + index = strings.length; strings.push(regex); + return prefix + "'" + index + "'"; + } + // kill comments + return comment !== "" ? " " : "\n"; + }); + + // protect character codes from namespace collision + codeWoStrings = codeWoStrings.replace(/__x([0-9A-F]{4})/g, function(all, hexCode) { + // $ = __x0024 + // _ = __x005F + // this protects existing character codes from conversion + // __x0024 = __x005F_x0024 + return "__x005F_x" + hexCode; + }); + + // convert dollar sign to character code + codeWoStrings = codeWoStrings.replace(/\$/g, "__x0024"); + + // Remove newlines after return statements + codeWoStrings = codeWoStrings.replace(/return\s*[\n\r]+/g, "return "); + + // removes generics + var genericsWereRemoved; + var codeWoGenerics = codeWoStrings; + var replaceFunc = function(all, before, types, after) { + if(!!before || !!after) { + return all; + } + genericsWereRemoved = true; + return ""; + }; + + do { + genericsWereRemoved = false; + codeWoGenerics = codeWoGenerics.replace(/([<]?)<\s*((?:\?|[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\[\])*(?:\s+(?:extends|super)\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)?(?:\s*,\s*(?:\?|[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\[\])*(?:\s+(?:extends|super)\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)?)*)\s*>([=]?)/g, replaceFunc); + } while (genericsWereRemoved); + + var atoms = splitToAtoms(codeWoGenerics); + var replaceContext; + var declaredClasses = {}, currentClassId, classIdSeed = 0; + + function addAtom(text, type) { + var lastIndex = atoms.length; + atoms.push(text); + return '"' + type + lastIndex + '"'; + } + + function generateClassId() { + return "class" + (++classIdSeed); + } + + function appendClass(class_, classId, scopeId) { + class_.classId = classId; + class_.scopeId = scopeId; + declaredClasses[classId] = class_; + } + + // functions defined below + var transformClassBody, transformInterfaceBody, transformStatementsBlock, transformStatements, transformMain, transformExpression; + + var classesRegex = /\b((?:(?:public|private|final|protected|static|abstract)\s+)*)(class|interface)\s+([A-Za-z_$][\w$]*\b)(\s+extends\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)*)?(\s+implements\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)*)?\s*("A\d+")/g; + var methodsRegex = /\b((?:(?:public|private|final|protected|static|abstract|synchronized)\s+)*)((?!(?:else|new|return|throw|function|public|private|protected)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+"|;)/g; + var fieldTest = /^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:else|new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*(?:"C\d+"\s*)*([=,]|$)/; + var cstrsRegex = /\b((?:(?:public|private|final|protected|static|abstract)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+")/g; + var attrAndTypeRegex = /^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*/; + var functionsRegex = /\bfunction(?:\s+([A-Za-z_$][\w$]*))?\s*("B\d+")\s*("A\d+")/g; + + // This converts classes, methods and functions into atoms, and adds them to the atoms array. + // classes = E, methods = D and functions = H + function extractClassesAndMethods(code) { + var s = code; + s = s.replace(classesRegex, function(all) { + return addAtom(all, 'E'); + }); + s = s.replace(methodsRegex, function(all) { + return addAtom(all, 'D'); + }); + s = s.replace(functionsRegex, function(all) { + return addAtom(all, 'H'); + }); + return s; + } + + // This converts constructors into atoms, and adds them to the atoms array. + // constructors = G + function extractConstructors(code, className) { + var result = code.replace(cstrsRegex, function(all, attr, name, params, throws_, body) { + if(name !== className) { + return all; + } + return addAtom(all, 'G'); + }); + return result; + } + + // AstParam contains the name of a parameter inside a function declaration + function AstParam(name) { + this.name = name; + } + AstParam.prototype.toString = function() { + return this.name; + }; + // AstParams contains an array of AstParam objects + function AstParams(params, methodArgsParam) { + this.params = params; + this.methodArgsParam = methodArgsParam; + } + AstParams.prototype.getNames = function() { + var names = []; + for(var i=0,l=this.params.length;i<l;++i) { + names.push(this.params[i].name); + } + return names; + }; + AstParams.prototype.prependMethodArgs = function(body) { + if (!this.methodArgsParam) { + return body; + } + return "{\nvar " + this.methodArgsParam.name + + " = Array.prototype.slice.call(arguments, " + + this.params.length + ");\n" + body.substring(1); + }; + AstParams.prototype.toString = function() { + if(this.params.length === 0) { + return "()"; + } + var result = "("; + for(var i=0,l=this.params.length;i<l;++i) { + result += this.params[i] + ", "; + } + return result.substring(0, result.length - 2) + ")"; + }; + + function transformParams(params) { + var paramsWoPars = trim(params.substring(1, params.length - 1)); + var result = [], methodArgsParam = null; + if(paramsWoPars !== "") { + var paramList = paramsWoPars.split(","); + for(var i=0; i < paramList.length; ++i) { + var param = /\b([A-Za-z_$][\w$]*\b)(\s*"[ABC][\d]*")*\s*$/.exec(paramList[i]); + if (i === paramList.length - 1 && paramList[i].indexOf('...') >= 0) { + methodArgsParam = new AstParam(param[1]); + break; + } + result.push(new AstParam(param[1])); + } + } + return new AstParams(result, methodArgsParam); + } + + function preExpressionTransform(expr) { + var s = expr; + // new type[] {...} --> {...} + s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"C\d+")+\s*("A\d+")/g, function(all, type, init) { + return init; + }); + // new Runnable() {...} --> "F???" + s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"B\d+")\s*("A\d+")/g, function(all, type, init) { + return addAtom(all, 'F'); + }); + // function(...) { } --> "H???" + s = s.replace(functionsRegex, function(all) { + return addAtom(all, 'H'); + }); + // new type[?] --> createJavaArray('type', [?]) + s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)\s*("C\d+"(?:\s*"C\d+")*)/g, function(all, type, index) { + var args = index.replace(/"C(\d+)"/g, function(all, j) { return atoms[j]; }) + .replace(/\[\s*\]/g, "[null]").replace(/\s*\]\s*\[\s*/g, ", "); + var arrayInitializer = "{" + args.substring(1, args.length - 1) + "}"; + var createArrayArgs = "('" + type + "', " + addAtom(arrayInitializer, 'A') + ")"; + return '$p.createJavaArray' + addAtom(createArrayArgs, 'B'); + }); + // .length() --> .length + s = s.replace(/(\.\s*length)\s*"B\d+"/g, "$1"); + // #000000 --> 0x000000 + s = s.replace(/#([0-9A-Fa-f]{6})\b/g, function(all, digits) { + return "0xFF" + digits; + }); + // delete (type)???, except (int)??? + s = s.replace(/"B(\d+)"(\s*(?:[\w$']|"B))/g, function(all, index, next) { + var atom = atoms[index]; + if(!/^\(\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\s*(?:"C\d+"\s*)*\)$/.test(atom)) { + return all; + } + if(/^\(\s*int\s*\)$/.test(atom)) { + return "(int)" + next; + } + var indexParts = atom.split(/"C(\d+)"/g); + if(indexParts.length > 1) { + // even items contains atom numbers, can check only first + if(! /^\[\s*\]$/.test(atoms[indexParts[1]])) { + return all; // fallback - not a cast + } + } + return "" + next; + }); + // (int)??? -> __int_cast(???) + s = s.replace(/\(int\)([^,\]\)\}\?\:\*\+\-\/\^\|\%\&\~<\>\=]+)/g, function(all, arg) { + var trimmed = trimSpaces(arg); + return trimmed.untrim("__int_cast(" + trimmed.middle + ")"); + }); + // super() -> $superCstr(), super. -> $super.; + s = s.replace(/\bsuper(\s*"B\d+")/g, "$$superCstr$1").replace(/\bsuper(\s*\.)/g, "$$super$1"); + // 000.43->0.43 and 0010f->10, but not 0010 + s = s.replace(/\b0+((\d*)(?:\.[\d*])?(?:[eE][\-\+]?\d+)?[fF]?)\b/, function(all, numberWo0, intPart) { + if( numberWo0 === intPart) { + return all; + } + return intPart === "" ? "0" + numberWo0 : numberWo0; + }); + // 3.0f -> 3.0 + s = s.replace(/\b(\.?\d+\.?)[fF]\b/g, "$1"); + // Weird (?) parsing errors with % + s = s.replace(/([^\s])%([^=\s])/g, "$1 % $2"); + // Since frameRate() and frameRate are different things, + // we need to differentiate them somehow. So when we parse + // the Processing.js source, replace frameRate so it isn't + // confused with frameRate(), as well as keyPressed and mousePressed + s = s.replace(/\b(frameRate|keyPressed|mousePressed)\b(?!\s*"B)/g, "__$1"); + // "boolean", "byte", "int", etc. => "parseBoolean", "parseByte", "parseInt", etc. + s = s.replace(/\b(boolean|byte|char|float|int)\s*"B/g, function(all, name) { + return "parse" + name.substring(0, 1).toUpperCase() + name.substring(1) + "\"B"; + }); + // "pixels" replacements: + // pixels[i] = c => pixels.setPixel(i,c) | pixels[i] => pixels.getPixel(i) + // pixels.length => pixels.getLength() + // pixels = ar => pixels.set(ar) | pixels => pixels.toArray() + s = s.replace(/\bpixels\b\s*(("C(\d+)")|\.length)?(\s*=(?!=)([^,\]\)\}]+))?/g, + function(all, indexOrLength, index, atomIndex, equalsPart, rightSide) { + if(index) { + var atom = atoms[atomIndex]; + if(equalsPart) { + return "pixels.setPixel" + addAtom("(" +atom.substring(1, atom.length - 1) + + "," + rightSide + ")", 'B'); + } + return "pixels.getPixel" + addAtom("(" + atom.substring(1, atom.length - 1) + + ")", 'B'); + } + if(indexOrLength) { + // length + return "pixels.getLength" + addAtom("()", 'B'); + } + if(equalsPart) { + return "pixels.set" + addAtom("(" + rightSide + ")", 'B'); + } + return "pixels.toArray" + addAtom("()", 'B'); + }); + // Java method replacements for: replace, replaceAll, replaceFirst, equals, hashCode, etc. + // xxx.replace(yyy) -> __replace(xxx, yyy) + // "xx".replace(yyy) -> __replace("xx", yyy) + var repeatJavaReplacement; + function replacePrototypeMethods(all, subject, method, atomIndex) { + var atom = atoms[atomIndex]; + repeatJavaReplacement = true; + var trimmed = trimSpaces(atom.substring(1, atom.length - 1)); + return "__" + method + ( trimmed.middle === "" ? addAtom("(" + subject.replace(/\.\s*$/, "") + ")", 'B') : + addAtom("(" + subject.replace(/\.\s*$/, "") + "," + trimmed.middle + ")", 'B') ); + } + do { + repeatJavaReplacement = false; + s = s.replace(/((?:'\d+'|\b[A-Za-z_$][\w$]*\s*(?:"[BC]\d+")*)\s*\.\s*(?:[A-Za-z_$][\w$]*\s*(?:"[BC]\d+"\s*)*\.\s*)*)(replace|replaceAll|replaceFirst|contains|equals|equalsIgnoreCase|hashCode|toCharArray|printStackTrace|split|startsWith|endsWith|codePointAt|matches)\s*"B(\d+)"/g, + replacePrototypeMethods); + } while (repeatJavaReplacement); + // xxx instanceof yyy -> __instanceof(xxx, yyy) + function replaceInstanceof(all, subject, type) { + repeatJavaReplacement = true; + return "__instanceof" + addAtom("(" + subject + ", " + type + ")", 'B'); + } + do { + repeatJavaReplacement = false; + s = s.replace(/((?:'\d+'|\b[A-Za-z_$][\w$]*\s*(?:"[BC]\d+")*)\s*(?:\.\s*[A-Za-z_$][\w$]*\s*(?:"[BC]\d+"\s*)*)*)instanceof\s+([A-Za-z_$][\w$]*\s*(?:\.\s*[A-Za-z_$][\w$]*)*)/g, + replaceInstanceof); + } while (repeatJavaReplacement); + // this() -> $constr() + s = s.replace(/\bthis(\s*"B\d+")/g, "$$constr$1"); + + return s; + } + + function AstInlineClass(baseInterfaceName, body) { + this.baseInterfaceName = baseInterfaceName; + this.body = body; + body.owner = this; + } + AstInlineClass.prototype.toString = function() { + return "new (" + this.body + ")"; + }; + + function transformInlineClass(class_) { + var m = new RegExp(/\bnew\s*([A-Za-z_$][\w$]*\s*(?:\.\s*[A-Za-z_$][\w$]*)*)\s*"B\d+"\s*"A(\d+)"/).exec(class_); + var oldClassId = currentClassId, newClassId = generateClassId(); + currentClassId = newClassId; + var uniqueClassName = m[1] + "$" + newClassId; + var inlineClass = new AstInlineClass(uniqueClassName, + transformClassBody(atoms[m[2]], uniqueClassName, "", "implements " + m[1])); + appendClass(inlineClass, newClassId, oldClassId); + currentClassId = oldClassId; + return inlineClass; + } + + function AstFunction(name, params, body) { + this.name = name; + this.params = params; + this.body = body; + } + AstFunction.prototype.toString = function() { + var oldContext = replaceContext; + // saving "this." and parameters + var names = appendToLookupTable({"this":null}, this.params.getNames()); + replaceContext = function (subject) { + return names.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); + }; + var result = "function"; + if(this.name) { + result += " " + this.name; + } + var body = this.params.prependMethodArgs(this.body.toString()); + result += this.params + " " + body; + replaceContext = oldContext; + return result; + }; + + function transformFunction(class_) { + var m = new RegExp(/\b([A-Za-z_$][\w$]*)\s*"B(\d+)"\s*"A(\d+)"/).exec(class_); + return new AstFunction( m[1] !== "function" ? m[1] : null, + transformParams(atoms[m[2]]), transformStatementsBlock(atoms[m[3]])); + } + + function AstInlineObject(members) { + this.members = members; + } + AstInlineObject.prototype.toString = function() { + var oldContext = replaceContext; + replaceContext = function (subject) { + return subject.name === "this" ? "this" : oldContext(subject); // saving "this." + }; + var result = ""; + for(var i=0,l=this.members.length;i<l;++i) { + if(this.members[i].label) { + result += this.members[i].label + ": "; + } + result += this.members[i].value.toString() + ", "; + } + replaceContext = oldContext; + return result.substring(0, result.length - 2); + }; + + function transformInlineObject(obj) { + var members = obj.split(','); + for(var i=0; i < members.length; ++i) { + var label = members[i].indexOf(':'); + if(label < 0) { + members[i] = { value: transformExpression(members[i]) }; + } else { + members[i] = { label: trim(members[i].substring(0, label)), + value: transformExpression( trim(members[i].substring(label + 1)) ) }; + } + } + return new AstInlineObject(members); + } + + function expandExpression(expr) { + if(expr.charAt(0) === '(' || expr.charAt(0) === '[') { + return expr.charAt(0) + expandExpression(expr.substring(1, expr.length - 1)) + expr.charAt(expr.length - 1); + } + if(expr.charAt(0) === '{') { + if(/^\{\s*(?:[A-Za-z_$][\w$]*|'\d+')\s*:/.test(expr)) { + return "{" + addAtom(expr.substring(1, expr.length - 1), 'I') + "}"; + } + return "[" + expandExpression(expr.substring(1, expr.length - 1)) + "]"; + } + var trimmed = trimSpaces(expr); + var result = preExpressionTransform(trimmed.middle); + result = result.replace(/"[ABC](\d+)"/g, function(all, index) { + return expandExpression(atoms[index]); + }); + return trimmed.untrim(result); + } + + function replaceContextInVars(expr) { + return expr.replace(/(\.\s*)?((?:\b[A-Za-z_]|\$)[\w$]*)(\s*\.\s*([A-Za-z_$][\w$]*)(\s*\()?)?/g, + function(all, memberAccessSign, identifier, suffix, subMember, callSign) { + if(memberAccessSign) { + return all; + } + var subject = { name: identifier, member: subMember, callSign: !!callSign }; + return replaceContext(subject) + (suffix === undef ? "" : suffix); + }); + } + + function AstExpression(expr, transforms) { + this.expr = expr; + this.transforms = transforms; + } + AstExpression.prototype.toString = function() { + var transforms = this.transforms; + var expr = replaceContextInVars(this.expr); + return expr.replace(/"!(\d+)"/g, function(all, index) { + return transforms[index].toString(); + }); + }; + + transformExpression = function(expr) { + var transforms = []; + var s = expandExpression(expr); + s = s.replace(/"H(\d+)"/g, function(all, index) { + transforms.push(transformFunction(atoms[index])); + return '"!' + (transforms.length - 1) + '"'; + }); + s = s.replace(/"F(\d+)"/g, function(all, index) { + transforms.push(transformInlineClass(atoms[index])); + return '"!' + (transforms.length - 1) + '"'; + }); + s = s.replace(/"I(\d+)"/g, function(all, index) { + transforms.push(transformInlineObject(atoms[index])); + return '"!' + (transforms.length - 1) + '"'; + }); + + return new AstExpression(s, transforms); + }; + + function AstVarDefinition(name, value, isDefault) { + this.name = name; + this.value = value; + this.isDefault = isDefault; + } + AstVarDefinition.prototype.toString = function() { + return this.name + ' = ' + this.value; + }; + + function transformVarDefinition(def, defaultTypeValue) { + var eqIndex = def.indexOf("="); + var name, value, isDefault; + if(eqIndex < 0) { + name = def; + value = defaultTypeValue; + isDefault = true; + } else { + name = def.substring(0, eqIndex); + value = transformExpression(def.substring(eqIndex + 1)); + isDefault = false; + } + return new AstVarDefinition( trim(name.replace(/(\s*"C\d+")+/g, "")), + value, isDefault); + } + + function getDefaultValueForType(type) { + if(type === "int" || type === "float") { + return "0"; + } + if(type === "boolean") { + return "false"; + } + if(type === "color") { + return "0x00000000"; + } + return "null"; + } + + function AstVar(definitions, varType) { + this.definitions = definitions; + this.varType = varType; + } + AstVar.prototype.getNames = function() { + var names = []; + for(var i=0,l=this.definitions.length;i<l;++i) { + names.push(this.definitions[i].name); + } + return names; + }; + AstVar.prototype.toString = function() { + return "var " + this.definitions.join(","); + }; + function AstStatement(expression) { + this.expression = expression; + } + AstStatement.prototype.toString = function() { + return this.expression.toString(); + }; + + function transformStatement(statement) { + if(fieldTest.test(statement)) { + var attrAndType = attrAndTypeRegex.exec(statement); + var definitions = statement.substring(attrAndType[0].length).split(","); + var defaultTypeValue = getDefaultValueForType(attrAndType[2]); + for(var i=0; i < definitions.length; ++i) { + definitions[i] = transformVarDefinition(definitions[i], defaultTypeValue); + } + return new AstVar(definitions, attrAndType[2]); + } + return new AstStatement(transformExpression(statement)); + } + + function AstForExpression(initStatement, condition, step) { + this.initStatement = initStatement; + this.condition = condition; + this.step = step; + } + AstForExpression.prototype.toString = function() { + return "(" + this.initStatement + "; " + this.condition + "; " + this.step + ")"; + }; + + function AstForInExpression(initStatement, container) { + this.initStatement = initStatement; + this.container = container; + } + AstForInExpression.prototype.toString = function() { + var init = this.initStatement.toString(); + if(init.indexOf("=") >= 0) { // can be without var declaration + init = init.substring(0, init.indexOf("=")); + } + return "(" + init + " in " + this.container + ")"; + }; + + function AstForEachExpression(initStatement, container) { + this.initStatement = initStatement; + this.container = container; + } + AstForEachExpression.iteratorId = 0; + AstForEachExpression.prototype.toString = function() { + var init = this.initStatement.toString(); + var iterator = "$it" + (AstForEachExpression.iteratorId++); + var variableName = init.replace(/^\s*var\s*/, "").split("=")[0]; + var initIteratorAndVariable = "var " + iterator + " = new $p.ObjectIterator(" + this.container + "), " + + variableName + " = void(0)"; + var nextIterationCondition = iterator + ".hasNext() && ((" + + variableName + " = " + iterator + ".next()) || true)"; + return "(" + initIteratorAndVariable + "; " + nextIterationCondition + ";)"; + }; + + function transformForExpression(expr) { + var content; + if (/\bin\b/.test(expr)) { + content = expr.substring(1, expr.length - 1).split(/\bin\b/g); + return new AstForInExpression( transformStatement(trim(content[0])), + transformExpression(content[1])); + } + if (expr.indexOf(":") >= 0 && expr.indexOf(";") < 0) { + content = expr.substring(1, expr.length - 1).split(":"); + return new AstForEachExpression( transformStatement(trim(content[0])), + transformExpression(content[1])); + } + content = expr.substring(1, expr.length - 1).split(";"); + return new AstForExpression( transformStatement(trim(content[0])), + transformExpression(content[1]), transformExpression(content[2])); + } + + function sortByWeight(array) { + array.sort(function (a,b) { + return b.weight - a.weight; + }); + } + + function AstInnerInterface(name, body, isStatic) { + this.name = name; + this.body = body; + this.isStatic = isStatic; + body.owner = this; + } + AstInnerInterface.prototype.toString = function() { + return "" + this.body; + }; + function AstInnerClass(name, body, isStatic) { + this.name = name; + this.body = body; + this.isStatic = isStatic; + body.owner = this; + } + AstInnerClass.prototype.toString = function() { + return "" + this.body; + }; + + function transformInnerClass(class_) { + var m = classesRegex.exec(class_); // 1 - attr, 2 - class|int, 3 - name, 4 - extends, 5 - implements, 6 - body + classesRegex.lastIndex = 0; + var isStatic = m[1].indexOf("static") >= 0; + var body = atoms[getAtomIndex(m[6])], innerClass; + var oldClassId = currentClassId, newClassId = generateClassId(); + currentClassId = newClassId; + if(m[2] === "interface") { + innerClass = new AstInnerInterface(m[3], transformInterfaceBody(body, m[3], m[4]), isStatic); + } else { + innerClass = new AstInnerClass(m[3], transformClassBody(body, m[3], m[4], m[5]), isStatic); + } + appendClass(innerClass, newClassId, oldClassId); + currentClassId = oldClassId; + return innerClass; + } + + function AstClassMethod(name, params, body, isStatic) { + this.name = name; + this.params = params; + this.body = body; + this.isStatic = isStatic; + } + AstClassMethod.prototype.toString = function(){ + var paramNames = appendToLookupTable({}, this.params.getNames()); + var oldContext = replaceContext; + replaceContext = function (subject) { + return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); + }; + var body = this.params.prependMethodArgs(this.body.toString()); + var result = "function " + this.methodId + this.params + " " + body +"\n"; + replaceContext = oldContext; + return result; + }; + + function transformClassMethod(method) { + var m = methodsRegex.exec(method); + methodsRegex.lastIndex = 0; + var isStatic = m[1].indexOf("static") >= 0; + var body = m[6] !== ';' ? atoms[getAtomIndex(m[6])] : "{}"; + return new AstClassMethod(m[3], transformParams(atoms[getAtomIndex(m[4])]), + transformStatementsBlock(body), isStatic ); + } + + function AstClassField(definitions, fieldType, isStatic) { + this.definitions = definitions; + this.fieldType = fieldType; + this.isStatic = isStatic; + } + AstClassField.prototype.getNames = function() { + var names = []; + for(var i=0,l=this.definitions.length;i<l;++i) { + names.push(this.definitions[i].name); + } + return names; + }; + AstClassField.prototype.toString = function() { + var thisPrefix = replaceContext({ name: "[this]" }); + if(this.isStatic) { + var className = this.owner.name; + var staticDeclarations = []; + for(var i=0,l=this.definitions.length;i<l;++i) { + var definition = this.definitions[i]; + var name = definition.name, staticName = className + "." + name; + var declaration = "if(" + staticName + " === void(0)) {\n" + + " " + staticName + " = " + definition.value + "; }\n" + + "$p.defineProperty(" + thisPrefix + ", " + + "'" + name + "', { get: function(){return " + staticName + ";}, " + + "set: function(val){" + staticName + " = val;} });\n"; + staticDeclarations.push(declaration); + } + return staticDeclarations.join(""); + } + return thisPrefix + "." + this.definitions.join("; " + thisPrefix + "."); + }; + + function transformClassField(statement) { + var attrAndType = attrAndTypeRegex.exec(statement); + var isStatic = attrAndType[1].indexOf("static") >= 0; + var definitions = statement.substring(attrAndType[0].length).split(/,\s*/g); + var defaultTypeValue = getDefaultValueForType(attrAndType[2]); + for(var i=0; i < definitions.length; ++i) { + definitions[i] = transformVarDefinition(definitions[i], defaultTypeValue); + } + return new AstClassField(definitions, attrAndType[2], isStatic); + } + + function AstConstructor(params, body) { + this.params = params; + this.body = body; + } + AstConstructor.prototype.toString = function() { + var paramNames = appendToLookupTable({}, this.params.getNames()); + var oldContext = replaceContext; + replaceContext = function (subject) { + return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); + }; + var prefix = "function $constr_" + this.params.params.length + this.params.toString(); + var body = this.params.prependMethodArgs(this.body.toString()); + if(!/\$(superCstr|constr)\b/.test(body)) { + body = "{\n$superCstr();\n" + body.substring(1); + } + replaceContext = oldContext; + return prefix + body + "\n"; + }; + + function transformConstructor(cstr) { + var m = new RegExp(/"B(\d+)"\s*"A(\d+)"/).exec(cstr); + var params = transformParams(atoms[m[1]]); + + return new AstConstructor(params, transformStatementsBlock(atoms[m[2]])); + } + + function AstInterfaceBody(name, interfacesNames, methodsNames, fields, innerClasses, misc) { + var i,l; + this.name = name; + this.interfacesNames = interfacesNames; + this.methodsNames = methodsNames; + this.fields = fields; + this.innerClasses = innerClasses; + this.misc = misc; + for(i=0,l=fields.length; i<l; ++i) { + fields[i].owner = this; + } + } + AstInterfaceBody.prototype.getMembers = function(classFields, classMethods, classInners) { + if(this.owner.base) { + this.owner.base.body.getMembers(classFields, classMethods, classInners); + } + var i, j, l, m; + for(i=0,l=this.fields.length;i<l;++i) { + var fieldNames = this.fields[i].getNames(); + for(j=0,m=fieldNames.length;j<m;++j) { + classFields[fieldNames[j]] = this.fields[i]; + } + } + for(i=0,l=this.methodsNames.length;i<l;++i) { + var methodName = this.methodsNames[i]; + classMethods[methodName] = true; + } + for(i=0,l=this.innerClasses.length;i<l;++i) { + var innerClass = this.innerClasses[i]; + classInners[innerClass.name] = innerClass; + } + }; + AstInterfaceBody.prototype.toString = function() { + function getScopeLevel(p) { + var i = 0; + while(p) { + ++i; + p=p.scope; + } + return i; + } + + var scopeLevel = getScopeLevel(this.owner); + + var className = this.name; + var staticDefinitions = ""; + var metadata = ""; + + var thisClassFields = {}, thisClassMethods = {}, thisClassInners = {}; + this.getMembers(thisClassFields, thisClassMethods, thisClassInners); + + var i, l, j, m; + + if (this.owner.interfaces) { + // interface name can be present, but interface is not + var resolvedInterfaces = [], resolvedInterface; + for (i = 0, l = this.interfacesNames.length; i < l; ++i) { + if (!this.owner.interfaces[i]) { + continue; + } + resolvedInterface = replaceContext({name: this.interfacesNames[i]}); + resolvedInterfaces.push(resolvedInterface); + staticDefinitions += "$p.extendInterfaceMembers(" + className + ", " + resolvedInterface + ");\n"; + } + metadata += className + ".$interfaces = [" + resolvedInterfaces.join(", ") + "];\n"; + } + metadata += className + ".$isInterface = true;\n"; + metadata += className + ".$methods = [\'" + this.methodsNames.join("\', \'") + "\'];\n"; + + sortByWeight(this.innerClasses); + for (i = 0, l = this.innerClasses.length; i < l; ++i) { + var innerClass = this.innerClasses[i]; + if (innerClass.isStatic) { + staticDefinitions += className + "." + innerClass.name + " = " + innerClass + ";\n"; + } + } + + for (i = 0, l = this.fields.length; i < l; ++i) { + var field = this.fields[i]; + if (field.isStatic) { + staticDefinitions += className + "." + field.definitions.join(";\n" + className + ".") + ";\n"; + } + } + + return "(function() {\n" + + "function " + className + "() { throw \'Unable to create the interface\'; }\n" + + staticDefinitions + + metadata + + "return " + className + ";\n" + + "})()"; + }; + + transformInterfaceBody = function(body, name, baseInterfaces) { + var declarations = body.substring(1, body.length - 1); + declarations = extractClassesAndMethods(declarations); + declarations = extractConstructors(declarations, name); + var methodsNames = [], classes = []; + declarations = declarations.replace(/"([DE])(\d+)"/g, function(all, type, index) { + if(type === 'D') { methodsNames.push(index); } + else if(type === 'E') { classes.push(index); } + return ""; + }); + var fields = declarations.split(/;(?:\s*;)*/g); + var baseInterfaceNames; + var i, l; + + if(baseInterfaces !== undef) { + baseInterfaceNames = baseInterfaces.replace(/^\s*extends\s+(.+?)\s*$/g, "$1").split(/\s*,\s*/g); + } + + for(i = 0, l = methodsNames.length; i < l; ++i) { + var method = transformClassMethod(atoms[methodsNames[i]]); + methodsNames[i] = method.name; + } + for(i = 0, l = fields.length - 1; i < l; ++i) { + var field = trimSpaces(fields[i]); + fields[i] = transformClassField(field.middle); + } + var tail = fields.pop(); + for(i = 0, l = classes.length; i < l; ++i) { + classes[i] = transformInnerClass(atoms[classes[i]]); + } + + return new AstInterfaceBody(name, baseInterfaceNames, methodsNames, fields, classes, { tail: tail }); + }; + + function AstClassBody(name, baseClassName, interfacesNames, functions, methods, fields, cstrs, innerClasses, misc) { + var i,l; + this.name = name; + this.baseClassName = baseClassName; + this.interfacesNames = interfacesNames; + this.functions = functions; + this.methods = methods; + this.fields = fields; + this.cstrs = cstrs; + this.innerClasses = innerClasses; + this.misc = misc; + for(i=0,l=fields.length; i<l; ++i) { + fields[i].owner = this; + } + } + AstClassBody.prototype.getMembers = function(classFields, classMethods, classInners) { + if(this.owner.base) { + this.owner.base.body.getMembers(classFields, classMethods, classInners); + } + var i, j, l, m; + for(i=0,l=this.fields.length;i<l;++i) { + var fieldNames = this.fields[i].getNames(); + for(j=0,m=fieldNames.length;j<m;++j) { + classFields[fieldNames[j]] = this.fields[i]; + } + } + for(i=0,l=this.methods.length;i<l;++i) { + var method = this.methods[i]; + classMethods[method.name] = method; + } + for(i=0,l=this.innerClasses.length;i<l;++i) { + var innerClass = this.innerClasses[i]; + classInners[innerClass.name] = innerClass; + } + }; + AstClassBody.prototype.toString = function() { + function getScopeLevel(p) { + var i = 0; + while(p) { + ++i; + p=p.scope; + } + return i; + } + + var scopeLevel = getScopeLevel(this.owner); + + var selfId = "$this_" + scopeLevel; + var className = this.name; + var result = "var " + selfId + " = this;\n"; + var staticDefinitions = ""; + var metadata = ""; + + var thisClassFields = {}, thisClassMethods = {}, thisClassInners = {}; + this.getMembers(thisClassFields, thisClassMethods, thisClassInners); + + var oldContext = replaceContext; + replaceContext = function (subject) { + var name = subject.name; + if(name === "this") { + // returns "$this_N.$self" pointer instead of "this" in cases: + // "this()", "this.XXX()", "this", but not for "this.XXX" + return subject.callSign || !subject.member ? selfId + ".$self" : selfId; + } + if(thisClassFields.hasOwnProperty(name)) { + return thisClassFields[name].isStatic ? className + "." + name : selfId + "." + name; + } + if(thisClassInners.hasOwnProperty(name)) { + return selfId + "." + name; + } + if(thisClassMethods.hasOwnProperty(name)) { + return thisClassMethods[name].isStatic ? className + "." + name : selfId + ".$self." + name; + } + return oldContext(subject); + }; + + var resolvedBaseClassName; + if (this.baseClassName) { + resolvedBaseClassName = oldContext({name: this.baseClassName}); + result += "var $super = { $upcast: " + selfId + " };\n"; + result += "function $superCstr(){" + resolvedBaseClassName + + ".apply($super,arguments);if(!('$self' in $super)) $p.extendClassChain($super)}\n"; + metadata += className + ".$base = " + resolvedBaseClassName + ";\n"; + } else { + result += "function $superCstr(){$p.extendClassChain("+ selfId +")}\n"; + } + + if (this.owner.base) { + // base class name can be present, but class is not + staticDefinitions += "$p.extendStaticMembers(" + className + ", " + resolvedBaseClassName + ");\n"; + } + + var i, l, j, m; + + if (this.owner.interfaces) { + // interface name can be present, but interface is not + var resolvedInterfaces = [], resolvedInterface; + for (i = 0, l = this.interfacesNames.length; i < l; ++i) { + if (!this.owner.interfaces[i]) { + continue; + } + resolvedInterface = oldContext({name: this.interfacesNames[i]}); + resolvedInterfaces.push(resolvedInterface); + staticDefinitions += "$p.extendInterfaceMembers(" + className + ", " + resolvedInterface + ");\n"; + } + metadata += className + ".$interfaces = [" + resolvedInterfaces.join(", ") + "];\n"; + } + + if (this.functions.length > 0) { + result += this.functions.join('\n') + '\n'; + } + + sortByWeight(this.innerClasses); + for (i = 0, l = this.innerClasses.length; i < l; ++i) { + var innerClass = this.innerClasses[i]; + if (innerClass.isStatic) { + staticDefinitions += className + "." + innerClass.name + " = " + innerClass + ";\n"; + result += selfId + "." + innerClass.name + " = " + className + "." + innerClass.name + ";\n"; + } else { + result += selfId + "." + innerClass.name + " = " + innerClass + ";\n"; + } + } + + for (i = 0, l = this.fields.length; i < l; ++i) { + var field = this.fields[i]; + if (field.isStatic) { + staticDefinitions += className + "." + field.definitions.join(";\n" + className + ".") + ";\n"; + for (j = 0, m = field.definitions.length; j < m; ++j) { + var fieldName = field.definitions[j].name, staticName = className + "." + fieldName; + result += "$p.defineProperty(" + selfId + ", '" + fieldName + "', {" + + "get: function(){return " + staticName + "}, " + + "set: function(val){" + staticName + " = val}});\n"; + } + } else { + result += selfId + "." + field.definitions.join(";\n" + selfId + ".") + ";\n"; + } + } + var methodOverloads = {}; + for (i = 0, l = this.methods.length; i < l; ++i) { + var method = this.methods[i]; + var overload = methodOverloads[method.name]; + var methodId = method.name + "$" + method.params.params.length; + var hasMethodArgs = !!method.params.methodArgsParam; + if (overload) { + ++overload; + methodId += "_" + overload; + } else { + overload = 1; + } + method.methodId = methodId; + methodOverloads[method.name] = overload; + if (method.isStatic) { + staticDefinitions += method; + staticDefinitions += "$p.addMethod(" + className + ", '" + method.name + "', " + methodId + ", " + hasMethodArgs + ");\n"; + result += "$p.addMethod(" + selfId + ", '" + method.name + "', " + methodId + ", " + hasMethodArgs + ");\n"; + } else { + result += method; + result += "$p.addMethod(" + selfId + ", '" + method.name + "', " + methodId + ", " + hasMethodArgs + ");\n"; + } + } + result += trim(this.misc.tail); + + if (this.cstrs.length > 0) { + result += this.cstrs.join('\n') + '\n'; + } + + result += "function $constr() {\n"; + var cstrsIfs = []; + for (i = 0, l = this.cstrs.length; i < l; ++i) { + var paramsLength = this.cstrs[i].params.params.length; + var methodArgsPresent = !!this.cstrs[i].params.methodArgsParam; + cstrsIfs.push("if(arguments.length " + (methodArgsPresent ? ">=" : "===") + + " " + paramsLength + ") { " + + "$constr_" + paramsLength + ".apply(" + selfId + ", arguments); }"); + } + if(cstrsIfs.length > 0) { + result += cstrsIfs.join(" else ") + " else "; + } + // ??? add check if length is 0, otherwise fail + result += "$superCstr();\n}\n"; + result += "$constr.apply(null, arguments);\n"; + + replaceContext = oldContext; + return "(function() {\n" + + "function " + className + "() {\n" + result + "}\n" + + staticDefinitions + + metadata + + "return " + className + ";\n" + + "})()"; + }; + + transformClassBody = function(body, name, baseName, interfaces) { + var declarations = body.substring(1, body.length - 1); + declarations = extractClassesAndMethods(declarations); + declarations = extractConstructors(declarations, name); + var methods = [], classes = [], cstrs = [], functions = []; + declarations = declarations.replace(/"([DEGH])(\d+)"/g, function(all, type, index) { + if(type === 'D') { methods.push(index); } + else if(type === 'E') { classes.push(index); } + else if(type === 'H') { functions.push(index); } + else { cstrs.push(index); } + return ""; + }); + var fields = declarations.replace(/^(?:\s*;)+/, "").split(/;(?:\s*;)*/g); + var baseClassName, interfacesNames; + var i; + + if(baseName !== undef) { + baseClassName = baseName.replace(/^\s*extends\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)\s*$/g, "$1"); + } + + if(interfaces !== undef) { + interfacesNames = interfaces.replace(/^\s*implements\s+(.+?)\s*$/g, "$1").split(/\s*,\s*/g); + } + + for(i = 0; i < functions.length; ++i) { + functions[i] = transformFunction(atoms[functions[i]]); + } + for(i = 0; i < methods.length; ++i) { + methods[i] = transformClassMethod(atoms[methods[i]]); + } + for(i = 0; i < fields.length - 1; ++i) { + var field = trimSpaces(fields[i]); + fields[i] = transformClassField(field.middle); + } + var tail = fields.pop(); + for(i = 0; i < cstrs.length; ++i) { + cstrs[i] = transformConstructor(atoms[cstrs[i]]); + } + for(i = 0; i < classes.length; ++i) { + classes[i] = transformInnerClass(atoms[classes[i]]); + } + + return new AstClassBody(name, baseClassName, interfacesNames, functions, methods, fields, cstrs, + classes, { tail: tail }); + }; + + function AstInterface(name, body) { + this.name = name; + this.body = body; + body.owner = this; + } + AstInterface.prototype.toString = function() { + return "var " + this.name + " = " + this.body + ";\n" + + "$p." + this.name + " = " + this.name + ";\n"; + }; + function AstClass(name, body) { + this.name = name; + this.body = body; + body.owner = this; + } + AstClass.prototype.toString = function() { + return "var " + this.name + " = " + this.body + ";\n" + + "$p." + this.name + " = " + this.name + ";\n"; + }; + + function transformGlobalClass(class_) { + var m = classesRegex.exec(class_); // 1 - attr, 2 - class|int, 3 - name, 4 - extends, 5 - implements, 6 - body + classesRegex.lastIndex = 0; + var body = atoms[getAtomIndex(m[6])]; + var oldClassId = currentClassId, newClassId = generateClassId(); + currentClassId = newClassId; + var globalClass; + if(m[2] === "interface") { + globalClass = new AstInterface(m[3], transformInterfaceBody(body, m[3], m[4]) ); + } else { + globalClass = new AstClass(m[3], transformClassBody(body, m[3], m[4], m[5]) ); + } + appendClass(globalClass, newClassId, oldClassId); + currentClassId = oldClassId; + return globalClass; + } + + function AstMethod(name, params, body) { + this.name = name; + this.params = params; + this.body = body; + } + AstMethod.prototype.toString = function(){ + var paramNames = appendToLookupTable({}, this.params.getNames()); + var oldContext = replaceContext; + replaceContext = function (subject) { + return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); + }; + var body = this.params.prependMethodArgs(this.body.toString()); + var result = "function " + this.name + this.params + " " + body + "\n" + + "$p." + this.name + " = " + this.name + ";\n" + + this.name + " = " + this.name + ".bind($p);"; +// "$p." + this.name + " = " + this.name + ";"; + replaceContext = oldContext; + return result; + }; + + function transformGlobalMethod(method) { + var m = methodsRegex.exec(method); + var result = + methodsRegex.lastIndex = 0; + return new AstMethod(m[3], transformParams(atoms[getAtomIndex(m[4])]), + transformStatementsBlock(atoms[getAtomIndex(m[6])])); + } + + function preStatementsTransform(statements) { + var s = statements; + // turns multiple catch blocks into one, because we have no way to properly get into them anyway. + s = s.replace(/\b(catch\s*"B\d+"\s*"A\d+")(\s*catch\s*"B\d+"\s*"A\d+")+/g, "$1"); + return s; + } + + function AstForStatement(argument, misc) { + this.argument = argument; + this.misc = misc; + } + AstForStatement.prototype.toString = function() { + return this.misc.prefix + this.argument.toString(); + }; + function AstCatchStatement(argument, misc) { + this.argument = argument; + this.misc = misc; + } + AstCatchStatement.prototype.toString = function() { + return this.misc.prefix + this.argument.toString(); + }; + function AstPrefixStatement(name, argument, misc) { + this.name = name; + this.argument = argument; + this.misc = misc; + } + AstPrefixStatement.prototype.toString = function() { + var result = this.misc.prefix; + if(this.argument !== undef) { + result += this.argument.toString(); + } + return result; + }; + function AstSwitchCase(expr) { + this.expr = expr; + } + AstSwitchCase.prototype.toString = function() { + return "case " + this.expr + ":"; + }; + function AstLabel(label) { + this.label = label; + } + AstLabel.prototype.toString = function() { + return this.label; + }; + + transformStatements = function(statements, transformMethod, transformClass) { + var nextStatement = new RegExp(/\b(catch|for|if|switch|while|with)\s*"B(\d+)"|\b(do|else|finally|return|throw|try|break|continue)\b|("[ADEH](\d+)")|\b(case)\s+([^:]+):|\b([A-Za-z_$][\w$]*\s*:)|(;)/g); + var res = []; + statements = preStatementsTransform(statements); + var lastIndex = 0, m, space; + // m contains the matches from the nextStatement regexp, null if there are no matches. + // nextStatement.exec starts searching at nextStatement.lastIndex. + while((m = nextStatement.exec(statements)) !== null) { + if(m[1] !== undef) { // catch, for ... + var i = statements.lastIndexOf('"B', nextStatement.lastIndex); + var statementsPrefix = statements.substring(lastIndex, i); + if(m[1] === "for") { + res.push(new AstForStatement(transformForExpression(atoms[m[2]]), + { prefix: statementsPrefix }) ); + } else if(m[1] === "catch") { + res.push(new AstCatchStatement(transformParams(atoms[m[2]]), + { prefix: statementsPrefix }) ); + } else { + res.push(new AstPrefixStatement(m[1], transformExpression(atoms[m[2]]), + { prefix: statementsPrefix }) ); + } + } else if(m[3] !== undef) { // do, else, ... + res.push(new AstPrefixStatement(m[3], undef, + { prefix: statements.substring(lastIndex, nextStatement.lastIndex) }) ); + } else if(m[4] !== undef) { // block, class and methods + space = statements.substring(lastIndex, nextStatement.lastIndex - m[4].length); + if(trim(space).length !== 0) { continue; } // avoiding new type[] {} construct + res.push(space); + var kind = m[4].charAt(1), atomIndex = m[5]; + if(kind === 'D') { + res.push(transformMethod(atoms[atomIndex])); + } else if(kind === 'E') { + res.push(transformClass(atoms[atomIndex])); + } else if(kind === 'H') { + res.push(transformFunction(atoms[atomIndex])); + } else { + res.push(transformStatementsBlock(atoms[atomIndex])); + } + } else if(m[6] !== undef) { // switch case + res.push(new AstSwitchCase(transformExpression(trim(m[7])))); + } else if(m[8] !== undef) { // label + space = statements.substring(lastIndex, nextStatement.lastIndex - m[8].length); + if(trim(space).length !== 0) { continue; } // avoiding ?: construct + res.push(new AstLabel(statements.substring(lastIndex, nextStatement.lastIndex)) ); + } else { // semicolon + var statement = trimSpaces(statements.substring(lastIndex, nextStatement.lastIndex - 1)); + res.push(statement.left); + res.push(transformStatement(statement.middle)); + res.push(statement.right + ";"); + } + lastIndex = nextStatement.lastIndex; + } + var statementsTail = trimSpaces(statements.substring(lastIndex)); + res.push(statementsTail.left); + if(statementsTail.middle !== "") { + res.push(transformStatement(statementsTail.middle)); + res.push(";" + statementsTail.right); + } + return res; + }; + + function getLocalNames(statements) { + var localNames = []; + for(var i=0,l=statements.length;i<l;++i) { + var statement = statements[i]; + if(statement instanceof AstVar) { + localNames = localNames.concat(statement.getNames()); + } else if(statement instanceof AstForStatement && + statement.argument.initStatement instanceof AstVar) { + localNames = localNames.concat(statement.argument.initStatement.getNames()); + } else if(statement instanceof AstInnerInterface || statement instanceof AstInnerClass || + statement instanceof AstInterface || statement instanceof AstClass || + statement instanceof AstMethod || statement instanceof AstFunction) { + localNames.push(statement.name); + } + } + return appendToLookupTable({}, localNames); + } + + function AstStatementsBlock(statements) { + this.statements = statements; + } + AstStatementsBlock.prototype.toString = function() { + var localNames = getLocalNames(this.statements); + var oldContext = replaceContext; + + // replacing context only when necessary + if(!isLookupTableEmpty(localNames)) { + replaceContext = function (subject) { + return localNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); + }; + } + + var result = "{\n" + this.statements.join('') + "\n}"; + replaceContext = oldContext; + return result; + }; + + transformStatementsBlock = function(block) { + var content = trimSpaces(block.substring(1, block.length - 1)); + return new AstStatementsBlock(transformStatements(content.middle)); + }; + + function AstRoot(statements) { + this.statements = statements; + } + AstRoot.prototype.toString = function() { + var classes = [], otherStatements = [], statement; + for (var i = 0, len = this.statements.length; i < len; ++i) { + statement = this.statements[i]; + if (statement instanceof AstClass || statement instanceof AstInterface) { + classes.push(statement); + } else { + otherStatements.push(statement); + } + } + sortByWeight(classes); + + var localNames = getLocalNames(this.statements); + replaceContext = function (subject) { + var name = subject.name; + if(localNames.hasOwnProperty(name)) { + return name; + } + if(globalMembers.hasOwnProperty(name) || + PConstants.hasOwnProperty(name) || + defaultScope.hasOwnProperty(name)) { + return "$p." + name; + } + return name; + }; + var result = "// this code was autogenerated from PJS\n" + + "(function($p) {\n" + + classes.join('') + "\n" + + otherStatements.join('') + "\n})"; + replaceContext = null; + return result; + }; + + transformMain = function() { + var statements = extractClassesAndMethods(atoms[0]); + statements = statements.replace(/\bimport\s+[^;]+;/g, ""); + return new AstRoot( transformStatements(statements, + transformGlobalMethod, transformGlobalClass) ); + }; + + function generateMetadata(ast) { + var globalScope = {}; + var id, class_; + for(id in declaredClasses) { + if(declaredClasses.hasOwnProperty(id)) { + class_ = declaredClasses[id]; + var scopeId = class_.scopeId, name = class_.name; + if(scopeId) { + var scope = declaredClasses[scopeId]; + class_.scope = scope; + if(scope.inScope === undef) { + scope.inScope = {}; + } + scope.inScope[name] = class_; + } else { + globalScope[name] = class_; + } + } + } + + function findInScopes(class_, name) { + var parts = name.split('.'); + var currentScope = class_.scope, found; + while(currentScope) { + if(currentScope.hasOwnProperty(parts[0])) { + found = currentScope[parts[0]]; break; + } + currentScope = currentScope.scope; + } + if(found === undef) { + found = globalScope[parts[0]]; + } + for(var i=1,l=parts.length;i<l && found;++i) { + found = found.inScope[parts[i]]; + } + return found; + } + + for(id in declaredClasses) { + if(declaredClasses.hasOwnProperty(id)) { + class_ = declaredClasses[id]; + var baseClassName = class_.body.baseClassName; + if(baseClassName) { + var parent = findInScopes(class_, baseClassName); + if (parent) { + class_.base = parent; + if (!parent.derived) { + parent.derived = []; + } + parent.derived.push(class_); + } + } + var interfacesNames = class_.body.interfacesNames, + interfaces = [], i, l; + if (interfacesNames && interfacesNames.length > 0) { + for (i = 0, l = interfacesNames.length; i < l; ++i) { + var interface_ = findInScopes(class_, interfacesNames[i]); + interfaces.push(interface_); + if (!interface_) { + continue; + } + if (!interface_.derived) { + interface_.derived = []; + } + interface_.derived.push(class_); + } + if (interfaces.length > 0) { + class_.interfaces = interfaces; + } + } + } + } + } + + function setWeight(ast) { + var queue = [], tocheck = {}; + var id, scopeId, class_; + // queue most inner and non-inherited + for (id in declaredClasses) { + if (declaredClasses.hasOwnProperty(id)) { + class_ = declaredClasses[id]; + if (!class_.inScope && !class_.derived) { + queue.push(id); + class_.weight = 0; + } else { + var dependsOn = []; + if (class_.inScope) { + for (scopeId in class_.inScope) { + if (class_.inScope.hasOwnProperty(scopeId)) { + dependsOn.push(class_.inScope[scopeId]); + } + } + } + if (class_.derived) { + dependsOn = dependsOn.concat(class_.derived); + } + tocheck[id] = dependsOn; + } + } + } + function removeDependentAndCheck(targetId, from) { + var dependsOn = tocheck[targetId]; + if (!dependsOn) { + return false; // no need to process + } + var i = dependsOn.indexOf(from); + if (i < 0) { + return false; + } + dependsOn.splice(i, 1); + if (dependsOn.length > 0) { + return false; + } + delete tocheck[targetId]; + return true; + } + while (queue.length > 0) { + id = queue.shift(); + class_ = declaredClasses[id]; + if (class_.scopeId && removeDependentAndCheck(class_.scopeId, class_)) { + queue.push(class_.scopeId); + declaredClasses[class_.scopeId].weight = class_.weight + 1; + } + if (class_.base && removeDependentAndCheck(class_.base.classId, class_)) { + queue.push(class_.base.classId); + class_.base.weight = class_.weight + 1; + } + if (class_.interfaces) { + var i, l; + for (i = 0, l = class_.interfaces.length; i < l; ++i) { + if (!class_.interfaces[i] || + !removeDependentAndCheck(class_.interfaces[i].classId, class_)) { + continue; + } + queue.push(class_.interfaces[i].classId); + class_.interfaces[i].weight = class_.weight + 1; + } + } + } + } + + var transformed = transformMain(); + generateMetadata(transformed); + setWeight(transformed); + + var redendered = transformed.toString(); + + // remove empty extra lines with space + redendered = redendered.replace(/\s*\n(?:[\t ]*\n)+/g, "\n\n"); + + // convert character codes to characters + redendered = redendered.replace(/__x([0-9A-F]{4})/g, function(all, hexCode) { + return String.fromCharCode(parseInt(hexCode,16)); + }); + + return injectStrings(redendered, strings); + }// Parser ends + + function preprocessCode(aCode, sketch) { + // Parse out @pjs directive, if any. + var dm = new RegExp(/\/\*\s*@pjs\s+((?:[^\*]|\*+[^\*\/])*)\*\//g).exec(aCode); + if (dm && dm.length === 2) { + // masks contents of a JSON to be replaced later + // to protect the contents from further parsing + var jsonItems = [], + directives = dm.splice(1, 2)[0].replace(/\{([\s\S]*?)\}/g, (function() { + return function(all, item) { + jsonItems.push(item); + return "{" + (jsonItems.length-1) + "}"; + }; + }())).replace('\n', '').replace('\r', '').split(";"); + + // We'll L/RTrim, and also remove any surrounding double quotes (e.g., just take string contents) + var clean = function(s) { + return s.replace(/^\s*["']?/, '').replace(/["']?\s*$/, ''); + }; + + for (var i = 0, dl = directives.length; i < dl; i++) { + var pair = directives[i].split('='); + if (pair && pair.length === 2) { + var key = clean(pair[0]), + value = clean(pair[1]), + list = []; + // A few directives require work beyond storying key/value pairings + if (key === "preload") { + list = value.split(','); + // All pre-loaded images will get put in imageCache, keyed on filename + for (var j = 0, jl = list.length; j < jl; j++) { + var imageName = clean(list[j]); + sketch.imageCache.add(imageName); + } + // fonts can be declared as a string containing a url, + // or a JSON object, containing a font name, and a url + } else if (key === "font") { + list = value.split(","); + for (var x = 0, xl = list.length; x < xl; x++) { + var fontName = clean(list[x]), + index = /^\{(\d*?)\}$/.exec(fontName); + // if index is not null, send JSON, otherwise, send string + PFont.preloading.add(index ? JSON.parse("{" + jsonItems[index[1]] + "}") : fontName); + } + } else if (key === "pauseOnBlur") { + sketch.options.pauseOnBlur = value === "true"; + } else if (key === "globalKeyEvents") { + sketch.options.globalKeyEvents = value === "true"; + } else if (key.substring(0, 6) === "param-") { + sketch.params[key.substring(6)] = value; + } else { + sketch.options[key] = value; + } + } + } + } + return aCode; + } + + // Parse/compiles Processing (Java-like) syntax to JavaScript syntax + Processing.compile = function(pdeCode) { + var sketch = new Processing.Sketch(); + var code = preprocessCode(pdeCode, sketch); + var compiledPde = parseProcessing(code); + sketch.sourceCode = compiledPde; + return sketch; + }; + + var PjsConsole = require("../Helpers/PjsConsole"); + Processing.logger = new PjsConsole(document); + + // done + return Processing; +}; + +},{"../Helpers/PjsConsole":5}],27:[function(require,module,exports){ +/** + * Processing.js object + */ + module.exports = function(options, undef) { + var defaultScope = options.defaultScope, + extend = options.extend, + Browser = options.Browser, + ajax = Browser.ajax, + navigator = Browser.navigator, + window = Browser.window, + XMLHttpRequest = window.XMLHttpRequest, + document = Browser.document, + noop = options.noop, + + PConstants = defaultScope.PConstants; + PFont = defaultScope.PFont, + PShapeSVG = defaultScope.PShapeSVG, + PVector = defaultScope.PVector, + Char = Character = defaultScope.Char, + ObjectIterator = defaultScope.ObjectIterator, + XMLElement = defaultScope.XMLElement, + XML = defaultScope.XML; + + // fascinating "read only" jshint error if we don't start a new var block here. + var HTMLCanvasElement = window.HTMLCanvasElement, + HTMLImageElement = window.HTMLImageElement; + + // window.localStorage cannot be accessed if a user is blocking cookies. + // In that case, we make it a temporary source cache object. + var localStorage; + try { localStorage = window.localStorage; } catch (e) { localStorage = {}; } + + var isDOMPresent = ("document" in this) && !("fake" in this.document); + + // document.head polyfill for the benefit of Firefox 3.6 + if (!document.head) { + document.head = document.getElementsByTagName('head')[0]; + } + + var Float32Array = setupTypedArray("Float32Array", "WebGLFloatArray"), + Int32Array = setupTypedArray("Int32Array", "WebGLIntArray"), + Uint16Array = setupTypedArray("Uint16Array", "WebGLUnsignedShortArray"), + Uint8Array = setupTypedArray("Uint8Array", "WebGLUnsignedByteArray"); + + // Typed Arrays: fallback to WebGL arrays or Native JS arrays if unavailable + function setupTypedArray(name, fallback) { + // Check if TypedArray exists, and use if so. + if (name in window) { + return window[name]; + } + + // Check if WebGLArray exists + if (typeof window[fallback] === "function") { + return window[fallback]; + } + + // Use Native JS array + return function(obj) { + if (obj instanceof Array) { + return obj; + } + if (typeof obj === "number") { + var arr = []; + arr.length = obj; + return arr; + } + }; + } + + /* IE9+ quirks mode check - ticket #1606 */ + if (document.documentMode >= 9 && !document.doctype) { + throw("The doctype directive is missing. The recommended doctype in Internet Explorer is the HTML5 doctype: <!DOCTYPE html>"); + } + + // Manage multiple Processing instances + var processingInstances = []; + var processingInstanceIds = {}; + + /** + * instance tracking - adding new instances + */ + var addInstance = function(processing) { + if (processing.externals.canvas.id === undef || !processing.externals.canvas.id.length) { + processing.externals.canvas.id = "__processing" + processingInstances.length; + } + processingInstanceIds[processing.externals.canvas.id] = processingInstances.length; + processingInstances.push(processing); + }; + + /** + * instance tracking - removal + */ + var removeInstance = function(id) { + processingInstances.splice(processingInstanceIds[id], 1); + delete processingInstanceIds[id]; + }; + + + /** + * The Processing object + */ + var Processing = this.Processing = function(aCanvas, aCode, aFunctions) { + + if (!(this instanceof Processing)) { + throw("called Processing constructor as if it were a function: missing 'new'."); + } + + var curElement = {}, + pgraphicsMode = (aCanvas === undef && aCode === undef); + + if (pgraphicsMode) { + curElement = document.createElement("canvas"); + } else { + // We'll take a canvas element or a string for a canvas element's id + curElement = typeof aCanvas === "string" ? document.getElementById(aCanvas) : aCanvas; + } + + if (!('getContext' in curElement)) { + throw("called Processing constructor without passing canvas element reference or id."); + } + + function unimplemented(s) { + Processing.debug('Unimplemented - ' + s); + } + + //////////////////////////////////////////////////////////////////////////// + // JavaScript event binding and releasing + //////////////////////////////////////////////////////////////////////////// + + var eventHandlers = []; + + function attachEventHandler(elem, type, fn) { + if (elem.addEventListener) { + elem.addEventListener(type, fn, false); + } else { + elem.attachEvent("on" + type, fn); + } + eventHandlers.push({elem: elem, type: type, fn: fn}); + } + + function detachEventHandler(eventHandler) { + var elem = eventHandler.elem, + type = eventHandler.type, + fn = eventHandler.fn; + if (elem.removeEventListener) { + elem.removeEventListener(type, fn, false); + } else if (elem.detachEvent) { + elem.detachEvent("on" + type, fn); + } + } + + function removeFirstArgument(args) { + return Array.prototype.slice.call(args, 1); + } + + // When something new is added to "p." it must also be added to the "names" array. + // The names array contains the names of everything that is inside "p." + var p = this; + + p.Char = p.Character = Char; + + // add in the Processing API functions + extend.withCommonFunctions(p); + extend.withMath(p); + extend.withProxyFunctions(p, removeFirstArgument); + extend.withTouch(p, curElement, attachEventHandler, document, PConstants); + + // custom functions and properties are added here + if(aFunctions) { + Object.keys(aFunctions).forEach(function(name) { + p[name] = aFunctions[name]; + }); + } + + // PJS specific (non-p5) methods and properties to externalize + p.externals = { + canvas: curElement, + context: undef, + sketch: undef, + window: window + }; + + p.name = 'Processing.js Instance'; // Set Processing defaults / environment variables + p.use3DContext = false; // default '2d' canvas context + + /** + * Confirms if a Processing program is "focused", meaning that it is + * active and will accept input from mouse or keyboard. This variable + * is "true" if it is focused and "false" if not. This variable is + * often used when you want to warn people they need to click on the + * browser before it will work. + */ + p.focused = false; + p.breakShape = false; + + // Glyph path storage for textFonts + p.glyphTable = {}; + + // Global vars for tracking mouse position + p.pmouseX = 0; + p.pmouseY = 0; + p.mouseX = 0; + p.mouseY = 0; + p.mouseButton = 0; + p.mouseScroll = 0; + + // Undefined event handlers to be replaced by user when needed + p.mouseClicked = undef; + p.mouseDragged = undef; + p.mouseMoved = undef; + p.mousePressed = undef; + p.mouseReleased = undef; + p.mouseScrolled = undef; + p.mouseOver = undef; + p.mouseOut = undef; + p.touchStart = undef; + p.touchEnd = undef; + p.touchMove = undef; + p.touchCancel = undef; + p.key = undef; + p.keyCode = undef; + p.keyPressed = noop; // needed to remove function checks + p.keyReleased = noop; + p.keyTyped = noop; + p.draw = undef; + p.setup = undef; + + // Remapped vars + p.__mousePressed = false; + p.__keyPressed = false; + p.__frameRate = 60; + + // The current animation frame + p.frameCount = 0; + + // The height/width of the canvas + p.width = 100; + p.height = 100; + + // "Private" variables used to maintain state + var curContext, + curSketch, + drawing, // hold a Drawing2D or Drawing3D object + doFill = true, + fillStyle = [1.0, 1.0, 1.0, 1.0], + currentFillColor = 0xFFFFFFFF, + isFillDirty = true, + doStroke = true, + strokeStyle = [0.0, 0.0, 0.0, 1.0], + currentStrokeColor = 0xFF000000, + isStrokeDirty = true, + lineWidth = 1, + loopStarted = false, + renderSmooth = false, + doLoop = true, + looping = 0, + curRectMode = PConstants.CORNER, + curEllipseMode = PConstants.CENTER, + normalX = 0, + normalY = 0, + normalZ = 0, + normalMode = PConstants.NORMAL_MODE_AUTO, + curFrameRate = 60, + curMsPerFrame = 1000/curFrameRate, + curCursor = PConstants.ARROW, + oldCursor = curElement.style.cursor, + curShape = PConstants.POLYGON, + curShapeCount = 0, + curvePoints = [], + curTightness = 0, + curveDet = 20, + curveInited = false, + backgroundObj = -3355444, // rgb(204, 204, 204) is the default gray background colour + bezDetail = 20, + colorModeA = 255, + colorModeX = 255, + colorModeY = 255, + colorModeZ = 255, + pathOpen = false, + mouseDragging = false, + pmouseXLastFrame = 0, + pmouseYLastFrame = 0, + curColorMode = PConstants.RGB, + curTint = null, + curTint3d = null, + getLoaded = false, + start = Date.now(), + timeSinceLastFPS = start, + framesSinceLastFPS = 0, + textcanvas, + curveBasisMatrix, + curveToBezierMatrix, + curveDrawMatrix, + bezierDrawMatrix, + bezierBasisInverse, + bezierBasisMatrix, + curContextCache = { attributes: {}, locations: {} }, + // Shaders + programObject3D, + programObject2D, + programObjectUnlitShape, + boxBuffer, + boxNormBuffer, + boxOutlineBuffer, + rectBuffer, + rectNormBuffer, + sphereBuffer, + lineBuffer, + fillBuffer, + fillColorBuffer, + strokeColorBuffer, + pointBuffer, + shapeTexVBO, + canTex, // texture for createGraphics + textTex, // texture for 3d tex + curTexture = {width:0,height:0}, + curTextureMode = PConstants.IMAGE, + usingTexture = false, + textBuffer, + textureBuffer, + indexBuffer, + // Text alignment + horizontalTextAlignment = PConstants.LEFT, + verticalTextAlignment = PConstants.BASELINE, + textMode = PConstants.MODEL, + // Font state + curFontName = "Arial", + curTextSize = 12, + curTextAscent = 9, + curTextDescent = 2, + curTextLeading = 14, + curTextFont = PFont.get(curFontName, curTextSize), + // Pixels cache + originalContext, + proxyContext = null, + isContextReplaced = false, + setPixelsCached, + maxPixelsCached = 1000, + pressedKeysMap = [], + lastPressedKeyCode = null, + codedKeys = [ PConstants.SHIFT, PConstants.CONTROL, PConstants.ALT, PConstants.CAPSLK, PConstants.PGUP, PConstants.PGDN, + PConstants.END, PConstants.HOME, PConstants.LEFT, PConstants.UP, PConstants.RIGHT, PConstants.DOWN, PConstants.NUMLK, + PConstants.INSERT, PConstants.F1, PConstants.F2, PConstants.F3, PConstants.F4, PConstants.F5, PConstants.F6, PConstants.F7, + PConstants.F8, PConstants.F9, PConstants.F10, PConstants.F11, PConstants.F12, PConstants.META ]; + + // User can only have MAX_LIGHTS lights + var lightCount = 0; + + //sphere stuff + var sphereDetailV = 0, + sphereDetailU = 0, + sphereX = [], + sphereY = [], + sphereZ = [], + sinLUT = new Float32Array(PConstants.SINCOS_LENGTH), + cosLUT = new Float32Array(PConstants.SINCOS_LENGTH), + sphereVerts, + sphereNorms; + + // Camera defaults and settings + var cam, + cameraInv, + modelView, + modelViewInv, + userMatrixStack, + userReverseMatrixStack, + inverseCopy, + projection, + manipulatingCamera = false, + frustumMode = false, + cameraFOV = 60 * (Math.PI / 180), + cameraX = p.width / 2, + cameraY = p.height / 2, + cameraZ = cameraY / Math.tan(cameraFOV / 2), + cameraNear = cameraZ / 10, + cameraFar = cameraZ * 10, + cameraAspect = p.width / p.height; + + var vertArray = [], + curveVertArray = [], + curveVertCount = 0, + isCurve = false, + isBezier = false, + firstVert = true; + + //PShape stuff + var curShapeMode = PConstants.CORNER; + + // Stores states for pushStyle() and popStyle(). + var styleArray = []; + + // The vertices for the box cannot be specified using a triangle strip since each + // side of the cube must have its own set of normals. + // Vertices are specified in a counter-clockwise order. + // Triangles are in this order: back, front, right, bottom, left, top. + var boxVerts = new Float32Array([ + 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, + 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, + 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, + -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5]); + + var boxOutlineVerts = new Float32Array([ + 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, + -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, + -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, + -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5]); + + var boxNorms = new Float32Array([ + 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, + 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, + 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, + -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, + 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]); + + // These verts are used for the fill and stroke using TRIANGLE_FAN and LINE_LOOP. + var rectVerts = new Float32Array([0,0,0, 0,1,0, 1,1,0, 1,0,0]); + + var rectNorms = new Float32Array([0,0,1, 0,0,1, 0,0,1, 0,0,1]); + + // Shader for points and lines in begin/endShape. + var vertexShaderSrcUnlitShape = + "varying vec4 vFrontColor;" + + + "attribute vec3 aVertex;" + + "attribute vec4 aColor;" + + + "uniform mat4 uView;" + + "uniform mat4 uProjection;" + + "uniform float uPointSize;" + + + "void main(void) {" + + " vFrontColor = aColor;" + + " gl_PointSize = uPointSize;" + + " gl_Position = uProjection * uView * vec4(aVertex, 1.0);" + + "}"; + + var fragmentShaderSrcUnlitShape = + "#ifdef GL_ES\n" + + "precision highp float;\n" + + "#endif\n" + + + "varying vec4 vFrontColor;" + + "uniform bool uSmooth;" + + + "void main(void){" + + " if(uSmooth == true){" + + " float dist = distance(gl_PointCoord, vec2(0.5));" + + " if(dist > 0.5){" + + " discard;" + + " }" + + " }" + + " gl_FragColor = vFrontColor;" + + "}"; + + // Shader for rect, text, box outlines, sphere outlines, point() and line(). + var vertexShaderSrc2D = + "varying vec4 vFrontColor;" + + + "attribute vec3 aVertex;" + + "attribute vec2 aTextureCoord;" + + "uniform vec4 uColor;" + + + "uniform mat4 uModel;" + + "uniform mat4 uView;" + + "uniform mat4 uProjection;" + + "uniform float uPointSize;" + + "varying vec2 vTextureCoord;"+ + + "void main(void) {" + + " gl_PointSize = uPointSize;" + + " vFrontColor = uColor;" + + " gl_Position = uProjection * uView * uModel * vec4(aVertex, 1.0);" + + " vTextureCoord = aTextureCoord;" + + "}"; + + var fragmentShaderSrc2D = + "#ifdef GL_ES\n" + + "precision highp float;\n" + + "#endif\n" + + + "varying vec4 vFrontColor;" + + "varying vec2 vTextureCoord;"+ + + "uniform sampler2D uSampler;"+ + "uniform int uIsDrawingText;"+ + "uniform bool uSmooth;" + + + "void main(void){" + + // WebGL does not support POINT_SMOOTH, so we do it ourselves + " if(uSmooth == true){" + + " float dist = distance(gl_PointCoord, vec2(0.5));" + + " if(dist > 0.5){" + + " discard;" + + " }" + + " }" + + + " if(uIsDrawingText == 1){" + + " float alpha = texture2D(uSampler, vTextureCoord).a;"+ + " gl_FragColor = vec4(vFrontColor.rgb * alpha, alpha);"+ + " }" + + " else{" + + " gl_FragColor = vFrontColor;" + + " }" + + "}"; + + var webglMaxTempsWorkaround = /Windows/.test(navigator.userAgent); + + // Vertex shader for boxes and spheres. + var vertexShaderSrc3D = + "varying vec4 vFrontColor;" + + + "attribute vec3 aVertex;" + + "attribute vec3 aNormal;" + + "attribute vec4 aColor;" + + "attribute vec2 aTexture;" + + "varying vec2 vTexture;" + + + "uniform vec4 uColor;" + + + "uniform bool uUsingMat;" + + "uniform vec3 uSpecular;" + + "uniform vec3 uMaterialEmissive;" + + "uniform vec3 uMaterialAmbient;" + + "uniform vec3 uMaterialSpecular;" + + "uniform float uShininess;" + + + "uniform mat4 uModel;" + + "uniform mat4 uView;" + + "uniform mat4 uProjection;" + + "uniform mat4 uNormalTransform;" + + + "uniform int uLightCount;" + + "uniform vec3 uFalloff;" + + + // Careful changing the order of these fields. Some cards + // have issues with memory alignment. + "struct Light {" + + " int type;" + + " vec3 color;" + + " vec3 position;" + + " vec3 direction;" + + " float angle;" + + " vec3 halfVector;" + + " float concentration;" + + "};" + + + // nVidia cards have issues with arrays of structures + // so instead we create 8 instances of Light. + "uniform Light uLights0;" + + "uniform Light uLights1;" + + "uniform Light uLights2;" + + "uniform Light uLights3;" + + "uniform Light uLights4;" + + "uniform Light uLights5;" + + "uniform Light uLights6;" + + "uniform Light uLights7;" + + + // GLSL does not support switch. + "Light getLight(int index){" + + " if(index == 0) return uLights0;" + + " if(index == 1) return uLights1;" + + " if(index == 2) return uLights2;" + + " if(index == 3) return uLights3;" + + " if(index == 4) return uLights4;" + + " if(index == 5) return uLights5;" + + " if(index == 6) return uLights6;" + + // Do not use a conditional for the last return statement + // because some video cards will fail and complain that + // "not all paths return". + " return uLights7;" + + "}" + + + "void AmbientLight( inout vec3 totalAmbient, in vec3 ecPos, in Light light ) {" + + // Get the vector from the light to the vertex and + // get the distance from the current vector to the light position. + " float d = length( light.position - ecPos );" + + " float attenuation = 1.0 / ( uFalloff[0] + ( uFalloff[1] * d ) + ( uFalloff[2] * d * d ));" + + " totalAmbient += light.color * attenuation;" + + "}" + + + /* + col - accumulated color + spec - accumulated specular highlight + vertNormal - Normal of the vertex + ecPos - eye coordinate position + light - light structure + */ + "void DirectionalLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in Light light ) {" + + " float powerFactor = 0.0;" + + " float nDotVP = max(0.0, dot( vertNormal, normalize(-light.position) ));" + + " float nDotVH = max(0.0, dot( vertNormal, normalize(-light.position-normalize(ecPos) )));" + + + " if( nDotVP != 0.0 ){" + + " powerFactor = pow( nDotVH, uShininess );" + + " }" + + + " col += light.color * nDotVP;" + + " spec += uSpecular * powerFactor;" + + "}" + + + /* + col - accumulated color + spec - accumulated specular highlight + vertNormal - Normal of the vertex + ecPos - eye coordinate position + light - light structure + */ + "void PointLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in Light light ) {" + + " float powerFactor;" + + + // Get the vector from the light to the vertex. + " vec3 VP = light.position - ecPos;" + + + // Get the distance from the current vector to the light position. + " float d = length( VP ); " + + + // Normalize the light ray so it can be used in the dot product operation. + " VP = normalize( VP );" + + + " float attenuation = 1.0 / ( uFalloff[0] + ( uFalloff[1] * d ) + ( uFalloff[2] * d * d ));" + + + " float nDotVP = max( 0.0, dot( vertNormal, VP ));" + + " vec3 halfVector = normalize( VP - normalize(ecPos) );" + + " float nDotHV = max( 0.0, dot( vertNormal, halfVector ));" + + + " if( nDotVP == 0.0 ) {" + + " powerFactor = 0.0;" + + " }" + + " else {" + + " powerFactor = pow( nDotHV, uShininess );" + + " }" + + + " spec += uSpecular * powerFactor * attenuation;" + + " col += light.color * nDotVP * attenuation;" + + "}" + + + /* + col - accumulated color + spec - accumulated specular highlight + vertNormal - Normal of the vertex + ecPos - eye coordinate position + light - light structure + */ + "void SpotLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in Light light ) {" + + " float spotAttenuation;" + + " float powerFactor = 0.0;" + + + // Calculate the vector from the current vertex to the light. + " vec3 VP = light.position - ecPos;" + + " vec3 ldir = normalize( -light.direction );" + + + // Get the distance from the spotlight and the vertex + " float d = length( VP );" + + " VP = normalize( VP );" + + + " float attenuation = 1.0 / ( uFalloff[0] + ( uFalloff[1] * d ) + ( uFalloff[2] * d * d ) );" + + + // Dot product of the vector from vertex to light and light direction. + " float spotDot = dot( VP, ldir );" + + + // If the vertex falls inside the cone + (webglMaxTempsWorkaround ? // Windows reports max temps error if light.angle is used + " spotAttenuation = 1.0; " : + " if( spotDot > cos( light.angle ) ) {" + + " spotAttenuation = pow( spotDot, light.concentration );" + + " }" + + " else{" + + " spotAttenuation = 0.0;" + + " }" + + " attenuation *= spotAttenuation;" + + "") + + + " float nDotVP = max( 0.0, dot( vertNormal, VP ) );" + + " vec3 halfVector = normalize( VP - normalize(ecPos) );" + + " float nDotHV = max( 0.0, dot( vertNormal, halfVector ) );" + + + " if( nDotVP != 0.0 ) {" + + " powerFactor = pow( nDotHV, uShininess );" + + " }" + + + " spec += uSpecular * powerFactor * attenuation;" + + " col += light.color * nDotVP * attenuation;" + + "}" + + + "void main(void) {" + + " vec3 finalAmbient = vec3( 0.0 );" + + " vec3 finalDiffuse = vec3( 0.0 );" + + " vec3 finalSpecular = vec3( 0.0 );" + + + " vec4 col = uColor;" + + + " if ( uColor[0] == -1.0 ){" + + " col = aColor;" + + " }" + + + // We use the sphere vertices as the normals when we create the sphere buffer. + // But this only works if the sphere vertices are unit length, so we + // have to normalize the normals here. Since this is only required for spheres + // we could consider placing this in a conditional later on. + " vec3 norm = normalize(vec3( uNormalTransform * vec4( aNormal, 0.0 ) ));" + + + " vec4 ecPos4 = uView * uModel * vec4(aVertex, 1.0);" + + " vec3 ecPos = (vec3(ecPos4))/ecPos4.w;" + + + // If there were no lights this draw call, just use the + // assigned fill color of the shape and the specular value. + " if( uLightCount == 0 ) {" + + " vFrontColor = col + vec4(uMaterialSpecular, 1.0);" + + " }" + + " else {" + + // WebGL forces us to iterate over a constant value + // so we can't iterate using lightCount. + " for( int i = 0; i < 8; i++ ) {" + + " Light l = getLight(i);" + + + // We can stop iterating if we know we have gone past + // the number of lights which are actually on. This gives us a + // significant performance increase with high vertex counts. + " if( i >= uLightCount ){" + + " break;" + + " }" + + + " if( l.type == 0 ) {" + + " AmbientLight( finalAmbient, ecPos, l );" + + " }" + + " else if( l.type == 1 ) {" + + " DirectionalLight( finalDiffuse, finalSpecular, norm, ecPos, l );" + + " }" + + " else if( l.type == 2 ) {" + + " PointLight( finalDiffuse, finalSpecular, norm, ecPos, l );" + + " }" + + " else {" + + " SpotLight( finalDiffuse, finalSpecular, norm, ecPos, l );" + + " }" + + " }" + + + " if( uUsingMat == false ) {" + + " vFrontColor = vec4(" + + " vec3( col ) * finalAmbient +" + + " vec3( col ) * finalDiffuse +" + + " vec3( col ) * finalSpecular," + + " col[3] );" + + " }" + + " else{" + + " vFrontColor = vec4( " + + " uMaterialEmissive + " + + " (vec3(col) * uMaterialAmbient * finalAmbient ) + " + + " (vec3(col) * finalDiffuse) + " + + " (uMaterialSpecular * finalSpecular), " + + " col[3] );" + + " }" + + " }" + + + " vTexture.xy = aTexture.xy;" + + " gl_Position = uProjection * uView * uModel * vec4( aVertex, 1.0 );" + + "}"; + + var fragmentShaderSrc3D = + "#ifdef GL_ES\n" + + "precision highp float;\n" + + "#endif\n" + + + "varying vec4 vFrontColor;" + + + "uniform sampler2D uSampler;" + + "uniform bool uUsingTexture;" + + "varying vec2 vTexture;" + + + // In Processing, when a texture is used, the fill color is ignored + // vec4(1.0,1.0,1.0,0.5) + "void main(void){" + + " if( uUsingTexture ){" + + " gl_FragColor = vec4(texture2D(uSampler, vTexture.xy)) * vFrontColor;" + + " }"+ + " else{" + + " gl_FragColor = vFrontColor;" + + " }" + + "}"; + + //////////////////////////////////////////////////////////////////////////// + // 3D Functions + //////////////////////////////////////////////////////////////////////////// + + /* + * Sets a uniform variable in a program object to a particular + * value. Before calling this function, ensure the correct + * program object has been installed as part of the current + * rendering state by calling useProgram. + * + * On some systems, if the variable exists in the shader but isn't used, + * the compiler will optimize it out and this function will fail. + * + * @param {String} cacheId + * @param {WebGLProgram} programObj program object returned from + * createProgramObject + * @param {String} varName the name of the variable in the shader + * @param {float | Array} varValue either a scalar value or an Array + * + * @returns none + * + * @see uniformi + * @see uniformMatrix + */ + function uniformf(cacheId, programObj, varName, varValue) { + var varLocation = curContextCache.locations[cacheId]; + if(varLocation === undef) { + varLocation = curContext.getUniformLocation(programObj, varName); + curContextCache.locations[cacheId] = varLocation; + } + // the variable won't be found if it was optimized out. + if (varLocation !== null) { + if (varValue.length === 4) { + curContext.uniform4fv(varLocation, varValue); + } else if (varValue.length === 3) { + curContext.uniform3fv(varLocation, varValue); + } else if (varValue.length === 2) { + curContext.uniform2fv(varLocation, varValue); + } else { + curContext.uniform1f(varLocation, varValue); + } + } + } + + /** + * Sets a uniform int or int array in a program object to a particular + * value. Before calling this function, ensure the correct + * program object has been installed as part of the current + * rendering state. + * + * On some systems, if the variable exists in the shader but isn't used, + * the compiler will optimize it out and this function will fail. + * + * @param {String} cacheId + * @param {WebGLProgram} programObj program object returned from + * createProgramObject + * @param {String} varName the name of the variable in the shader + * @param {int | Array} varValue either a scalar value or an Array + * + * @returns none + * + * @see uniformf + * @see uniformMatrix + */ + function uniformi(cacheId, programObj, varName, varValue) { + var varLocation = curContextCache.locations[cacheId]; + if(varLocation === undef) { + varLocation = curContext.getUniformLocation(programObj, varName); + curContextCache.locations[cacheId] = varLocation; + } + // the variable won't be found if it was optimized out. + if (varLocation !== null) { + if (varValue.length === 4) { + curContext.uniform4iv(varLocation, varValue); + } else if (varValue.length === 3) { + curContext.uniform3iv(varLocation, varValue); + } else if (varValue.length === 2) { + curContext.uniform2iv(varLocation, varValue); + } else { + curContext.uniform1i(varLocation, varValue); + } + } + } + + /** + * Sets the value of a uniform matrix variable in a program + * object. Before calling this function, ensure the correct + * program object has been installed as part of the current + * rendering state. + * + * On some systems, if the variable exists in the shader but + * isn't used, the compiler will optimize it out and this + * function will fail. + * + * @param {String} cacheId + * @param {WebGLProgram} programObj program object returned from + * createProgramObject + * @param {String} varName the name of the variable in the shader + * @param {boolean} transpose must be false + * @param {Array} matrix an array of 4, 9 or 16 values + * + * @returns none + * + * @see uniformi + * @see uniformf + */ + function uniformMatrix(cacheId, programObj, varName, transpose, matrix) { + var varLocation = curContextCache.locations[cacheId]; + if(varLocation === undef) { + varLocation = curContext.getUniformLocation(programObj, varName); + curContextCache.locations[cacheId] = varLocation; + } + // The variable won't be found if it was optimized out. + if (varLocation !== -1) { + if (matrix.length === 16) { + curContext.uniformMatrix4fv(varLocation, transpose, matrix); + } else if (matrix.length === 9) { + curContext.uniformMatrix3fv(varLocation, transpose, matrix); + } else { + curContext.uniformMatrix2fv(varLocation, transpose, matrix); + } + } + } + + /** + * Binds the VBO, sets the vertex attribute data for the program + * object and enables the attribute. + * + * On some systems, if the attribute exists in the shader but + * isn't used, the compiler will optimize it out and this + * function will fail. + * + * @param {String} cacheId + * @param {WebGLProgram} programObj program object returned from + * createProgramObject + * @param {String} varName the name of the variable in the shader + * @param {int} size the number of components per vertex attribute + * @param {WebGLBuffer} VBO Vertex Buffer Object + * + * @returns none + * + * @see disableVertexAttribPointer + */ + function vertexAttribPointer(cacheId, programObj, varName, size, VBO) { + var varLocation = curContextCache.attributes[cacheId]; + if(varLocation === undef) { + varLocation = curContext.getAttribLocation(programObj, varName); + curContextCache.attributes[cacheId] = varLocation; + } + if (varLocation !== -1) { + curContext.bindBuffer(curContext.ARRAY_BUFFER, VBO); + curContext.vertexAttribPointer(varLocation, size, curContext.FLOAT, false, 0, 0); + curContext.enableVertexAttribArray(varLocation); + } + } + + /** + * Disables a program object attribute from being sent to WebGL. + * + * @param {String} cacheId + * @param {WebGLProgram} programObj program object returned from + * createProgramObject + * @param {String} varName name of the attribute + * + * @returns none + * + * @see vertexAttribPointer + */ + function disableVertexAttribPointer(cacheId, programObj, varName){ + var varLocation = curContextCache.attributes[cacheId]; + if(varLocation === undef) { + varLocation = curContext.getAttribLocation(programObj, varName); + curContextCache.attributes[cacheId] = varLocation; + } + if (varLocation !== -1) { + curContext.disableVertexAttribArray(varLocation); + } + } + + /** + * Creates a WebGL program object. + * + * @param {String} vetexShaderSource + * @param {String} fragmentShaderSource + * + * @returns {WebGLProgram} A program object + */ + var createProgramObject = function(curContext, vetexShaderSource, fragmentShaderSource) { + var vertexShaderObject = curContext.createShader(curContext.VERTEX_SHADER); + curContext.shaderSource(vertexShaderObject, vetexShaderSource); + curContext.compileShader(vertexShaderObject); + if (!curContext.getShaderParameter(vertexShaderObject, curContext.COMPILE_STATUS)) { + throw curContext.getShaderInfoLog(vertexShaderObject); + } + + var fragmentShaderObject = curContext.createShader(curContext.FRAGMENT_SHADER); + curContext.shaderSource(fragmentShaderObject, fragmentShaderSource); + curContext.compileShader(fragmentShaderObject); + if (!curContext.getShaderParameter(fragmentShaderObject, curContext.COMPILE_STATUS)) { + throw curContext.getShaderInfoLog(fragmentShaderObject); + } + + var programObject = curContext.createProgram(); + curContext.attachShader(programObject, vertexShaderObject); + curContext.attachShader(programObject, fragmentShaderObject); + curContext.linkProgram(programObject); + if (!curContext.getProgramParameter(programObject, curContext.LINK_STATUS)) { + throw "Error linking shaders."; + } + + return programObject; + }; + + //////////////////////////////////////////////////////////////////////////// + // 2D/3D drawing handling + //////////////////////////////////////////////////////////////////////////// + var imageModeCorner = function(x, y, w, h, whAreSizes) { + return { + x: x, + y: y, + w: w, + h: h + }; + }; + var imageModeConvert = imageModeCorner; + + var imageModeCorners = function(x, y, w, h, whAreSizes) { + return { + x: x, + y: y, + w: whAreSizes ? w : w - x, + h: whAreSizes ? h : h - y + }; + }; + + var imageModeCenter = function(x, y, w, h, whAreSizes) { + return { + x: x - w / 2, + y: y - h / 2, + w: w, + h: h + }; + }; + + // Objects for shared, 2D and 3D contexts + var DrawingShared = function(){}; + var Drawing2D = function(){}; + var Drawing3D = function(){}; + var DrawingPre = function(){}; + + // Setup the prototype chain + Drawing2D.prototype = new DrawingShared(); + Drawing2D.prototype.constructor = Drawing2D; + Drawing3D.prototype = new DrawingShared(); + Drawing3D.prototype.constructor = Drawing3D; + DrawingPre.prototype = new DrawingShared(); + DrawingPre.prototype.constructor = DrawingPre; + + // A no-op function for when the user calls 3D functions from a 2D sketch + // We can change this to a throw or console.error() later if we want + DrawingShared.prototype.a3DOnlyFunction = noop; + + /** + * The shape() function displays shapes to the screen. + * Processing currently works with SVG shapes only. + * The <b>shape</b> parameter specifies the shape to display and the <b>x</b> + * and <b>y</b> parameters define the location of the shape from its + * upper-left corner. + * The shape is displayed at its original size unless the <b>width</b> + * and <b>height</b> parameters specify a different size. + * The <b>shapeMode()</b> function changes the way the parameters work. + * A call to <b>shapeMode(CORNERS)</b>, for example, will change the width + * and height parameters to define the x and y values of the opposite corner + * of the shape. + * <br><br> + * Note complex shapes may draw awkwardly with P2D, P3D, and OPENGL. Those + * renderers do not yet support shapes that have holes or complicated breaks. + * + * @param {PShape} shape the shape to display + * @param {int|float} x x-coordinate of the shape + * @param {int|float} y y-coordinate of the shape + * @param {int|float} width width to display the shape + * @param {int|float} height height to display the shape + * + * @see PShape + * @see loadShape() + * @see shapeMode() + */ + p.shape = function(shape, x, y, width, height) { + if (arguments.length >= 1 && arguments[0] !== null) { + if (shape.isVisible()) { + p.pushMatrix(); + if (curShapeMode === PConstants.CENTER) { + if (arguments.length === 5) { + p.translate(x - width/2, y - height/2); + p.scale(width / shape.getWidth(), height / shape.getHeight()); + } else if (arguments.length === 3) { + p.translate(x - shape.getWidth()/2, - shape.getHeight()/2); + } else { + p.translate(-shape.getWidth()/2, -shape.getHeight()/2); + } + } else if (curShapeMode === PConstants.CORNER) { + if (arguments.length === 5) { + p.translate(x, y); + p.scale(width / shape.getWidth(), height / shape.getHeight()); + } else if (arguments.length === 3) { + p.translate(x, y); + } + } else if (curShapeMode === PConstants.CORNERS) { + if (arguments.length === 5) { + width -= x; + height -= y; + p.translate(x, y); + p.scale(width / shape.getWidth(), height / shape.getHeight()); + } else if (arguments.length === 3) { + p.translate(x, y); + } + } + shape.draw(p); + if ((arguments.length === 1 && curShapeMode === PConstants.CENTER ) || arguments.length > 1) { + p.popMatrix(); + } + } + } + }; + + /** + * The shapeMode() function modifies the location from which shapes draw. + * The default mode is <b>shapeMode(CORNER)</b>, which specifies the + * location to be the upper left corner of the shape and uses the third + * and fourth parameters of <b>shape()</b> to specify the width and height. + * The syntax <b>shapeMode(CORNERS)</b> uses the first and second parameters + * of <b>shape()</b> to set the location of one corner and uses the third + * and fourth parameters to set the opposite corner. + * The syntax <b>shapeMode(CENTER)</b> draws the shape from its center point + * and uses the third and forth parameters of <b>shape()</b> 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, <b>loadShape()</b> should be used inside <b>setup()</b> because loading shapes inside <b>draw()</b> will reduce the speed of a sketch. + * + * @param {String} filename an SVG file + * + * @return {PShape} a object of type PShape or null + * @see PShape + * @see PApplet#shape() + * @see PApplet#shapeMode() + */ + p.loadShape = function (filename) { + if (arguments.length === 1) { + if (filename.indexOf(".svg") > -1) { + return new PShapeSVG(null, filename); + } + } + return null; + }; + + /** + * Processing 2.0 function for loading XML files. + * + * @param {String} uri The uri for the xml file to load. + * + * @return {XML} An XML object representing the xml data. + */ + p.loadXML = function(uri) { + return new XML(p, uri); + }; + + /** + * Processing 2.0 function for creating XML elements from string + * + * @param {String} xml the XML source code + * + * @return {XML} An XML object representation of the input XML markup. + */ + p.parseXML = function(xmlstring) { + var element = new XML(); + element.parse(xmlstring); + return element; + }; + + //////////////////////////////////////////////////////////////////////////// + // 2D Matrix + //////////////////////////////////////////////////////////////////////////// + + /** + * Helper function for printMatrix(). Finds the largest scalar + * in the matrix, then number of digits left of the decimal. + * Call from PMatrix2D and PMatrix3D's print() function. + */ + var printMatrixHelper = function(elements) { + var big = 0; + for (var i = 0; i < elements.length; i++) { + if (i !== 0) { + big = Math.max(big, Math.abs(elements[i])); + } else { + big = Math.abs(elements[i]); + } + } + + var digits = (big + "").indexOf("."); + if (digits === 0) { + digits = 1; + } else if (digits === -1) { + digits = (big + "").length; + } + + return digits; + }; + /** + * PMatrix2D is a 3x2 affine matrix implementation. The constructor accepts another PMatrix2D or a list of six float elements. + * If no parameters are provided the matrix is set to the identity matrix. + * + * @param {PMatrix2D} matrix the initial matrix to set to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the second element of the matrix + * @param {float} m02 the third element of the matrix + * @param {float} m10 the fourth element of the matrix + * @param {float} m11 the fifth element of the matrix + * @param {float} m12 the sixth element of the matrix + */ + var PMatrix2D = p.PMatrix2D = function() { + if (arguments.length === 0) { + this.reset(); + } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { + this.set(arguments[0].array()); + } else if (arguments.length === 6) { + this.set(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); + } + }; + /** + * PMatrix2D methods + */ + PMatrix2D.prototype = { + /** + * @member PMatrix2D + * The set() function sets the matrix elements. The function accepts either another PMatrix2D, an array of elements, or a list of six floats. + * + * @param {PMatrix2D} matrix the matrix to set this matrix to + * @param {float[]} elements an array of elements to set this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the third element of the matrix + * @param {float} m10 the fourth element of the matrix + * @param {float} m11 the fith element of the matrix + * @param {float} m12 the sixth element of the matrix + */ + set: function() { + if (arguments.length === 6) { + var a = arguments; + this.set([a[0], a[1], a[2], + a[3], a[4], a[5]]); + } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { + this.elements = arguments[0].array(); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + this.elements = arguments[0].slice(); + } + }, + /** + * @member PMatrix2D + * The get() function returns a copy of this PMatrix2D. + * + * @return {PMatrix2D} a copy of this PMatrix2D + */ + get: function() { + var outgoing = new PMatrix2D(); + outgoing.set(this.elements); + return outgoing; + }, + /** + * @member PMatrix2D + * The reset() function sets this PMatrix2D to the identity matrix. + */ + reset: function() { + this.set([1, 0, 0, 0, 1, 0]); + }, + /** + * @member PMatrix2D + * The array() function returns a copy of the element values. + * @addon + * + * @return {float[]} returns a copy of the element values + */ + array: function array() { + return this.elements.slice(); + }, + /** + * @member PMatrix2D + * The translate() function translates this matrix by moving the current coordinates to the location specified by tx and ty. + * + * @param {float} tx the x-axis coordinate to move to + * @param {float} ty the y-axis coordinate to move to + */ + translate: function(tx, ty) { + this.elements[2] = tx * this.elements[0] + ty * this.elements[1] + this.elements[2]; + this.elements[5] = tx * this.elements[3] + ty * this.elements[4] + this.elements[5]; + }, + /** + * @member PMatrix2D + * The invTranslate() function translates this matrix by moving the current coordinates to the negative location specified by tx and ty. + * + * @param {float} tx the x-axis coordinate to move to + * @param {float} ty the y-axis coordinate to move to + */ + invTranslate: function(tx, ty) { + this.translate(-tx, -ty); + }, + /** + * @member PMatrix2D + * The transpose() function is not used in processingjs. + */ + transpose: function() { + // Does nothing in Processing. + }, + /** + * @member PMatrix2D + * The mult() function multiplied this matrix. + * If two array elements are passed in the function will multiply a two element vector against this matrix. + * If target is null or not length four, a new float array will be returned. + * The values for vec and target can be the same (though that's less efficient). + * If two PVectors are passed in the function multiply the x and y coordinates of a PVector against this matrix. + * + * @param {PVector} source, target the PVectors used to multiply this matrix + * @param {float[]} source, target the arrays used to multiply this matrix + * + * @return {PVector|float[]} returns a PVector or an array representing the new matrix + */ + mult: function(source, target) { + var x, y; + if (source instanceof PVector) { + x = source.x; + y = source.y; + if (!target) { + target = new PVector(); + } + } else if (source instanceof Array) { + x = source[0]; + y = source[1]; + if (!target) { + target = []; + } + } + if (target instanceof Array) { + target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2]; + target[1] = this.elements[3] * x + this.elements[4] * y + this.elements[5]; + } else if (target instanceof PVector) { + target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2]; + target.y = this.elements[3] * x + this.elements[4] * y + this.elements[5]; + target.z = 0; + } + return target; + }, + /** + * @member PMatrix2D + * The multX() function calculates the x component of a vector from a transformation. + * + * @param {float} x the x component of the vector being transformed + * @param {float} y the y component of the vector being transformed + * + * @return {float} returnes the result of the calculation + */ + multX: function(x, y) { + return (x * this.elements[0] + y * this.elements[1] + this.elements[2]); + }, + /** + * @member PMatrix2D + * The multY() function calculates the y component of a vector from a transformation. + * + * @param {float} x the x component of the vector being transformed + * @param {float} y the y component of the vector being transformed + * + * @return {float} returnes the result of the calculation + */ + multY: function(x, y) { + return (x * this.elements[3] + y * this.elements[4] + this.elements[5]); + }, + /** + * @member PMatrix2D + * The skewX() function skews the matrix along the x-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> 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 <b>radians()</b> function. + * + * @param {float} angle angle of skew specified in radians + */ + skewY: function(angle) { + this.apply(1, 0, 1, 0, angle, 0); + }, + /** + * @member PMatrix2D + * The shearX() function shears the matrix along the x-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. + * + * @param {float} angle angle of skew specified in radians + */ + shearX: function(angle) { + this.apply(1, 0, 1, Math.tan(angle) , 0, 0); + }, + /** + * @member PMatrix2D + * The shearY() function shears the matrix along the y-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. + * + * @param {float} angle angle of skew specified in radians + */ + shearY: function(angle) { + this.apply(1, 0, 1, 0, Math.tan(angle), 0); + }, + /** + * @member PMatrix2D + * The determinant() function calvculates the determinant of this matrix. + * + * @return {float} the determinant of the matrix + */ + determinant: function() { + return (this.elements[0] * this.elements[4] - this.elements[1] * this.elements[3]); + }, + /** + * @member PMatrix2D + * The invert() function inverts this matrix + * + * @return {boolean} true if successful + */ + invert: function() { + var d = this.determinant(); + if (Math.abs( d ) > PConstants.MIN_INT) { + var old00 = this.elements[0]; + var old01 = this.elements[1]; + var old02 = this.elements[2]; + var old10 = this.elements[3]; + var old11 = this.elements[4]; + var old12 = this.elements[5]; + this.elements[0] = old11 / d; + this.elements[3] = -old10 / d; + this.elements[1] = -old01 / d; + this.elements[4] = old00 / d; + this.elements[2] = (old01 * old12 - old11 * old02) / d; + this.elements[5] = (old10 * old02 - old00 * old12) / d; + return true; + } + return false; + }, + /** + * @member PMatrix2D + * The scale() function increases or decreases the size of a shape by expanding and contracting vertices. When only one parameter is specified scale will occur in all dimensions. + * This is equivalent to a two parameter call. + * + * @param {float} sx the amount to scale on the x-axis + * @param {float} sy the amount to scale on the y-axis + */ + scale: function(sx, sy) { + if (sx && !sy) { + sy = sx; + } + if (sx && sy) { + this.elements[0] *= sx; + this.elements[1] *= sy; + this.elements[3] *= sx; + this.elements[4] *= sy; + } + }, + /** + * @member PMatrix2D + * The invScale() function decreases or increases the size of a shape by contracting and expanding vertices. When only one parameter is specified scale will occur in all dimensions. + * This is equivalent to a two parameter call. + * + * @param {float} sx the amount to scale on the x-axis + * @param {float} sy the amount to scale on the y-axis + */ + invScale: function(sx, sy) { + if (sx && !sy) { + sy = sx; + } + this.scale(1 / sx, 1 / sy); + }, + /** + * @member PMatrix2D + * The apply() function multiplies the current matrix by the one specified through the parameters. Note that either a PMatrix2D or a list of floats can be passed in. + * + * @param {PMatrix2D} matrix the matrix to apply this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the third element of the matrix + * @param {float} m10 the fourth element of the matrix + * @param {float} m11 the fith element of the matrix + * @param {float} m12 the sixth element of the matrix + */ + apply: function() { + var source; + if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { + source = arguments[0].array(); + } else if (arguments.length === 6) { + source = Array.prototype.slice.call(arguments); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + source = arguments[0]; + } + + var result = [0, 0, this.elements[2], + 0, 0, this.elements[5]]; + var e = 0; + for (var row = 0; row < 2; row++) { + for (var col = 0; col < 3; col++, e++) { + result[e] += this.elements[row * 3 + 0] * source[col + 0] + + this.elements[row * 3 + 1] * source[col + 3]; + } + } + this.elements = result.slice(); + }, + /** + * @member PMatrix2D + * The preApply() function applies another matrix to the left of this one. Note that either a PMatrix2D or elements of a matrix can be passed in. + * + * @param {PMatrix2D} matrix the matrix to apply this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the third element of the matrix + * @param {float} m10 the fourth element of the matrix + * @param {float} m11 the fith element of the matrix + * @param {float} m12 the sixth element of the matrix + */ + preApply: function() { + var source; + if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { + source = arguments[0].array(); + } else if (arguments.length === 6) { + source = Array.prototype.slice.call(arguments); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + source = arguments[0]; + } + var result = [0, 0, source[2], + 0, 0, source[5]]; + result[2] = source[2] + this.elements[2] * source[0] + this.elements[5] * source[1]; + result[5] = source[5] + this.elements[2] * source[3] + this.elements[5] * source[4]; + result[0] = this.elements[0] * source[0] + this.elements[3] * source[1]; + result[3] = this.elements[0] * source[3] + this.elements[3] * source[4]; + result[1] = this.elements[1] * source[0] + this.elements[4] * source[1]; + result[4] = this.elements[1] * source[3] + this.elements[4] * source[4]; + this.elements = result.slice(); + }, + /** + * @member PMatrix2D + * The rotate() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotate: function(angle) { + var c = Math.cos(angle); + var s = Math.sin(angle); + var temp1 = this.elements[0]; + var temp2 = this.elements[1]; + this.elements[0] = c * temp1 + s * temp2; + this.elements[1] = -s * temp1 + c * temp2; + temp1 = this.elements[3]; + temp2 = this.elements[4]; + this.elements[3] = c * temp1 + s * temp2; + this.elements[4] = -s * temp1 + c * temp2; + }, + /** + * @member PMatrix2D + * The rotateZ() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotateZ: function(angle) { + this.rotate(angle); + }, + /** + * @member PMatrix2D + * The invRotateZ() function rotates the matrix in opposite direction. + * + * @param {float} angle the angle of rotation in radiants + */ + invRotateZ: function(angle) { + this.rotateZ(angle - Math.PI); + }, + /** + * @member PMatrix2D + * The print() function prints out the elements of this matrix + */ + print: function() { + var digits = printMatrixHelper(this.elements); + var output = "" + p.nfs(this.elements[0], digits, 4) + " " + + p.nfs(this.elements[1], digits, 4) + " " + + p.nfs(this.elements[2], digits, 4) + "\n" + + p.nfs(this.elements[3], digits, 4) + " " + + p.nfs(this.elements[4], digits, 4) + " " + + p.nfs(this.elements[5], digits, 4) + "\n\n"; + p.println(output); + } + }; + + /** + * PMatrix3D is a 4x4 matrix implementation. The constructor accepts another PMatrix3D or a list of six or sixteen float elements. + * If no parameters are provided the matrix is set to the identity matrix. + */ + var PMatrix3D = p.PMatrix3D = function() { + // When a matrix is created, it is set to an identity matrix + this.reset(); + }; + /** + * PMatrix3D methods + */ + PMatrix3D.prototype = { + /** + * @member PMatrix2D + * The set() function sets the matrix elements. The function accepts either another PMatrix3D, an array of elements, or a list of six or sixteen floats. + * + * @param {PMatrix3D} matrix the initial matrix to set to + * @param {float[]} elements an array of elements to set this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the second element of the matrix + * @param {float} m02 the third element of the matrix + * @param {float} m03 the fourth element of the matrix + * @param {float} m10 the fifth element of the matrix + * @param {float} m11 the sixth element of the matrix + * @param {float} m12 the seventh element of the matrix + * @param {float} m13 the eight element of the matrix + * @param {float} m20 the nineth element of the matrix + * @param {float} m21 the tenth element of the matrix + * @param {float} m22 the eleventh element of the matrix + * @param {float} m23 the twelveth element of the matrix + * @param {float} m30 the thirteenth element of the matrix + * @param {float} m31 the fourtheenth element of the matrix + * @param {float} m32 the fivetheenth element of the matrix + * @param {float} m33 the sixteenth element of the matrix + */ + set: function() { + if (arguments.length === 16) { + this.elements = Array.prototype.slice.call(arguments); + } else if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { + this.elements = arguments[0].array(); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + this.elements = arguments[0].slice(); + } + }, + /** + * @member PMatrix3D + * The get() function returns a copy of this PMatrix3D. + * + * @return {PMatrix3D} a copy of this PMatrix3D + */ + get: function() { + var outgoing = new PMatrix3D(); + outgoing.set(this.elements); + return outgoing; + }, + /** + * @member PMatrix3D + * The reset() function sets this PMatrix3D to the identity matrix. + */ + reset: function() { + this.elements = [1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1]; + }, + /** + * @member PMatrix3D + * The array() function returns a copy of the element values. + * @addon + * + * @return {float[]} returns a copy of the element values + */ + array: function array() { + return this.elements.slice(); + }, + /** + * @member PMatrix3D + * The translate() function translates this matrix by moving the current coordinates to the location specified by tx, ty, and tz. + * + * @param {float} tx the x-axis coordinate to move to + * @param {float} ty the y-axis coordinate to move to + * @param {float} tz the z-axis coordinate to move to + */ + translate: function(tx, ty, tz) { + if (tz === undef) { + tz = 0; + } + + this.elements[3] += tx * this.elements[0] + ty * this.elements[1] + tz * this.elements[2]; + this.elements[7] += tx * this.elements[4] + ty * this.elements[5] + tz * this.elements[6]; + this.elements[11] += tx * this.elements[8] + ty * this.elements[9] + tz * this.elements[10]; + this.elements[15] += tx * this.elements[12] + ty * this.elements[13] + tz * this.elements[14]; + }, + /** + * @member PMatrix3D + * The transpose() function transpose this matrix. + */ + transpose: function() { + var temp = this.elements[4]; + this.elements[4] = this.elements[1]; + this.elements[1] = temp; + + temp = this.elements[8]; + this.elements[8] = this.elements[2]; + this.elements[2] = temp; + + temp = this.elements[6]; + this.elements[6] = this.elements[9]; + this.elements[9] = temp; + + temp = this.elements[3]; + this.elements[3] = this.elements[12]; + this.elements[12] = temp; + + temp = this.elements[7]; + this.elements[7] = this.elements[13]; + this.elements[13] = temp; + + temp = this.elements[11]; + this.elements[11] = this.elements[14]; + this.elements[14] = temp; + }, + /** + * @member PMatrix3D + * The mult() function multiplied this matrix. + * If two array elements are passed in the function will multiply a two element vector against this matrix. + * If target is null or not length four, a new float array will be returned. + * The values for vec and target can be the same (though that's less efficient). + * If two PVectors are passed in the function multiply the x and y coordinates of a PVector against this matrix. + * + * @param {PVector} source, target the PVectors used to multiply this matrix + * @param {float[]} source, target the arrays used to multiply this matrix + * + * @return {PVector|float[]} returns a PVector or an array representing the new matrix + */ + mult: function(source, target) { + var x, y, z, w; + if (source instanceof PVector) { + x = source.x; + y = source.y; + z = source.z; + w = 1; + if (!target) { + target = new PVector(); + } + } else if (source instanceof Array) { + x = source[0]; + y = source[1]; + z = source[2]; + w = source[3] || 1; + + if ( !target || (target.length !== 3 && target.length !== 4) ) { + target = [0, 0, 0]; + } + } + + if (target instanceof Array) { + if (target.length === 3) { + target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; + target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; + target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; + } else if (target.length === 4) { + target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w; + target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w; + target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w; + target[3] = this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w; + } + } + if (target instanceof PVector) { + target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; + target.y = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; + target.z = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; + } + return target; + }, + /** + * @member PMatrix3D + * The preApply() function applies another matrix to the left of this one. Note that either a PMatrix3D or elements of a matrix can be passed in. + * + * @param {PMatrix3D} matrix the matrix to apply this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the second element of the matrix + * @param {float} m02 the third element of the matrix + * @param {float} m03 the fourth element of the matrix + * @param {float} m10 the fifth element of the matrix + * @param {float} m11 the sixth element of the matrix + * @param {float} m12 the seventh element of the matrix + * @param {float} m13 the eight element of the matrix + * @param {float} m20 the nineth element of the matrix + * @param {float} m21 the tenth element of the matrix + * @param {float} m22 the eleventh element of the matrix + * @param {float} m23 the twelveth element of the matrix + * @param {float} m30 the thirteenth element of the matrix + * @param {float} m31 the fourtheenth element of the matrix + * @param {float} m32 the fivetheenth element of the matrix + * @param {float} m33 the sixteenth element of the matrix + */ + preApply: function() { + var source; + if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { + source = arguments[0].array(); + } else if (arguments.length === 16) { + source = Array.prototype.slice.call(arguments); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + source = arguments[0]; + } + + var result = [0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0]; + var e = 0; + for (var row = 0; row < 4; row++) { + for (var col = 0; col < 4; col++, e++) { + result[e] += this.elements[col + 0] * source[row * 4 + 0] + this.elements[col + 4] * + source[row * 4 + 1] + this.elements[col + 8] * source[row * 4 + 2] + + this.elements[col + 12] * source[row * 4 + 3]; + } + } + this.elements = result.slice(); + }, + /** + * @member PMatrix3D + * The apply() function multiplies the current matrix by the one specified through the parameters. Note that either a PMatrix3D or a list of floats can be passed in. + * + * @param {PMatrix3D} matrix the matrix to apply this matrix to + * @param {float} m00 the first element of the matrix + * @param {float} m01 the second element of the matrix + * @param {float} m02 the third element of the matrix + * @param {float} m03 the fourth element of the matrix + * @param {float} m10 the fifth element of the matrix + * @param {float} m11 the sixth element of the matrix + * @param {float} m12 the seventh element of the matrix + * @param {float} m13 the eight element of the matrix + * @param {float} m20 the nineth element of the matrix + * @param {float} m21 the tenth element of the matrix + * @param {float} m22 the eleventh element of the matrix + * @param {float} m23 the twelveth element of the matrix + * @param {float} m30 the thirteenth element of the matrix + * @param {float} m31 the fourtheenth element of the matrix + * @param {float} m32 the fivetheenth element of the matrix + * @param {float} m33 the sixteenth element of the matrix + */ + apply: function() { + var source; + if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { + source = arguments[0].array(); + } else if (arguments.length === 16) { + source = Array.prototype.slice.call(arguments); + } else if (arguments.length === 1 && arguments[0] instanceof Array) { + source = arguments[0]; + } + + var result = [0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0]; + var e = 0; + for (var row = 0; row < 4; row++) { + for (var col = 0; col < 4; col++, e++) { + result[e] += this.elements[row * 4 + 0] * source[col + 0] + this.elements[row * 4 + 1] * + source[col + 4] + this.elements[row * 4 + 2] * source[col + 8] + + this.elements[row * 4 + 3] * source[col + 12]; + } + } + this.elements = result.slice(); + }, + /** + * @member PMatrix3D + * The rotate() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotate: function(angle, v0, v1, v2) { + if (arguments.length < 4) { + this.rotateZ(angle); + } else { + var v = new PVector(v0, v1, v2); + var m = v.mag(); + if (m === 0) { + return; + } else if (m != 1) { + v.normalize(); + v0 = v.x; + v1 = v.y; + v2 = v.z; + } + var c = p.cos(angle); + var s = p.sin(angle); + var t = 1.0 - c; + + this.apply((t * v0 * v0) + c, + (t * v0 * v1) - (s * v2), + (t * v0 * v2) + (s * v1), + 0, + (t * v0 * v1) + (s * v2), + (t * v1 * v1) + c, + (t * v1 * v2) - (s * v0), + 0, + (t * v0 * v2) - (s * v1), + (t * v1 * v2) + (s * v0), + (t * v2 * v2) + c, + 0, + 0, 0, 0, 1); + } + }, + /** + * @member PMatrix3D + * The invApply() function applies the inverted matrix to this matrix. + * + * @param {float} m00 the first element of the matrix + * @param {float} m01 the second element of the matrix + * @param {float} m02 the third element of the matrix + * @param {float} m03 the fourth element of the matrix + * @param {float} m10 the fifth element of the matrix + * @param {float} m11 the sixth element of the matrix + * @param {float} m12 the seventh element of the matrix + * @param {float} m13 the eight element of the matrix + * @param {float} m20 the nineth element of the matrix + * @param {float} m21 the tenth element of the matrix + * @param {float} m22 the eleventh element of the matrix + * @param {float} m23 the twelveth element of the matrix + * @param {float} m30 the thirteenth element of the matrix + * @param {float} m31 the fourtheenth element of the matrix + * @param {float} m32 the fivetheenth element of the matrix + * @param {float} m33 the sixteenth element of the matrix + * + * @return {boolean} returns true if the operation was successful. + */ + invApply: function() { + if (inverseCopy === undef) { + inverseCopy = new PMatrix3D(); + } + var a = arguments; + inverseCopy.set(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], + a[9], a[10], a[11], a[12], a[13], a[14], a[15]); + + if (!inverseCopy.invert()) { + return false; + } + this.preApply(inverseCopy); + return true; + }, + /** + * @member PMatrix3D + * The rotateZ() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotateX: function(angle) { + var c = p.cos(angle); + var s = p.sin(angle); + this.apply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]); + }, + /** + * @member PMatrix3D + * The rotateY() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotateY: function(angle) { + var c = p.cos(angle); + var s = p.sin(angle); + this.apply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]); + }, + /** + * @member PMatrix3D + * The rotateZ() function rotates the matrix. + * + * @param {float} angle the angle of rotation in radiants + */ + rotateZ: function(angle) { + var c = Math.cos(angle); + var s = Math.sin(angle); + this.apply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); + }, + /** + * @member PMatrix3D + * The scale() function increases or decreases the size of a matrix by expanding and contracting vertices. When only one parameter is specified scale will occur in all dimensions. + * This is equivalent to a three parameter call. + * + * @param {float} sx the amount to scale on the x-axis + * @param {float} sy the amount to scale on the y-axis + * @param {float} sz the amount to scale on the z-axis + */ + scale: function(sx, sy, sz) { + if (sx && !sy && !sz) { + sy = sz = sx; + } else if (sx && sy && !sz) { + sz = 1; + } + + if (sx && sy && sz) { + this.elements[0] *= sx; + this.elements[1] *= sy; + this.elements[2] *= sz; + this.elements[4] *= sx; + this.elements[5] *= sy; + this.elements[6] *= sz; + this.elements[8] *= sx; + this.elements[9] *= sy; + this.elements[10] *= sz; + this.elements[12] *= sx; + this.elements[13] *= sy; + this.elements[14] *= sz; + } + }, + /** + * @member PMatrix3D + * The skewX() function skews the matrix along the x-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> 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 <b>radians()</b> function. + * + * @param {float} angle angle of skew specified in radians + */ + skewY: function(angle) { + var t = Math.tan(angle); + this.apply(1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + }, + /** + * @member PMatrix3D + * The shearX() function shears the matrix along the x-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. + * + * @param {float} angle angle of shear specified in radians + */ + shearX: function(angle) { + var t = Math.tan(angle); + this.apply(1, t, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + }, + /** + * @member PMatrix3D + * The shearY() function shears the matrix along the y-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. + * + * @param {float} angle angle of shear specified in radians + */ + shearY: function(angle) { + var t = Math.tan(angle); + this.apply(1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + }, + multX: function(x, y, z, w) { + if (!z) { + return this.elements[0] * x + this.elements[1] * y + this.elements[3]; + } + if (!w) { + return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; + } + return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w; + }, + multY: function(x, y, z, w) { + if (!z) { + return this.elements[4] * x + this.elements[5] * y + this.elements[7]; + } + if (!w) { + return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; + } + return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w; + }, + multZ: function(x, y, z, w) { + if (!w) { + return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; + } + return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w; + }, + multW: function(x, y, z, w) { + if (!w) { + return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15]; + } + return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w; + }, + /** + * @member PMatrix3D + * The invert() function inverts this matrix + * + * @return {boolean} true if successful + */ + invert: function() { + var fA0 = this.elements[0] * this.elements[5] - this.elements[1] * this.elements[4]; + var fA1 = this.elements[0] * this.elements[6] - this.elements[2] * this.elements[4]; + var fA2 = this.elements[0] * this.elements[7] - this.elements[3] * this.elements[4]; + var fA3 = this.elements[1] * this.elements[6] - this.elements[2] * this.elements[5]; + var fA4 = this.elements[1] * this.elements[7] - this.elements[3] * this.elements[5]; + var fA5 = this.elements[2] * this.elements[7] - this.elements[3] * this.elements[6]; + var fB0 = this.elements[8] * this.elements[13] - this.elements[9] * this.elements[12]; + var fB1 = this.elements[8] * this.elements[14] - this.elements[10] * this.elements[12]; + var fB2 = this.elements[8] * this.elements[15] - this.elements[11] * this.elements[12]; + var fB3 = this.elements[9] * this.elements[14] - this.elements[10] * this.elements[13]; + var fB4 = this.elements[9] * this.elements[15] - this.elements[11] * this.elements[13]; + var fB5 = this.elements[10] * this.elements[15] - this.elements[11] * this.elements[14]; + + // Determinant + var fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; + + // Account for a very small value + // return false if not successful. + if (Math.abs(fDet) <= 1e-9) { + return false; + } + + var kInv = []; + kInv[0] = +this.elements[5] * fB5 - this.elements[6] * fB4 + this.elements[7] * fB3; + kInv[4] = -this.elements[4] * fB5 + this.elements[6] * fB2 - this.elements[7] * fB1; + kInv[8] = +this.elements[4] * fB4 - this.elements[5] * fB2 + this.elements[7] * fB0; + kInv[12] = -this.elements[4] * fB3 + this.elements[5] * fB1 - this.elements[6] * fB0; + kInv[1] = -this.elements[1] * fB5 + this.elements[2] * fB4 - this.elements[3] * fB3; + kInv[5] = +this.elements[0] * fB5 - this.elements[2] * fB2 + this.elements[3] * fB1; + kInv[9] = -this.elements[0] * fB4 + this.elements[1] * fB2 - this.elements[3] * fB0; + kInv[13] = +this.elements[0] * fB3 - this.elements[1] * fB1 + this.elements[2] * fB0; + kInv[2] = +this.elements[13] * fA5 - this.elements[14] * fA4 + this.elements[15] * fA3; + kInv[6] = -this.elements[12] * fA5 + this.elements[14] * fA2 - this.elements[15] * fA1; + kInv[10] = +this.elements[12] * fA4 - this.elements[13] * fA2 + this.elements[15] * fA0; + kInv[14] = -this.elements[12] * fA3 + this.elements[13] * fA1 - this.elements[14] * fA0; + kInv[3] = -this.elements[9] * fA5 + this.elements[10] * fA4 - this.elements[11] * fA3; + kInv[7] = +this.elements[8] * fA5 - this.elements[10] * fA2 + this.elements[11] * fA1; + kInv[11] = -this.elements[8] * fA4 + this.elements[9] * fA2 - this.elements[11] * fA0; + kInv[15] = +this.elements[8] * fA3 - this.elements[9] * fA1 + this.elements[10] * fA0; + + // Inverse using Determinant + var fInvDet = 1.0 / fDet; + kInv[0] *= fInvDet; + kInv[1] *= fInvDet; + kInv[2] *= fInvDet; + kInv[3] *= fInvDet; + kInv[4] *= fInvDet; + kInv[5] *= fInvDet; + kInv[6] *= fInvDet; + kInv[7] *= fInvDet; + kInv[8] *= fInvDet; + kInv[9] *= fInvDet; + kInv[10] *= fInvDet; + kInv[11] *= fInvDet; + kInv[12] *= fInvDet; + kInv[13] *= fInvDet; + kInv[14] *= fInvDet; + kInv[15] *= fInvDet; + + this.elements = kInv.slice(); + return true; + }, + toString: function() { + var str = ""; + for (var i = 0; i < 15; i++) { + str += this.elements[i] + ", "; + } + str += this.elements[15]; + return str; + }, + /** + * @member PMatrix3D + * The print() function prints out the elements of this matrix + */ + print: function() { + var digits = printMatrixHelper(this.elements); + + var output = "" + p.nfs(this.elements[0], digits, 4) + " " + p.nfs(this.elements[1], digits, 4) + + " " + p.nfs(this.elements[2], digits, 4) + " " + p.nfs(this.elements[3], digits, 4) + + "\n" + p.nfs(this.elements[4], digits, 4) + " " + p.nfs(this.elements[5], digits, 4) + + " " + p.nfs(this.elements[6], digits, 4) + " " + p.nfs(this.elements[7], digits, 4) + + "\n" + p.nfs(this.elements[8], digits, 4) + " " + p.nfs(this.elements[9], digits, 4) + + " " + p.nfs(this.elements[10], digits, 4) + " " + p.nfs(this.elements[11], digits, 4) + + "\n" + p.nfs(this.elements[12], digits, 4) + " " + p.nfs(this.elements[13], digits, 4) + + " " + p.nfs(this.elements[14], digits, 4) + " " + p.nfs(this.elements[15], digits, 4) + "\n\n"; + p.println(output); + }, + invTranslate: function(tx, ty, tz) { + this.preApply(1, 0, 0, -tx, 0, 1, 0, -ty, 0, 0, 1, -tz, 0, 0, 0, 1); + }, + invRotateX: function(angle) { + var c = Math.cos(-angle); + var s = Math.sin(-angle); + this.preApply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]); + }, + invRotateY: function(angle) { + var c = Math.cos(-angle); + var s = Math.sin(-angle); + this.preApply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]); + }, + invRotateZ: function(angle) { + var c = Math.cos(-angle); + var s = Math.sin(-angle); + this.preApply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); + }, + invScale: function(x, y, z) { + this.preApply([1 / x, 0, 0, 0, 0, 1 / y, 0, 0, 0, 0, 1 / z, 0, 0, 0, 0, 1]); + } + }; + + /** + * @private + * The matrix stack stores the transformations and translations that occur within the space. + */ + var PMatrixStack = p.PMatrixStack = function() { + this.matrixStack = []; + }; + + /** + * @member PMatrixStack + * load pushes the matrix given in the function into the stack + * + * @param {Object | Array} matrix the matrix to be pushed into the stack + */ + PMatrixStack.prototype.load = function() { + var tmpMatrix = drawing.$newPMatrix(); + + if (arguments.length === 1) { + tmpMatrix.set(arguments[0]); + } else { + tmpMatrix.set(arguments); + } + this.matrixStack.push(tmpMatrix); + }; + + Drawing2D.prototype.$newPMatrix = function() { + return new PMatrix2D(); + }; + + Drawing3D.prototype.$newPMatrix = function() { + return new PMatrix3D(); + }; + + /** + * @member PMatrixStack + * push adds a duplicate of the top of the stack onto the stack - uses the peek function + */ + PMatrixStack.prototype.push = function() { + this.matrixStack.push(this.peek()); + }; + + /** + * @member PMatrixStack + * pop removes returns the matrix at the top of the stack + * + * @returns {Object} the matrix at the top of the stack + */ + PMatrixStack.prototype.pop = function() { + return this.matrixStack.pop(); + }; + + /** + * @member PMatrixStack + * peek returns but doesn't remove the matrix at the top of the stack + * + * @returns {Object} the matrix at the top of the stack + */ + PMatrixStack.prototype.peek = function() { + var tmpMatrix = drawing.$newPMatrix(); + + tmpMatrix.set(this.matrixStack[this.matrixStack.length - 1]); + return tmpMatrix; + }; + + /** + * @member PMatrixStack + * this function multiplies the matrix at the top of the stack with the matrix given as a parameter + * + * @param {Object | Array} matrix the matrix to be multiplied into the stack + */ + PMatrixStack.prototype.mult = function(matrix) { + this.matrixStack[this.matrixStack.length - 1].apply(matrix); + }; + + //////////////////////////////////////////////////////////////////////////// + // Array handling + //////////////////////////////////////////////////////////////////////////// + + /** + * The split() function breaks a string into pieces using a character or string + * as the divider. The delim parameter specifies the character or characters that + * mark the boundaries between each piece. A String[] array is returned that contains + * each of the pieces. + * If the result is a set of numbers, you can convert the String[] array to to a float[] + * or int[] array using the datatype conversion functions int() and float() (see example above). + * The splitTokens() function works in a similar fashion, except that it splits using a range + * of characters instead of a specific character or sequence. + * + * @param {String} str the String to be split + * @param {String} delim the character or String used to separate the data + * + * @returns {string[]} The new string array + * + * @see splitTokens + * @see join + * @see trim + */ + p.split = function(str, delim) { + return str.split(delim); + }; + + /** + * The splitTokens() function splits a String at one or many character "tokens." The tokens + * parameter specifies the character or characters to be used as a boundary. + * If no tokens character is specified, any whitespace character is used to split. + * Whitespace characters include tab (\t), line feed (\n), carriage return (\r), form + * feed (\f), and space. To convert a String to an array of integers or floats, use the + * datatype conversion functions int() and float() to convert the array of Strings. + * + * @param {String} str the String to be split + * @param {Char[]} tokens list of individual characters that will be used as separators + * + * @returns {string[]} The new string array + * + * @see split + * @see join + * @see trim + */ + p.splitTokens = function(str, tokens) { + if (tokens === undef) { + return str.split(/\s+/g); + } + + var chars = tokens.split(/()/g), + buffer = "", + len = str.length, + i, c, + tokenized = []; + + for (i = 0; i < len; i++) { + c = str[i]; + if (chars.indexOf(c) > -1) { + if (buffer !== "") { + tokenized.push(buffer); + } + buffer = ""; + } else { + buffer += c; + } + } + + if (buffer !== "") { + tokenized.push(buffer); + } + + return tokenized; + }; + + /** + * Expands an array by one element and adds data to the new position. The datatype of + * the element parameter must be the same as the datatype of the array. + * When using an array of objects, the data returned from the function must be cast to + * the object array's data type. For example: SomeClass[] items = (SomeClass[]) + * append(originalArray, element). + * + * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array boolean[], + * byte[], char[], int[], float[], or String[], or an array of objects + * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} element new data for the array + * + * @returns Array (the same datatype as the input) + * + * @see shorten + * @see expand + */ + p.append = function(array, element) { + array[array.length] = element; + return array; + }; + + /** + * Concatenates two arrays. For example, concatenating the array { 1, 2, 3 } and the + * array { 4, 5, 6 } yields { 1, 2, 3, 4, 5, 6 }. Both parameters must be arrays of the + * same datatype. + * When using an array of objects, the data returned from the function must be cast to the + * object array's data type. For example: SomeClass[] items = (SomeClass[]) concat(array1, array2). + * + * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array1 boolean[], + * byte[], char[], int[], float[], String[], or an array of objects + * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array2 boolean[], + * byte[], char[], int[], float[], String[], or an array of objects + * + * @returns Array (the same datatype as the input) + * + * @see splice + */ + p.concat = function(array1, array2) { + return array1.concat(array2); + }; + + /** + * Sorts an array of numbers from smallest to largest and puts an array of + * words in alphabetical order. The original array is not modified, a + * re-ordered array is returned. The count parameter states the number of + * elements to sort. For example if there are 12 elements in an array and + * if count is the value 5, only the first five elements on the array will + * be sorted. Alphabetical ordering is case insensitive. + * + * @param {String[] | int[] | float[]} array Array of elements to sort + * @param {int} numElem Number of elements to sort + * + * @returns {String[] | int[] | float[]} Array (same datatype as the input) + * + * @see reverse + */ + p.sort = function(array, numElem) { + var ret = []; + + // depending on the type used (int, float) or string + // we'll need to use a different compare function + if (array.length > 0) { + // copy since we need to return another array + var elemsToCopy = numElem > 0 ? numElem : array.length; + for (var i = 0; i < elemsToCopy; i++) { + ret.push(array[i]); + } + if (typeof array[0] === "string") { + ret.sort(); + } + // int or float + else { + ret.sort(function(a, b) { + return a - b; + }); + } + + // copy on the rest of the elements that were not sorted in case the user + // only wanted a subset of an array to be sorted. + if (numElem > 0) { + for (var j = ret.length; j < array.length; j++) { + ret.push(array[j]); + } + } + } + return ret; + }; + + /** + * Inserts a value or array of values into an existing array. The first two parameters must + * be of the same datatype. The array parameter defines the array which will be modified + * and the second parameter defines the data which will be inserted. When using an array + * of objects, the data returned from the function must be cast to the object array's data + * type. For example: SomeClass[] items = (SomeClass[]) splice(array1, array2, index). + * + * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array boolean[], + * byte[], char[], int[], float[], String[], or an array of objects + * @param {boolean|byte|char|int|float|String|boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} + * value boolean, byte, char, int, float, String, boolean[], byte[], char[], int[], + * float[], String[], or other Object: value or an array of objects to be spliced in + * @param {int} index position in the array from which to insert data + * + * @returns Array (the same datatype as the input) + * + * @see contract + * @see subset + */ + p.splice = function(array, value, index) { + + // Trying to splice an empty array into "array" in P5 won't do + // anything, just return the original. + if(value.length === 0) + { + return array; + } + + // If the second argument was an array, we'll need to iterate over all + // the "value" elements and add one by one because + // array.splice(index, 0, value); + // would create a multi-dimensional array which isn't what we want. + if(value instanceof Array) { + for(var i = 0, j = index; i < value.length; j++,i++) { + array.splice(j, 0, value[i]); + } + } else { + array.splice(index, 0, value); + } + + return array; + }; + + /** + * Extracts an array of elements from an existing array. The array parameter defines the + * array from which the elements will be copied and the offset and length parameters determine + * which elements to extract. If no length is given, elements will be extracted from the offset + * to the end of the array. When specifying the offset remember the first array element is 0. + * This function does not change the source array. + * When using an array of objects, the data returned from the function must be cast to the + * object array's data type. + * + * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array boolean[], + * byte[], char[], int[], float[], String[], or an array of objects + * @param {int} offset position to begin + * @param {int} length number of values to extract + * + * @returns Array (the same datatype as the input) + * + * @see splice + */ + p.subset = function(array, offset, length) { + var end = (length !== undef) ? offset + length : array.length; + return array.slice(offset, end); + }; + + /** + * Combines an array of Strings into one String, each separated by the character(s) used for + * the separator parameter. To join arrays of ints or floats, it's necessary to first convert + * them to strings using nf() or nfs(). + * + * @param {Array} array array of Strings + * @param {char|String} separator char or String to be placed between each item + * + * @returns {String} The combined string + * + * @see split + * @see trim + * @see nf + * @see nfs + */ + p.join = function(array, seperator) { + return array.join(seperator); + }; + + /** + * Decreases an array by one element and returns the shortened array. When using an + * array of objects, the data returned from the function must be cast to the object array's + * data type. For example: SomeClass[] items = (SomeClass[]) shorten(originalArray). + * + * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array + * boolean[], byte[], char[], int[], float[], or String[], or an array of objects + * + * @returns Array (the same datatype as the input) + * + * @see append + * @see expand + */ + p.shorten = function(ary) { + var newary = []; + + // copy array into new array + var len = ary.length; + for (var i = 0; i < len; i++) { + newary[i] = ary[i]; + } + newary.pop(); + + return newary; + }; + + /** + * Increases the size of an array. By default, this function doubles the size of the array, + * but the optional newSize parameter provides precise control over the increase in size. + * When using an array of objects, the data returned from the function must be cast to the + * object array's data type. For example: SomeClass[] items = (SomeClass[]) expand(originalArray). + * + * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} ary + * boolean[], byte[], char[], int[], float[], String[], or an array of objects + * @param {int} newSize positive int: new size for the array + * + * @returns Array (the same datatype as the input) + * + * @see contract + */ + p.expand = function(ary, targetSize) { + var temp = ary.slice(0), + newSize = targetSize || ary.length * 2; + temp.length = newSize; + return temp; + }; + + /** + * Copies an array (or part of an array) to another array. The src array is copied to the + * dst array, beginning at the position specified by srcPos and into the position specified + * by dstPos. The number of elements to copy is determined by length. The simplified version + * with two arguments copies an entire array to another of the same size. It is equivalent + * to "arrayCopy(src, 0, dst, 0, src.length)". This function is far more efficient for copying + * array data than iterating through a for and copying each element. + * + * @param {Array} src an array of any data type: the source array + * @param {Array} dest an array of any data type (as long as it's the same as src): the destination array + * @param {int} srcPos starting position in the source array + * @param {int} destPos starting position in the destination array + * @param {int} length number of array elements to be copied + * + * @returns none + */ + p.arrayCopy = function() { // src, srcPos, dest, destPos, length) { + var src, srcPos = 0, dest, destPos = 0, length; + + if (arguments.length === 2) { + // recall itself and copy src to dest from start index 0 to 0 of src.length + src = arguments[0]; + dest = arguments[1]; + length = src.length; + } else if (arguments.length === 3) { + // recall itself and copy src to dest from start index 0 to 0 of length + src = arguments[0]; + dest = arguments[1]; + length = arguments[2]; + } else if (arguments.length === 5) { + src = arguments[0]; + srcPos = arguments[1]; + dest = arguments[2]; + destPos = arguments[3]; + length = arguments[4]; + } + + // copy src to dest from index srcPos to index destPos of length recursivly on objects + for (var i = srcPos, j = destPos; i < length + srcPos; i++, j++) { + if (dest[j] !== undef) { + dest[j] = src[i]; + } else { + throw "array index out of bounds exception"; + } + } + }; + + /** + * Reverses the order of an array. + * + * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]} array + * boolean[], byte[], char[], int[], float[], or String[] + * + * @returns Array (the same datatype as the input) + * + * @see sort + */ + p.reverse = function(array) { + return array.reverse(); + }; + + + //////////////////////////////////////////////////////////////////////////// + // Color functions + //////////////////////////////////////////////////////////////////////////// + + // helper functions for internal blending modes + p.mix = function(a, b, f) { + return a + (((b - a) * f) >> 8); + }; + + p.peg = function(n) { + return (n < 0) ? 0 : ((n > 255) ? 255 : n); + }; + + // blending modes + /** + * These are internal blending modes used for BlendColor() + * + * @param {Color} c1 First Color to blend + * @param {Color} c2 Second Color to blend + * + * @returns {Color} The blended Color + * + * @see BlendColor + * @see Blend + */ + p.modes = (function() { + var ALPHA_MASK = PConstants.ALPHA_MASK, + RED_MASK = PConstants.RED_MASK, + GREEN_MASK = PConstants.GREEN_MASK, + BLUE_MASK = PConstants.BLUE_MASK, + min = Math.min, + max = Math.max; + + function applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb) { + var a = min(((c1 & 0xff000000) >>> 24) + f, 0xff) << 24; + + var r = (ar + (((cr - ar) * f) >> 8)); + r = ((r < 0) ? 0 : ((r > 255) ? 255 : r)) << 16; + + var g = (ag + (((cg - ag) * f) >> 8)); + g = ((g < 0) ? 0 : ((g > 255) ? 255 : g)) << 8; + + var b = ab + (((cb - ab) * f) >> 8); + b = (b < 0) ? 0 : ((b > 255) ? 255 : b); + + return (a | r | g | b); + } + + return { + replace: function(c1, c2) { + return c2; + }, + blend: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24, + ar = (c1 & RED_MASK), + ag = (c1 & GREEN_MASK), + ab = (c1 & BLUE_MASK), + br = (c2 & RED_MASK), + bg = (c2 & GREEN_MASK), + bb = (c2 & BLUE_MASK); + + return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (ar + (((br - ar) * f) >> 8)) & RED_MASK | + (ag + (((bg - ag) * f) >> 8)) & GREEN_MASK | + (ab + (((bb - ab) * f) >> 8)) & BLUE_MASK); + }, + add: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24; + return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + min(((c1 & RED_MASK) + ((c2 & RED_MASK) >> 8) * f), RED_MASK) & RED_MASK | + min(((c1 & GREEN_MASK) + ((c2 & GREEN_MASK) >> 8) * f), GREEN_MASK) & GREEN_MASK | + min((c1 & BLUE_MASK) + (((c2 & BLUE_MASK) * f) >> 8), BLUE_MASK)); + }, + subtract: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24; + return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + max(((c1 & RED_MASK) - ((c2 & RED_MASK) >> 8) * f), GREEN_MASK) & RED_MASK | + max(((c1 & GREEN_MASK) - ((c2 & GREEN_MASK) >> 8) * f), BLUE_MASK) & GREEN_MASK | + max((c1 & BLUE_MASK) - (((c2 & BLUE_MASK) * f) >> 8), 0)); + }, + lightest: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24; + return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + max(c1 & RED_MASK, ((c2 & RED_MASK) >> 8) * f) & RED_MASK | + max(c1 & GREEN_MASK, ((c2 & GREEN_MASK) >> 8) * f) & GREEN_MASK | + max(c1 & BLUE_MASK, ((c2 & BLUE_MASK) * f) >> 8)); + }, + darkest: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24, + ar = (c1 & RED_MASK), + ag = (c1 & GREEN_MASK), + ab = (c1 & BLUE_MASK), + br = min(c1 & RED_MASK, ((c2 & RED_MASK) >> 8) * f), + bg = min(c1 & GREEN_MASK, ((c2 & GREEN_MASK) >> 8) * f), + bb = min(c1 & BLUE_MASK, ((c2 & BLUE_MASK) * f) >> 8); + + return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (ar + (((br - ar) * f) >> 8)) & RED_MASK | + (ag + (((bg - ag) * f) >> 8)) & GREEN_MASK | + (ab + (((bb - ab) * f) >> 8)) & BLUE_MASK); + }, + difference: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24, + ar = (c1 & RED_MASK) >> 16, + ag = (c1 & GREEN_MASK) >> 8, + ab = (c1 & BLUE_MASK), + br = (c2 & RED_MASK) >> 16, + bg = (c2 & GREEN_MASK) >> 8, + bb = (c2 & BLUE_MASK), + cr = (ar > br) ? (ar - br) : (br - ar), + cg = (ag > bg) ? (ag - bg) : (bg - ag), + cb = (ab > bb) ? (ab - bb) : (bb - ab); + + return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); + }, + exclusion: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24, + ar = (c1 & RED_MASK) >> 16, + ag = (c1 & GREEN_MASK) >> 8, + ab = (c1 & BLUE_MASK), + br = (c2 & RED_MASK) >> 16, + bg = (c2 & GREEN_MASK) >> 8, + bb = (c2 & BLUE_MASK), + cr = ar + br - ((ar * br) >> 7), + cg = ag + bg - ((ag * bg) >> 7), + cb = ab + bb - ((ab * bb) >> 7); + + return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); + }, + multiply: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24, + ar = (c1 & RED_MASK) >> 16, + ag = (c1 & GREEN_MASK) >> 8, + ab = (c1 & BLUE_MASK), + br = (c2 & RED_MASK) >> 16, + bg = (c2 & GREEN_MASK) >> 8, + bb = (c2 & BLUE_MASK), + cr = (ar * br) >> 8, + cg = (ag * bg) >> 8, + cb = (ab * bb) >> 8; + + return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); + }, + screen: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24, + ar = (c1 & RED_MASK) >> 16, + ag = (c1 & GREEN_MASK) >> 8, + ab = (c1 & BLUE_MASK), + br = (c2 & RED_MASK) >> 16, + bg = (c2 & GREEN_MASK) >> 8, + bb = (c2 & BLUE_MASK), + cr = 255 - (((255 - ar) * (255 - br)) >> 8), + cg = 255 - (((255 - ag) * (255 - bg)) >> 8), + cb = 255 - (((255 - ab) * (255 - bb)) >> 8); + + return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); + }, + hard_light: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24, + ar = (c1 & RED_MASK) >> 16, + ag = (c1 & GREEN_MASK) >> 8, + ab = (c1 & BLUE_MASK), + br = (c2 & RED_MASK) >> 16, + bg = (c2 & GREEN_MASK) >> 8, + bb = (c2 & BLUE_MASK), + cr = (br < 128) ? ((ar * br) >> 7) : (255 - (((255 - ar) * (255 - br)) >> 7)), + cg = (bg < 128) ? ((ag * bg) >> 7) : (255 - (((255 - ag) * (255 - bg)) >> 7)), + cb = (bb < 128) ? ((ab * bb) >> 7) : (255 - (((255 - ab) * (255 - bb)) >> 7)); + + return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); + }, + soft_light: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24, + ar = (c1 & RED_MASK) >> 16, + ag = (c1 & GREEN_MASK) >> 8, + ab = (c1 & BLUE_MASK), + br = (c2 & RED_MASK) >> 16, + bg = (c2 & GREEN_MASK) >> 8, + bb = (c2 & BLUE_MASK), + cr = ((ar * br) >> 7) + ((ar * ar) >> 8) - ((ar * ar * br) >> 15), + cg = ((ag * bg) >> 7) + ((ag * ag) >> 8) - ((ag * ag * bg) >> 15), + cb = ((ab * bb) >> 7) + ((ab * ab) >> 8) - ((ab * ab * bb) >> 15); + + return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); + }, + overlay: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24, + ar = (c1 & RED_MASK) >> 16, + ag = (c1 & GREEN_MASK) >> 8, + ab = (c1 & BLUE_MASK), + br = (c2 & RED_MASK) >> 16, + bg = (c2 & GREEN_MASK) >> 8, + bb = (c2 & BLUE_MASK), + cr = (ar < 128) ? ((ar * br) >> 7) : (255 - (((255 - ar) * (255 - br)) >> 7)), + cg = (ag < 128) ? ((ag * bg) >> 7) : (255 - (((255 - ag) * (255 - bg)) >> 7)), + cb = (ab < 128) ? ((ab * bb) >> 7) : (255 - (((255 - ab) * (255 - bb)) >> 7)); + + return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); + }, + dodge: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24, + ar = (c1 & RED_MASK) >> 16, + ag = (c1 & GREEN_MASK) >> 8, + ab = (c1 & BLUE_MASK), + br = (c2 & RED_MASK) >> 16, + bg = (c2 & GREEN_MASK) >> 8, + bb = (c2 & BLUE_MASK); + + var cr = 255; + if (br !== 255) { + cr = (ar << 8) / (255 - br); + cr = (cr < 0) ? 0 : ((cr > 255) ? 255 : cr); + } + + var cg = 255; + if (bg !== 255) { + cg = (ag << 8) / (255 - bg); + cg = (cg < 0) ? 0 : ((cg > 255) ? 255 : cg); + } + + var cb = 255; + if (bb !== 255) { + cb = (ab << 8) / (255 - bb); + cb = (cb < 0) ? 0 : ((cb > 255) ? 255 : cb); + } + + return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); + }, + burn: function(c1, c2) { + var f = (c2 & ALPHA_MASK) >>> 24, + ar = (c1 & RED_MASK) >> 16, + ag = (c1 & GREEN_MASK) >> 8, + ab = (c1 & BLUE_MASK), + br = (c2 & RED_MASK) >> 16, + bg = (c2 & GREEN_MASK) >> 8, + bb = (c2 & BLUE_MASK); + + var cr = 0; + if (br !== 0) { + cr = ((255 - ar) << 8) / br; + cr = 255 - ((cr < 0) ? 0 : ((cr > 255) ? 255 : cr)); + } + + var cg = 0; + if (bg !== 0) { + cg = ((255 - ag) << 8) / bg; + cg = 255 - ((cg < 0) ? 0 : ((cg > 255) ? 255 : cg)); + } + + var cb = 0; + if (bb !== 0) { + cb = ((255 - ab) << 8) / bb; + cb = 255 - ((cb < 0) ? 0 : ((cb > 255) ? 255 : cb)); + } + + return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); + } + }; + }()); + + function color$4(aValue1, aValue2, aValue3, aValue4) { + var r, g, b, a; + + if (curColorMode === PConstants.HSB) { + var rgb = p.color.toRGB(aValue1, aValue2, aValue3); + r = rgb[0]; + g = rgb[1]; + b = rgb[2]; + } else { + r = Math.round(255 * (aValue1 / colorModeX)); + g = Math.round(255 * (aValue2 / colorModeY)); + b = Math.round(255 * (aValue3 / colorModeZ)); + } + + a = Math.round(255 * (aValue4 / colorModeA)); + + // Limit values less than 0 and greater than 255 + r = (r < 0) ? 0 : r; + g = (g < 0) ? 0 : g; + b = (b < 0) ? 0 : b; + a = (a < 0) ? 0 : a; + r = (r > 255) ? 255 : r; + g = (g > 255) ? 255 : g; + b = (b > 255) ? 255 : b; + a = (a > 255) ? 255 : a; + + // Create color int + return (a << 24) & PConstants.ALPHA_MASK | (r << 16) & PConstants.RED_MASK | (g << 8) & PConstants.GREEN_MASK | b & PConstants.BLUE_MASK; + } + + function color$2(aValue1, aValue2) { + var a; + + // Color int and alpha + if (aValue1 & PConstants.ALPHA_MASK) { + a = Math.round(255 * (aValue2 / colorModeA)); + // Limit values less than 0 and greater than 255 + a = (a > 255) ? 255 : a; + a = (a < 0) ? 0 : a; + + return aValue1 - (aValue1 & PConstants.ALPHA_MASK) + ((a << 24) & PConstants.ALPHA_MASK); + } + // Grayscale and alpha + if (curColorMode === PConstants.RGB) { + return color$4(aValue1, aValue1, aValue1, aValue2); + } + if (curColorMode === PConstants.HSB) { + return color$4(0, 0, (aValue1 / colorModeX) * colorModeZ, aValue2); + } + } + + function color$1(aValue1) { + // Grayscale + if (aValue1 <= colorModeX && aValue1 >= 0) { + if (curColorMode === PConstants.RGB) { + return color$4(aValue1, aValue1, aValue1, colorModeA); + } + if (curColorMode === PConstants.HSB) { + return color$4(0, 0, (aValue1 / colorModeX) * colorModeZ, colorModeA); + } + } + // Color int + if (aValue1) { + if (aValue1 > 2147483647) { + // Java Overflow + aValue1 -= 4294967296; + } + return aValue1; + } + } + + /** + * Creates colors for storing in variables of the color datatype. The parameters are + * interpreted as RGB or HSB values depending on the current colorMode(). The default + * mode is RGB values from 0 to 255 and therefore, the function call color(255, 204, 0) + * will return a bright yellow color. More about how colors are stored can be found in + * the reference for the color datatype. + * + * @param {int|float} aValue1 red or hue or grey values relative to the current color range. + * Also can be color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) + * @param {int|float} aValue2 green or saturation values relative to the current color range + * @param {int|float} aValue3 blue or brightness values relative to the current color range + * @param {int|float} aValue4 relative to current color range. Represents alpha + * + * @returns {color} the color + * + * @see colorMode + */ + p.color = function(aValue1, aValue2, aValue3, aValue4) { + + // 4 arguments: (R, G, B, A) or (H, S, B, A) + if (aValue1 !== undef && aValue2 !== undef && aValue3 !== undef && aValue4 !== undef) { + return color$4(aValue1, aValue2, aValue3, aValue4); + } + + // 3 arguments: (R, G, B) or (H, S, B) + if (aValue1 !== undef && aValue2 !== undef && aValue3 !== undef) { + return color$4(aValue1, aValue2, aValue3, colorModeA); + } + + // 2 arguments: (Color, A) or (Grayscale, A) + if (aValue1 !== undef && aValue2 !== undef) { + return color$2(aValue1, aValue2); + } + + // 1 argument: (Grayscale) or (Color) + if (typeof aValue1 === "number") { + return color$1(aValue1); + } + + // Default + return color$4(colorModeX, colorModeY, colorModeZ, colorModeA); + }; + + // Ease of use function to extract the colour bits into a string + p.color.toString = function(colorInt) { + return "rgba(" + ((colorInt & PConstants.RED_MASK) >>> 16) + "," + ((colorInt & PConstants.GREEN_MASK) >>> 8) + + "," + ((colorInt & PConstants.BLUE_MASK)) + "," + ((colorInt & PConstants.ALPHA_MASK) >>> 24) / 255 + ")"; + }; + + // Easy of use function to pack rgba values into a single bit-shifted color int. + p.color.toInt = function(r, g, b, a) { + return (a << 24) & PConstants.ALPHA_MASK | (r << 16) & PConstants.RED_MASK | (g << 8) & PConstants.GREEN_MASK | b & PConstants.BLUE_MASK; + }; + + // Creates a simple array in [R, G, B, A] format, [255, 255, 255, 255] + p.color.toArray = function(colorInt) { + return [(colorInt & PConstants.RED_MASK) >>> 16, (colorInt & PConstants.GREEN_MASK) >>> 8, + colorInt & PConstants.BLUE_MASK, (colorInt & PConstants.ALPHA_MASK) >>> 24]; + }; + + // Creates a WebGL color array in [R, G, B, A] format. WebGL wants the color ranges between 0 and 1, [1, 1, 1, 1] + p.color.toGLArray = function(colorInt) { + return [((colorInt & PConstants.RED_MASK) >>> 16) / 255, ((colorInt & PConstants.GREEN_MASK) >>> 8) / 255, + (colorInt & PConstants.BLUE_MASK) / 255, ((colorInt & PConstants.ALPHA_MASK) >>> 24) / 255]; + }; + + // HSB conversion function from Mootools, MIT Licensed + p.color.toRGB = function(h, s, b) { + // Limit values greater than range + h = (h > colorModeX) ? colorModeX : h; + s = (s > colorModeY) ? colorModeY : s; + b = (b > colorModeZ) ? colorModeZ : b; + + h = (h / colorModeX) * 360; + s = (s / colorModeY) * 100; + b = (b / colorModeZ) * 100; + + var br = Math.round(b / 100 * 255); + + if (s === 0) { // Grayscale + return [br, br, br]; + } + var hue = h % 360; + var f = hue % 60; + var p = Math.round((b * (100 - s)) / 10000 * 255); + var q = Math.round((b * (6000 - s * f)) / 600000 * 255); + var t = Math.round((b * (6000 - s * (60 - f))) / 600000 * 255); + switch (Math.floor(hue / 60)) { + case 0: + return [br, t, p]; + case 1: + return [q, br, p]; + case 2: + return [p, br, t]; + case 3: + return [p, q, br]; + case 4: + return [t, p, br]; + case 5: + return [br, p, q]; + } + }; + + function colorToHSB(colorInt) { + var red, green, blue; + + red = ((colorInt & PConstants.RED_MASK) >>> 16) / 255; + green = ((colorInt & PConstants.GREEN_MASK) >>> 8) / 255; + blue = (colorInt & PConstants.BLUE_MASK) / 255; + + var max = p.max(p.max(red,green), blue), + min = p.min(p.min(red,green), blue), + hue, saturation; + + if (min === max) { + return [0, 0, max*colorModeZ]; + } + saturation = (max - min) / max; + + if (red === max) { + hue = (green - blue) / (max - min); + } else if (green === max) { + hue = 2 + ((blue - red) / (max - min)); + } else { + hue = 4 + ((red - green) / (max - min)); + } + + hue /= 6; + + if (hue < 0) { + hue += 1; + } else if (hue > 1) { + hue -= 1; + } + return [hue*colorModeX, saturation*colorModeY, max*colorModeZ]; + } + + /** + * Extracts the brightness value from a color. + * + * @param {color} colInt any value of the color datatype + * + * @returns {float} The brightness color value. + * + * @see red + * @see green + * @see blue + * @see hue + * @see saturation + */ + p.brightness = function(colInt){ + return colorToHSB(colInt)[2]; + }; + + /** + * Extracts the saturation value from a color. + * + * @param {color} colInt any value of the color datatype + * + * @returns {float} The saturation color value. + * + * @see red + * @see green + * @see blue + * @see hue + * @see brightness + */ + p.saturation = function(colInt){ + return colorToHSB(colInt)[1]; + }; + + /** + * Extracts the hue value from a color. + * + * @param {color} colInt any value of the color datatype + * + * @returns {float} The hue color value. + * + * @see red + * @see green + * @see blue + * @see saturation + * @see brightness + */ + p.hue = function(colInt){ + return colorToHSB(colInt)[0]; + }; + + /** + * Extracts the red value from a color, scaled to match current colorMode(). + * This value is always returned as a float so be careful not to assign it to an int value. + * + * @param {color} aColor any value of the color datatype + * + * @returns {float} The red color value. + * + * @see green + * @see blue + * @see alpha + * @see >> right shift + * @see hue + * @see saturation + * @see brightness + */ + p.red = function(aColor) { + return ((aColor & PConstants.RED_MASK) >>> 16) / 255 * colorModeX; + }; + + /** + * Extracts the green value from a color, scaled to match current colorMode(). + * This value is always returned as a float so be careful not to assign it to an int value. + * + * @param {color} aColor any value of the color datatype + * + * @returns {float} The green color value. + * + * @see red + * @see blue + * @see alpha + * @see >> right shift + * @see hue + * @see saturation + * @see brightness + */ + p.green = function(aColor) { + return ((aColor & PConstants.GREEN_MASK) >>> 8) / 255 * colorModeY; + }; + + /** + * Extracts the blue value from a color, scaled to match current colorMode(). + * This value is always returned as a float so be careful not to assign it to an int value. + * + * @param {color} aColor any value of the color datatype + * + * @returns {float} The blue color value. + * + * @see red + * @see green + * @see alpha + * @see >> right shift + * @see hue + * @see saturation + * @see brightness + */ + p.blue = function(aColor) { + return (aColor & PConstants.BLUE_MASK) / 255 * colorModeZ; + }; + + /** + * Extracts the alpha value from a color, scaled to match current colorMode(). + * This value is always returned as a float so be careful not to assign it to an int value. + * + * @param {color} aColor any value of the color datatype + * + * @returns {float} The alpha color value. + * + * @see red + * @see green + * @see blue + * @see >> right shift + * @see hue + * @see saturation + * @see brightness + */ + p.alpha = function(aColor) { + return ((aColor & PConstants.ALPHA_MASK) >>> 24) / 255 * colorModeA; + }; + + /** + * Calculates a color or colors between two colors at a specific increment. + * The amt parameter is the amount to interpolate between the two values where 0.0 + * equal to the first point, 0.1 is very near the first point, 0.5 is half-way in between, etc. + * + * @param {color} c1 interpolate from this color + * @param {color} c2 interpolate to this color + * @param {float} amt between 0.0 and 1.0 + * + * @returns {float} The blended color. + * + * @see blendColor + * @see color + */ + p.lerpColor = function(c1, c2, amt) { + var r, g, b, a, r1, g1, b1, a1, r2, g2, b2, a2; + var hsb1, hsb2, rgb, h, s; + var colorBits1 = p.color(c1); + var colorBits2 = p.color(c2); + + if (curColorMode === PConstants.HSB) { + // Special processing for HSB mode. + // Get HSB and Alpha values for Color 1 and 2 + hsb1 = colorToHSB(colorBits1); + a1 = ((colorBits1 & PConstants.ALPHA_MASK) >>> 24) / colorModeA; + hsb2 = colorToHSB(colorBits2); + a2 = ((colorBits2 & PConstants.ALPHA_MASK) >>> 24) / colorModeA; + + // Return lerp value for each channel, for HSB components + h = p.lerp(hsb1[0], hsb2[0], amt); + s = p.lerp(hsb1[1], hsb2[1], amt); + b = p.lerp(hsb1[2], hsb2[2], amt); + rgb = p.color.toRGB(h, s, b); + // ... and for Alpha-range + a = (p.lerp(a1, a2, amt) * colorModeA + 0.5) | 0; + + return (a << 24) & PConstants.ALPHA_MASK | + (rgb[0] << 16) & PConstants.RED_MASK | + (rgb[1] << 8) & PConstants.GREEN_MASK | + rgb[2] & PConstants.BLUE_MASK; + } + + // Get RGBA values for Color 1 to floats + r1 = (colorBits1 & PConstants.RED_MASK) >>> 16; + g1 = (colorBits1 & PConstants.GREEN_MASK) >>> 8; + b1 = (colorBits1 & PConstants.BLUE_MASK); + a1 = ((colorBits1 & PConstants.ALPHA_MASK) >>> 24) / colorModeA; + + // Get RGBA values for Color 2 to floats + r2 = (colorBits2 & PConstants.RED_MASK) >>> 16; + g2 = (colorBits2 & PConstants.GREEN_MASK) >>> 8; + b2 = (colorBits2 & PConstants.BLUE_MASK); + a2 = ((colorBits2 & PConstants.ALPHA_MASK) >>> 24) / colorModeA; + + // Return lerp value for each channel, INT for color, Float for Alpha-range + r = (p.lerp(r1, r2, amt) + 0.5) | 0; + g = (p.lerp(g1, g2, amt) + 0.5) | 0; + b = (p.lerp(b1, b2, amt) + 0.5) | 0; + a = (p.lerp(a1, a2, amt) * colorModeA + 0.5) | 0; + + return (a << 24) & PConstants.ALPHA_MASK | + (r << 16) & PConstants.RED_MASK | + (g << 8) & PConstants.GREEN_MASK | + b & PConstants.BLUE_MASK; + }; + + /** + * Changes the way Processing interprets color data. By default, fill(), stroke(), and background() + * colors are set by values between 0 and 255 using the RGB color model. It is possible to change the + * numerical range used for specifying colors and to switch color systems. For example, calling colorMode(RGB, 1.0) + * will specify that values are specified between 0 and 1. The limits for defining colors are altered by setting the + * parameters range1, range2, range3, and range 4. + * + * @param {MODE} mode Either RGB or HSB, corresponding to Red/Green/Blue and Hue/Saturation/Brightness + * @param {int|float} range range for all color elements + * @param {int|float} range1 range for the red or hue depending on the current color mode + * @param {int|float} range2 range for the green or saturation depending on the current color mode + * @param {int|float} range3 range for the blue or brightness depending on the current color mode + * @param {int|float} range4 range for the alpha + * + * @returns none + * + * @see background + * @see fill + * @see stroke + */ + p.colorMode = function() { // mode, range1, range2, range3, range4 + curColorMode = arguments[0]; + if (arguments.length > 1) { + colorModeX = arguments[1]; + colorModeY = arguments[2] || arguments[1]; + colorModeZ = arguments[3] || arguments[1]; + colorModeA = arguments[4] || arguments[1]; + } + }; + + /** + * Blends two color values together based on the blending mode given as the MODE parameter. + * The possible modes are described in the reference for the blend() function. + * + * @param {color} c1 color: the first color to blend + * @param {color} c2 color: the second color to blend + * @param {MODE} MODE Either BLEND, ADD, SUBTRACT, DARKEST, LIGHTEST, DIFFERENCE, EXCLUSION, MULTIPLY, + * SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, or BURN + * + * @returns {float} The blended color. + * + * @see blend + * @see color + */ + p.blendColor = function(c1, c2, mode) { + if (mode === PConstants.REPLACE) { + return p.modes.replace(c1, c2); + } else if (mode === PConstants.BLEND) { + return p.modes.blend(c1, c2); + } else if (mode === PConstants.ADD) { + return p.modes.add(c1, c2); + } else if (mode === PConstants.SUBTRACT) { + return p.modes.subtract(c1, c2); + } else if (mode === PConstants.LIGHTEST) { + return p.modes.lightest(c1, c2); + } else if (mode === PConstants.DARKEST) { + return p.modes.darkest(c1, c2); + } else if (mode === PConstants.DIFFERENCE) { + return p.modes.difference(c1, c2); + } else if (mode === PConstants.EXCLUSION) { + return p.modes.exclusion(c1, c2); + } else if (mode === PConstants.MULTIPLY) { + return p.modes.multiply(c1, c2); + } else if (mode === PConstants.SCREEN) { + return p.modes.screen(c1, c2); + } else if (mode === PConstants.HARD_LIGHT) { + return p.modes.hard_light(c1, c2); + } else if (mode === PConstants.SOFT_LIGHT) { + return p.modes.soft_light(c1, c2); + } else if (mode === PConstants.OVERLAY) { + return p.modes.overlay(c1, c2); + } else if (mode === PConstants.DODGE) { + return p.modes.dodge(c1, c2); + } else if (mode === PConstants.BURN) { + return p.modes.burn(c1, c2); + } + }; + + //////////////////////////////////////////////////////////////////////////// + // Canvas-Matrix manipulation + //////////////////////////////////////////////////////////////////////////// + + function saveContext() { + curContext.save(); + } + + function restoreContext() { + curContext.restore(); + isStrokeDirty = true; + isFillDirty = true; + } + + /** + * Prints the current matrix to the text window. + * + * @returns none + * + * @see pushMatrix + * @see popMatrix + * @see resetMatrix + * @see applyMatrix + */ + p.printMatrix = function() { + modelView.print(); + }; + + /** + * Specifies an amount to displace objects within the display window. The x parameter specifies left/right translation, + * the y parameter specifies up/down translation, and the z parameter specifies translations toward/away from the screen. + * Using this function with the z parameter requires using the P3D or OPENGL parameter in combination with size as shown + * in the above example. Transformations apply to everything that happens after and subsequent calls to the function + * accumulates the effect. For example, calling translate(50, 0) and then translate(20, 0) is the same as translate(70, 0). + * If translate() is called within draw(), the transformation is reset when the loop begins again. + * This function can be further controlled by the pushMatrix() and popMatrix(). + * + * @param {int|float} x left/right translation + * @param {int|float} y up/down translation + * @param {int|float} z forward/back translation + * + * @returns none + * + * @see pushMatrix + * @see popMatrix + * @see scale + * @see rotate + * @see rotateX + * @see rotateY + * @see rotateZ + */ + Drawing2D.prototype.translate = function(x, y) { + modelView.translate(x, y); + modelViewInv.invTranslate(x, y); + curContext.translate(x, y); + }; + + Drawing3D.prototype.translate = function(x, y, z) { + modelView.translate(x, y, z); + modelViewInv.invTranslate(x, y, z); + }; + + /** + * Increases or decreases the size of a shape by expanding and contracting vertices. Objects always scale from their + * relative origin to the coordinate system. Scale values are specified as decimal percentages. For example, the + * function call scale(2.0) increases the dimension of a shape by 200%. Transformations apply to everything that + * happens after and subsequent calls to the function multiply the effect. For example, calling scale(2.0) and + * then scale(1.5) is the same as scale(3.0). If scale() is called within draw(), the transformation is reset when + * the loop begins again. Using this fuction with the z parameter requires passing P3D or OPENGL into the size() + * parameter as shown in the example above. This function can be further controlled by pushMatrix() and popMatrix(). + * + * @param {int|float} size percentage to scale the object + * @param {int|float} x percentage to scale the object in the x-axis + * @param {int|float} y percentage to scale the object in the y-axis + * @param {int|float} z percentage to scale the object in the z-axis + * + * @returns none + * + * @see pushMatrix + * @see popMatrix + * @see translate + * @see rotate + * @see rotateX + * @see rotateY + * @see rotateZ + */ + Drawing2D.prototype.scale = function(x, y) { + modelView.scale(x, y); + modelViewInv.invScale(x, y); + curContext.scale(x, y || x); + }; + + Drawing3D.prototype.scale = function(x, y, z) { + modelView.scale(x, y, z); + modelViewInv.invScale(x, y, z); + }; + + + /** + * helper function for applying a transfrom matrix to a 2D context. + */ + Drawing2D.prototype.transform = function(pmatrix) { + var e = pmatrix.array(); + curContext.transform(e[0],e[3],e[1],e[4],e[2],e[5]); + }; + + /** + * helper function for applying a transfrom matrix to a 3D context. + * not currently implemented. + */ + Drawing3D.prototype.transformm = function(pmatrix3d) { + throw("p.transform is currently not supported in 3D mode"); + }; + + + /** + * Pushes the current transformation matrix onto the matrix stack. Understanding pushMatrix() and popMatrix() + * requires understanding the concept of a matrix stack. The pushMatrix() function saves the current coordinate + * system to the stack and popMatrix() restores the prior coordinate system. pushMatrix() and popMatrix() are + * used in conjuction with the other transformation methods and may be embedded to control the scope of + * the transformations. + * + * @returns none + * + * @see popMatrix + * @see translate + * @see rotate + * @see rotateX + * @see rotateY + * @see rotateZ + */ + Drawing2D.prototype.pushMatrix = function() { + userMatrixStack.load(modelView); + userReverseMatrixStack.load(modelViewInv); + saveContext(); + }; + + Drawing3D.prototype.pushMatrix = function() { + userMatrixStack.load(modelView); + userReverseMatrixStack.load(modelViewInv); + }; + + /** + * Pops the current transformation matrix off the matrix stack. Understanding pushing and popping requires + * understanding the concept of a matrix stack. The pushMatrix() function saves the current coordinate system to + * the stack and popMatrix() restores the prior coordinate system. pushMatrix() and popMatrix() are used in + * conjuction with the other transformation methods and may be embedded to control the scope of the transformations. + * + * @returns none + * + * @see popMatrix + * @see pushMatrix + */ + Drawing2D.prototype.popMatrix = function() { + modelView.set(userMatrixStack.pop()); + modelViewInv.set(userReverseMatrixStack.pop()); + restoreContext(); + }; + + Drawing3D.prototype.popMatrix = function() { + modelView.set(userMatrixStack.pop()); + modelViewInv.set(userReverseMatrixStack.pop()); + }; + + /** + * Replaces the current matrix with the identity matrix. The equivalent function in OpenGL is glLoadIdentity(). + * + * @returns none + * + * @see popMatrix + * @see pushMatrix + * @see applyMatrix + * @see printMatrix + */ + Drawing2D.prototype.resetMatrix = function() { + modelView.reset(); + modelViewInv.reset(); + curContext.setTransform(1,0,0,1,0,0); + }; + + Drawing3D.prototype.resetMatrix = function() { + modelView.reset(); + modelViewInv.reset(); + }; + + /** + * Multiplies the current matrix by the one specified through the parameters. This is very slow because it will + * try to calculate the inverse of the transform, so avoid it whenever possible. The equivalent function + * in OpenGL is glMultMatrix(). + * + * @param {int|float} n00-n15 numbers which define the 4x4 matrix to be multiplied + * + * @returns none + * + * @see popMatrix + * @see pushMatrix + * @see resetMatrix + * @see printMatrix + */ + DrawingShared.prototype.applyMatrix = function() { + var a = arguments; + modelView.apply(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); + modelViewInv.invApply(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); + }; + + Drawing2D.prototype.applyMatrix = function() { + var a = arguments; + for (var cnt = a.length; cnt < 16; cnt++) { + a[cnt] = 0; + } + a[10] = a[15] = 1; + DrawingShared.prototype.applyMatrix.apply(this, a); + }; + + /** + * Rotates a shape around the x-axis the amount specified by the angle parameter. Angles should be + * specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. + * Objects are always rotated around their relative position to the origin and positive numbers + * rotate objects in a counterclockwise direction. Transformations apply to everything that happens + * after and subsequent calls to the function accumulates the effect. For example, calling rotateX(PI/2) + * and then rotateX(PI/2) is the same as rotateX(PI). If rotateX() is called within the draw(), the + * transformation is reset when the loop begins again. This function requires passing P3D or OPENGL + * into the size() parameter as shown in the example above. + * + * @param {int|float} angleInRadians angle of rotation specified in radians + * + * @returns none + * + * @see rotateY + * @see rotateZ + * @see rotate + * @see translate + * @see scale + * @see popMatrix + * @see pushMatrix + */ + p.rotateX = function(angleInRadians) { + modelView.rotateX(angleInRadians); + modelViewInv.invRotateX(angleInRadians); + }; + + /** + * Rotates a shape around the z-axis the amount specified by the angle parameter. Angles should be + * specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. + * Objects are always rotated around their relative position to the origin and positive numbers + * rotate objects in a counterclockwise direction. Transformations apply to everything that happens + * after and subsequent calls to the function accumulates the effect. For example, calling rotateZ(PI/2) + * and then rotateZ(PI/2) is the same as rotateZ(PI). If rotateZ() is called within the draw(), the + * transformation is reset when the loop begins again. This function requires passing P3D or OPENGL + * into the size() parameter as shown in the example above. + * + * @param {int|float} angleInRadians angle of rotation specified in radians + * + * @returns none + * + * @see rotateX + * @see rotateY + * @see rotate + * @see translate + * @see scale + * @see popMatrix + * @see pushMatrix + */ + Drawing2D.prototype.rotateZ = function() { + throw "rotateZ() is not supported in 2D mode. Use rotate(float) instead."; + }; + + Drawing3D.prototype.rotateZ = function(angleInRadians) { + modelView.rotateZ(angleInRadians); + modelViewInv.invRotateZ(angleInRadians); + }; + + /** + * Rotates a shape around the y-axis the amount specified by the angle parameter. Angles should be + * specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. + * Objects are always rotated around their relative position to the origin and positive numbers + * rotate objects in a counterclockwise direction. Transformations apply to everything that happens + * after and subsequent calls to the function accumulates the effect. For example, calling rotateY(PI/2) + * and then rotateY(PI/2) is the same as rotateY(PI). If rotateY() is called within the draw(), the + * transformation is reset when the loop begins again. This function requires passing P3D or OPENGL + * into the size() parameter as shown in the example above. + * + * @param {int|float} angleInRadians angle of rotation specified in radians + * + * @returns none + * + * @see rotateX + * @see rotateZ + * @see rotate + * @see translate + * @see scale + * @see popMatrix + * @see pushMatrix + */ + p.rotateY = function(angleInRadians) { + modelView.rotateY(angleInRadians); + modelViewInv.invRotateY(angleInRadians); + }; + + /** + * Rotates a shape the amount specified by the angle parameter. Angles should be specified in radians + * (values from 0 to TWO_PI) or converted to radians with the radians() function. Objects are always + * rotated around their relative position to the origin and positive numbers rotate objects in a + * clockwise direction. Transformations apply to everything that happens after and subsequent calls + * to the function accumulates the effect. For example, calling rotate(HALF_PI) and then rotate(HALF_PI) + * is the same as rotate(PI). All tranformations are reset when draw() begins again. Technically, + * rotate() multiplies the current transformation matrix by a rotation matrix. This function can be + * further controlled by the pushMatrix() and popMatrix(). + * + * @param {int|float} angleInRadians angle of rotation specified in radians + * + * @returns none + * + * @see rotateX + * @see rotateY + * @see rotateZ + * @see rotate + * @see translate + * @see scale + * @see popMatrix + * @see pushMatrix + */ + Drawing2D.prototype.rotate = function(angleInRadians) { + modelView.rotateZ(angleInRadians); + modelViewInv.invRotateZ(angleInRadians); + curContext.rotate(angleInRadians); + }; + + Drawing3D.prototype.rotate = function(angleInRadians) { + if (arguments.length < 4) { + p.rotateZ(angleInRadians); + } else { + modelView.rotate(angleInRadians, arguments[1], arguments[2], arguments[3]); + modelViewInv.rotate((-angleInRadians), arguments[1], arguments[2], arguments[3]); + } + }; + + /** + * Shears a shape around the x-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to radians + * with the radians() function. Objects are always sheared around their relative position + * to the origin and positive numbers shear objects in a clockwise direction. Transformations + * apply to everything that happens after and subsequent calls to the function accumulates the + * effect. For example, calling shearX(PI/2) and then shearX(PI/2) is the same as shearX(PI) + * + * @param {int|float} angleInRadians angle of rotation specified in radians + * + * @returns none + * + * @see rotateX + * @see rotateY + * @see rotateZ + * @see rotate + * @see translate + * @see scale + * @see popMatrix + * @see pushMatrix + */ + + Drawing2D.prototype.shearX = function(angleInRadians) { + modelView.shearX(angleInRadians); + curContext.transform(1,0,angleInRadians,1,0,0); + }; + + Drawing3D.prototype.shearX = function(angleInRadians) { + modelView.shearX(angleInRadians); + }; + + /** + * Shears a shape around the y-axis the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to PI*2) or converted to + * radians with the radians() function. Objects are always sheared around their + * relative position to the origin and positive numbers shear objects in a + * clockwise direction. Transformations apply to everything that happens after + * and subsequent calls to the function accumulates the effect. For example, + * calling shearY(PI/2) and then shearY(PI/2) is the same as shearY(PI). + * + * @param {int|float} angleInRadians angle of rotation specified in radians + * + * @returns none + * + * @see rotateX + * @see rotateY + * @see rotateZ + * @see rotate + * @see translate + * @see scale + * @see popMatrix + * @see pushMatrix + * @see shearX + */ + + Drawing2D.prototype.shearY = function(angleInRadians) { + modelView.shearY(angleInRadians); + curContext.transform(1,angleInRadians,0,1,0,0); + }; + + Drawing3D.prototype.shearY = function(angleInRadians) { + modelView.shearY(angleInRadians); + }; + + /** + * The pushStyle() function saves the current style settings and popStyle() restores the prior settings. + * Note that these functions are always used together. They allow you to change the style settings and later + * return to what you had. When a new style is started with pushStyle(), it builds on the current style information. + * The pushStyle() and popStyle() functions can be embedded to provide more control (see the second example + * above for a demonstration.) + * The style information controlled by the following functions are included in the style: fill(), stroke(), tint(), + * strokeWeight(), strokeCap(), strokeJoin(), imageMode(), rectMode(), ellipseMode(), shapeMode(), colorMode(), + * textAlign(), textFont(), textMode(), textSize(), textLeading(), emissive(), specular(), shininess(), ambient() + * + * @returns none + * + * @see popStyle + */ + p.pushStyle = function() { + // Save the canvas state. + saveContext(); + + p.pushMatrix(); + + var newState = { + 'doFill': doFill, + 'currentFillColor': currentFillColor, + 'doStroke': doStroke, + 'currentStrokeColor': currentStrokeColor, + 'curTint': curTint, + 'curRectMode': curRectMode, + 'curColorMode': curColorMode, + 'colorModeX': colorModeX, + 'colorModeZ': colorModeZ, + 'colorModeY': colorModeY, + 'colorModeA': colorModeA, + 'curTextFont': curTextFont, + 'horizontalTextAlignment': horizontalTextAlignment, + 'verticalTextAlignment': verticalTextAlignment, + 'textMode': textMode, + 'curFontName': curFontName, + 'curTextSize': curTextSize, + 'curTextAscent': curTextAscent, + 'curTextDescent': curTextDescent, + 'curTextLeading': curTextLeading + }; + + styleArray.push(newState); + }; + + /** + * The pushStyle() function saves the current style settings and popStyle() restores the prior settings; these + * functions are always used together. They allow you to change the style settings and later return to what you had. + * When a new style is started with pushStyle(), it builds on the current style information. The pushStyle() and + * popStyle() functions can be embedded to provide more control (see the second example above for a demonstration.) + * + * @returns none + * + * @see pushStyle + */ + p.popStyle = function() { + var oldState = styleArray.pop(); + + if (oldState) { + restoreContext(); + + p.popMatrix(); + + doFill = oldState.doFill; + currentFillColor = oldState.currentFillColor; + doStroke = oldState.doStroke; + currentStrokeColor = oldState.currentStrokeColor; + curTint = oldState.curTint; + curRectMode = oldState.curRectMode; + curColorMode = oldState.curColorMode; + colorModeX = oldState.colorModeX; + colorModeZ = oldState.colorModeZ; + colorModeY = oldState.colorModeY; + colorModeA = oldState.colorModeA; + curTextFont = oldState.curTextFont; + curFontName = oldState.curFontName; + curTextSize = oldState.curTextSize; + horizontalTextAlignment = oldState.horizontalTextAlignment; + verticalTextAlignment = oldState.verticalTextAlignment; + textMode = oldState.textMode; + curTextAscent = oldState.curTextAscent; + curTextDescent = oldState.curTextDescent; + curTextLeading = oldState.curTextLeading; + } else { + throw "Too many popStyle() without enough pushStyle()"; + } + }; + + //////////////////////////////////////////////////////////////////////////// + // Time based functions + //////////////////////////////////////////////////////////////////////////// + + /** + * Processing communicates with the clock on your computer. + * The year() function returns the current year as an integer (2003, 2004, 2005, etc). + * + * @returns {float} The current year. + * + * @see millis + * @see second + * @see minute + * @see hour + * @see day + * @see month + */ + p.year = function() { + return new Date().getFullYear(); + }; + /** + * Processing communicates with the clock on your computer. + * The month() function returns the current month as a value from 1 - 12. + * + * @returns {float} The current month. + * + * @see millis + * @see second + * @see minute + * @see hour + * @see day + * @see year + */ + p.month = function() { + return new Date().getMonth() + 1; + }; + /** + * Processing communicates with the clock on your computer. + * The day() function returns the current day as a value from 1 - 31. + * + * @returns {float} The current day. + * + * @see millis + * @see second + * @see minute + * @see hour + * @see month + * @see year + */ + p.day = function() { + return new Date().getDate(); + }; + /** + * Processing communicates with the clock on your computer. + * The hour() function returns the current hour as a value from 0 - 23. + * + * @returns {float} The current hour. + * + * @see millis + * @see second + * @see minute + * @see month + * @see day + * @see year + */ + p.hour = function() { + return new Date().getHours(); + }; + /** + * Processing communicates with the clock on your computer. + * The minute() function returns the current minute as a value from 0 - 59. + * + * @returns {float} The current minute. + * + * @see millis + * @see second + * @see month + * @see hour + * @see day + * @see year + */ + p.minute = function() { + return new Date().getMinutes(); + }; + /** + * Processing communicates with the clock on your computer. + * The second() function returns the current second as a value from 0 - 59. + * + * @returns {float} The current minute. + * + * @see millis + * @see month + * @see minute + * @see hour + * @see day + * @see year + */ + p.second = function() { + return new Date().getSeconds(); + }; + /** + * Returns the number of milliseconds (thousandths of a second) since starting a sketch. + * This information is often used for timing animation sequences. + * + * @returns {long} The number of milliseconds since starting the sketch. + * + * @see month + * @see second + * @see minute + * @see hour + * @see day + * @see year + */ + p.millis = function() { + return Date.now() - start; + }; + + /** + * Executes the code within draw() one time. This functions allows the program to update + * the display window only when necessary, for example when an event registered by + * mousePressed() or keyPressed() occurs. + * In structuring a program, it only makes sense to call redraw() within events such as + * mousePressed(). This is because redraw() does not run draw() immediately (it only sets + * a flag that indicates an update is needed). + * Calling redraw() within draw() has no effect because draw() is continuously called anyway. + * + * @returns none + * + * @see noLoop + * @see loop + */ + function redrawHelper() { + var sec = (Date.now() - timeSinceLastFPS) / 1000; + framesSinceLastFPS++; + var fps = framesSinceLastFPS / sec; + + // recalculate FPS every half second for better accuracy. + if (sec > 0.5) { + timeSinceLastFPS = Date.now(); + framesSinceLastFPS = 0; + p.__frameRate = fps; + } + + p.frameCount++; + } + + Drawing2D.prototype.redraw = function() { + redrawHelper(); + + curContext.lineWidth = lineWidth; + var pmouseXLastEvent = p.pmouseX, + pmouseYLastEvent = p.pmouseY; + p.pmouseX = pmouseXLastFrame; + p.pmouseY = pmouseYLastFrame; + + saveContext(); + p.draw(); + restoreContext(); + + pmouseXLastFrame = p.mouseX; + pmouseYLastFrame = p.mouseY; + p.pmouseX = pmouseXLastEvent; + p.pmouseY = pmouseYLastEvent; + }; + + Drawing3D.prototype.redraw = function() { + redrawHelper(); + + var pmouseXLastEvent = p.pmouseX, + pmouseYLastEvent = p.pmouseY; + p.pmouseX = pmouseXLastFrame; + p.pmouseY = pmouseYLastFrame; + // even if the color buffer isn't cleared with background(), + // the depth buffer needs to be cleared regardless. + curContext.clear(curContext.DEPTH_BUFFER_BIT); + curContextCache = { attributes: {}, locations: {} }; + // Delete all the lighting states and the materials the + // user set in the last draw() call. + p.noLights(); + p.lightFalloff(1, 0, 0); + p.shininess(1); + p.ambient(255, 255, 255); + p.specular(0, 0, 0); + p.emissive(0, 0, 0); + p.camera(); + p.draw(); + + pmouseXLastFrame = p.mouseX; + pmouseYLastFrame = p.mouseY; + p.pmouseX = pmouseXLastEvent; + p.pmouseY = pmouseYLastEvent; + }; + + /** + * Stops Processing from continuously executing the code within draw(). If loop() is + * called, the code in draw() begin to run continuously again. If using noLoop() in + * setup(), it should be the last line inside the block. + * When noLoop() is used, it's not possible to manipulate or access the screen inside event + * handling functions such as mousePressed() or keyPressed(). Instead, use those functions + * to call redraw() or loop(), which will run draw(), which can update the screen properly. + * This means that when noLoop() has been called, no drawing can happen, and functions like + * saveFrame() or loadPixels() may not be used. + * Note that if the sketch is resized, redraw() will be called to update the sketch, even + * after noLoop() has been specified. Otherwise, the sketch would enter an odd state until + * loop() was called. + * + * @returns none + * + * @see redraw + * @see draw + * @see loop + */ + p.noLoop = function() { + doLoop = false; + loopStarted = false; + clearInterval(looping); + curSketch.onPause(); + }; + + /** + * Causes Processing to continuously execute the code within draw(). If noLoop() is called, + * the code in draw() stops executing. + * + * @returns none + * + * @see noLoop + */ + p.loop = function() { + if (loopStarted) { + return; + } + + timeSinceLastFPS = Date.now(); + framesSinceLastFPS = 0; + + looping = window.setInterval(function() { + try { + curSketch.onFrameStart(); + p.redraw(); + curSketch.onFrameEnd(); + } catch(e_loop) { + window.clearInterval(looping); + throw e_loop; + } + }, curMsPerFrame); + doLoop = true; + loopStarted = true; + curSketch.onLoop(); + }; + + /** + * Specifies the number of frames to be displayed every second. If the processor is not + * fast enough to maintain the specified rate, it will not be achieved. For example, the + * function call frameRate(30) will attempt to refresh 30 times a second. It is recommended + * to set the frame rate within setup(). The default rate is 60 frames per second. + * + * @param {int} aRate number of frames per second. + * + * @returns none + * + * @see delay + */ + p.frameRate = function(aRate) { + curFrameRate = aRate; + curMsPerFrame = 1000 / curFrameRate; + + // clear and reset interval + if (doLoop) { + p.noLoop(); + p.loop(); + } + }; + + /** + * Quits/stops/exits the program. + * Rather than terminating immediately, exit() will cause the sketch to exit after draw() + * has completed (or after setup() completes if called during the setup() method). + * + * @returns none + */ + p.exit = function() { + // cleanup + window.clearInterval(looping); + removeInstance(p.externals.canvas.id); + delete(curElement.onmousedown); + + // Step through the libraries to detach them + for (var lib in Processing.lib) { + if (Processing.lib.hasOwnProperty(lib)) { + if (Processing.lib[lib].hasOwnProperty("detach")) { + Processing.lib[lib].detach(p); + } + } + } + + // clean up all event handling + var i = eventHandlers.length; + while (i--) { + detachEventHandler(eventHandlers[i]); + } + curSketch.onExit(); + }; + + //////////////////////////////////////////////////////////////////////////// + // MISC functions + //////////////////////////////////////////////////////////////////////////// + + /** + * Sets the cursor to a predefined symbol, an image, or turns it on if already hidden. + * If you are trying to set an image as the cursor, it is recommended to make the size + * 16x16 or 32x32 pixels. It is not possible to load an image as the cursor if you are + * exporting your program for the Web. The values for parameters x and y must be less + * than the dimensions of the image. + * + * @param {MODE} MODE either ARROW, CROSS, HAND, MOVE, TEXT, WAIT + * @param {PImage} image any variable of type PImage + * @param {int} x the horizonal active spot of the cursor + * @param {int} y the vertical active spot of the cursor + * + * @returns none + * + * @see noCursor + */ + p.cursor = function() { + if (arguments.length > 1 || (arguments.length === 1 && arguments[0] instanceof p.PImage)) { + var image = arguments[0], + x, y; + if (arguments.length >= 3) { + x = arguments[1]; + y = arguments[2]; + if (x < 0 || y < 0 || y >= image.height || x >= image.width) { + throw "x and y must be non-negative and less than the dimensions of the image"; + } + } else { + x = image.width >>> 1; + y = image.height >>> 1; + } + + // see https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property + var imageDataURL = image.toDataURL(); + var style = "url(\"" + imageDataURL + "\") " + x + " " + y + ", default"; + curCursor = curElement.style.cursor = style; + } else if (arguments.length === 1) { + var mode = arguments[0]; + curCursor = curElement.style.cursor = mode; + } else { + curCursor = curElement.style.cursor = oldCursor; + } + }; + + /** + * Hides the cursor from view. + * + * @returns none + * + * @see cursor + */ + p.noCursor = function() { + curCursor = curElement.style.cursor = PConstants.NOCURSOR; + }; + + /** + * Links to a webpage either in the same window or in a new window. The complete URL + * must be specified. + * + * @param {String} href complete url as a String in quotes + * @param {String} target name of the window to load the URL as a string in quotes + * + * @returns none + */ + p.link = function(href, target) { + if (target !== undef) { + window.open(href, target); + } else { + window.location = href; + } + }; + + // PGraphics methods + // These functions exist only for compatibility with P5 + p.beginDraw = noop; + p.endDraw = noop; + + /** + * This function takes content from a canvas and turns it into an ImageData object to be used with a PImage + * + * @returns {ImageData} ImageData object to attach to a PImage (1D array of pixel data) + * + * @see PImage + */ + Drawing2D.prototype.toImageData = function(x, y, w, h) { + x = x !== undef ? x : 0; + y = y !== undef ? y : 0; + w = w !== undef ? w : p.width; + h = h !== undef ? h : p.height; + return curContext.getImageData(x, y, w, h); + }; + + Drawing3D.prototype.toImageData = function(x, y, w, h) { + x = x !== undef ? x : 0; + y = y !== undef ? y : 0; + w = w !== undef ? w : p.width; + h = h !== undef ? h : p.height; + var c = document.createElement("canvas"), + ctx = c.getContext("2d"), + obj = ctx.createImageData(w, h), + uBuff = new Uint8Array(w * h * 4); + curContext.readPixels(x, y, w, h, curContext.RGBA, curContext.UNSIGNED_BYTE, uBuff); + for (var i=0, ul=uBuff.length, obj_data=obj.data; i < ul; i++) { + obj_data[i] = uBuff[(h - 1 - Math.floor(i / 4 / w)) * w * 4 + (i % (w * 4))]; + } + return obj; + }; + + /** + * Displays message in the browser's status area. This is the text area in the lower + * left corner of the browser. The status() function will only work when the + * Processing program is running in a web browser. + * + * @param {String} text any valid String + * + * @returns none + */ + p.status = function(text) { + window.status = text; + }; + + //////////////////////////////////////////////////////////////////////////// + // Binary Functions + //////////////////////////////////////////////////////////////////////////// + + /** + * Converts a byte, char, int, or color to a String containing the equivalent binary + * notation. For example color(0, 102, 153, 255) will convert to the String + * "11111111000000000110011010011001". This function can help make your geeky debugging + * sessions much happier. + * + * @param {byte|char|int|color} num byte, char, int, color: value to convert + * @param {int} numBits number of digits to return + * + * @returns {String} + * + * @see unhex + * @see hex + * @see unbinary + */ + p.binary = function(num, numBits) { + var bit; + if (numBits > 0) { + bit = numBits; + } else if(num instanceof Char) { + bit = 16; + num |= 0; // making it int + } else { + // autodetect, skipping zeros + bit = 32; + while (bit > 1 && !((num >>> (bit - 1)) & 1)) { + bit--; + } + } + var result = ""; + while (bit > 0) { + result += ((num >>> (--bit)) & 1) ? "1" : "0"; + } + return result; + }; + + /** + * Converts a String representation of a binary number to its equivalent integer value. + * For example, unbinary("00001000") will return 8. + * + * @param {String} binaryString String + * + * @returns {Int} + * + * @see hex + * @see binary + * @see unbinary + */ + p.unbinary = function(binaryString) { + var i = binaryString.length - 1, mask = 1, result = 0; + while (i >= 0) { + var ch = binaryString[i--]; + if (ch !== '0' && ch !== '1') { + throw "the value passed into unbinary was not an 8 bit binary number"; + } + if (ch === '1') { + result += mask; + } + mask <<= 1; + } + return result; + }; + + var decimalToHex = function(d, padding) { + //if there is no padding value added, default padding to 8 else go into while statement. + padding = (padding === undef || padding === null) ? padding = 8 : padding; + if (d < 0) { + d = 0xFFFFFFFF + d + 1; + } + var hex = Number(d).toString(16).toUpperCase(); + while (hex.length < padding) { + hex = "0" + hex; + } + if (hex.length >= padding) { + hex = hex.substring(hex.length - padding, hex.length); + } + return hex; + }; + + // note: since we cannot keep track of byte, int types by default the returned string is 8 chars long + // if no 2nd argument is passed. closest compromise we can use to match java implementation Feb 5 2010 + // also the char parser has issues with chars that are not digits or letters IE: !@#$%^&* + /** + * Converts a byte, char, int, or color to a String containing the equivalent hexadecimal notation. + * For example color(0, 102, 153, 255) will convert to the String "FF006699". This function can help + * make your geeky debugging sessions much happier. + * + * @param {byte|char|int|Color} value the value to turn into a hex string + * @param {int} digits the number of digits to return + * + * @returns {String} + * + * @see unhex + * @see binary + * @see unbinary + */ + p.hex = function(value, len) { + if (arguments.length === 1) { + if (value instanceof Char) { + len = 4; + } else { // int or byte, indistinguishable at the moment, default to 8 + len = 8; + } + } + return decimalToHex(value, len); + }; + + function unhexScalar(hex) { + var value = parseInt("0x" + hex, 16); + + // correct for int overflow java expectation + if (value > 2147483647) { + value -= 4294967296; + } + return value; + } + + /** + * Converts a String representation of a hexadecimal number to its equivalent integer value. + * + * @param {String} hex the hex string to convert to an int + * + * @returns {int} + * + * @see hex + * @see binary + * @see unbinary + */ + p.unhex = function(hex) { + if (hex instanceof Array) { + var arr = []; + for (var i = 0; i < hex.length; i++) { + arr.push(unhexScalar(hex[i])); + } + return arr; + } + return unhexScalar(hex); + }; + + // Load a file or URL into strings + /** + * Reads the contents of a file or url and creates a String array of its individual lines. + * The filename parameter can also be a URL to a file found online. If the file is not available or an error occurs, + * null will be returned and an error message will be printed to the console. The error message does not halt + * the program. + * + * @param {String} filename name of the file or url to load + * + * @returns {String[]} + * + * @see loadBytes + * @see saveStrings + * @see saveBytes + */ + p.loadStrings = function(filename) { + if (localStorage[filename]) { + return localStorage[filename].split("\n"); + } + + var filecontent = ajax(filename); + if(typeof filecontent !== "string" || filecontent === "") { + return []; + } + + // deal with the fact that Windows uses \r\n, Unix uses \n, + // Mac uses \r, and we actually expect \n + filecontent = filecontent.replace(/(\r\n?)/g,"\n").replace(/\n$/,""); + + return filecontent.split("\n"); + }; + + // Writes an array of strings to a file, one line per string + /** + * Writes an array of strings to a file, one line per string. This file is saved to the localStorage. + * + * @param {String} filename name of the file to save to localStorage + * @param {String[]} strings string array to be written + * + * @see loadBytes + * @see loadStrings + * @see saveBytes + */ + p.saveStrings = function(filename, strings) { + localStorage[filename] = strings.join('\n'); + }; + + /** + * Reads the contents of a file or url and places it in a byte array. If a file is specified, it must be located in the localStorage. + * The filename parameter can also be a URL to a file found online. + * + * @param {String} filename name of a file in the localStorage or a URL. + * + * @returns {byte[]} + * + * @see loadStrings + * @see saveStrings + * @see saveBytes + */ + p.loadBytes = function(url) { + var string = ajax(url); + var ret = []; + + for (var i = 0; i < string.length; i++) { + ret.push(string.charCodeAt(i)); + } + + return ret; + }; + + //////////////////////////////////////////////////////////////////////////// + // String Functions + //////////////////////////////////////////////////////////////////////////// + /** + * The matchAll() function is identical to match(), except that it returns an array of all matches in + * the specified String, rather than just the first. + * + * @param {String} aString the String to search inside + * @param {String} aRegExp the regexp to be used for matching + * + * @return {String[]} returns an array of matches + * + * @see #match + */ + p.matchAll = function(aString, aRegExp) { + var results = [], + latest; + var regexp = new RegExp(aRegExp, "g"); + while ((latest = regexp.exec(aString)) !== null) { + results.push(latest); + if (latest[0].length === 0) { + ++regexp.lastIndex; + } + } + return results.length > 0 ? results : null; + }; + /** + * The match() function matches a string with a regular expression, and returns the match as an + * array. The first index is the matching expression, and array elements + * [1] and higher represent each of the groups (sequences found in parens). + * + * @param {String} str the String to be searched + * @param {String} regexp the regexp to be used for matching + * + * @return {String[]} an array of matching strings + */ + p.match = function(str, regexp) { + return str.match(regexp); + }; + + //////////////////////////////////////////////////////////////////////////// + // Other java specific functions + //////////////////////////////////////////////////////////////////////////// + + + var logBuffer = []; + + /** + * The println() function writes to the console area of the Processing environment. + * Each call to this function creates a new line of output. Individual elements can be separated with quotes ("") and joined with the string concatenation operator (+). + * + * @param {String} message the string to write to the console + * + * @see #join + * @see #print + */ + p.println = function() { + Processing.logger.println.apply(Processing.logger, arguments); + }; + /** + * The print() function writes to the console area of the Processing environment. + * + * @param {String} message the string to write to the console + * + * @see #join + */ + p.print = function() { + Processing.logger.print.apply(Processing.logger, arguments); + }; + + // Alphanumeric chars arguments automatically converted to numbers when + // passed in, and will come out as numbers. + p.str = function(val) { + if (val instanceof Array) { + var arr = []; + for (var i = 0; i < val.length; i++) { + arr.push(val[i].toString() + ""); + } + return arr; + } + return (val.toString() + ""); + }; + + + // Conversion + function booleanScalar(val) { + if (typeof val === 'number') { + return val !== 0; + } + if (typeof val === 'boolean') { + return val; + } + if (typeof val === 'string') { + return val.toLowerCase() === 'true'; + } + if (val instanceof Char) { + // 1, T or t + return val.code === 49 || val.code === 84 || val.code === 116; + } + } + + /** + * Converts the passed parameter to the function to its boolean value. + * It will return an array of booleans if an array is passed in. + * + * @param {int, byte, string} val the parameter to be converted to boolean + * @param {int[], byte[], string[]} val the array to be converted to boolean[] + * + * @return {boolean|boolean[]} returns a boolean or an array of booleans + */ + p.parseBoolean = function (val) { + if (val instanceof Array) { + var ret = []; + for (var i = 0; i < val.length; i++) { + ret.push(booleanScalar(val[i])); + } + return ret; + } + return booleanScalar(val); + }; + + /** + * Converts the passed parameter to the function to its byte value. + * A byte is a number between -128 and 127. + * It will return an array of bytes if an array is passed in. + * + * @param {int, char} what the parameter to be conveted to byte + * @param {int[], char[]} what the array to be converted to byte[] + * + * @return {byte|byte[]} returns a byte or an array of bytes + */ + p.parseByte = function(what) { + if (what instanceof Array) { + var bytes = []; + for (var i = 0; i < what.length; i++) { + bytes.push((0 - (what[i] & 0x80)) | (what[i] & 0x7F)); + } + return bytes; + } + return (0 - (what & 0x80)) | (what & 0x7F); + }; + + /** + * Converts the passed parameter to the function to its char value. + * It will return an array of chars if an array is passed in. + * + * @param {int, byte} key the parameter to be conveted to char + * @param {int[], byte[]} key the array to be converted to char[] + * + * @return {char|char[]} returns a char or an array of chars + */ + p.parseChar = function(key) { + if (typeof key === "number") { + return new Char(String.fromCharCode(key & 0xFFFF)); + } + if (key instanceof Array) { + var ret = []; + for (var i = 0; i < key.length; i++) { + ret.push(new Char(String.fromCharCode(key[i] & 0xFFFF))); + } + return ret; + } + throw "char() may receive only one argument of type int, byte, int[], or byte[]."; + }; + + // Processing doc claims good argument types are: int, char, byte, boolean, + // String, int[], char[], byte[], boolean[], String[]. + // floats should not work. However, floats with only zeroes right of the + // decimal will work because JS converts those to int. + function floatScalar(val) { + if (typeof val === 'number') { + return val; + } + if (typeof val === 'boolean') { + return val ? 1 : 0; + } + if (typeof val === 'string') { + return parseFloat(val); + } + if (val instanceof Char) { + return val.code; + } + } + + /** + * Converts the passed parameter to the function to its float value. + * It will return an array of floats if an array is passed in. + * + * @param {int, char, boolean, string} val the parameter to be conveted to float + * @param {int[], char[], boolean[], string[]} val the array to be converted to float[] + * + * @return {float|float[]} returns a float or an array of floats + */ + p.parseFloat = function(val) { + if (val instanceof Array) { + var ret = []; + for (var i = 0; i < val.length; i++) { + ret.push(floatScalar(val[i])); + } + return ret; + } + return floatScalar(val); + }; + + function intScalar(val, radix) { + if (typeof val === 'number') { + return val & 0xFFFFFFFF; + } + if (typeof val === 'boolean') { + return val ? 1 : 0; + } + if (typeof val === 'string') { + var number = parseInt(val, radix || 10); // Default to decimal radix. + return number & 0xFFFFFFFF; + } + if (val instanceof Char) { + return val.code; + } + } + + /** + * Converts the passed parameter to the function to its int value. + * It will return an array of ints if an array is passed in. + * + * @param {string, char, boolean, float} val the parameter to be conveted to int + * @param {string[], char[], boolean[], float[]} val the array to be converted to int[] + * @param {int} radix optional the radix of the number (for js compatibility) + * + * @return {int|int[]} returns a int or an array of ints + */ + p.parseInt = function(val, radix) { + if (val instanceof Array) { + var ret = []; + for (var i = 0; i < val.length; i++) { + if (typeof val[i] === 'string' && !/^\s*[+\-]?\d+\s*$/.test(val[i])) { + ret.push(0); + } else { + ret.push(intScalar(val[i], radix)); + } + } + return ret; + } + return intScalar(val, radix); + }; + + p.__int_cast = function(val) { + return 0|val; + }; + + p.__instanceof = function(obj, type) { + if (typeof type !== "function") { + throw "Function is expected as type argument for instanceof operator"; + } + + if (typeof obj === "string") { + // special case for strings + return type === Object || type === String; + } + + if (obj instanceof type) { + // fast check if obj is already of type instance + return true; + } + + if (typeof obj !== "object" || obj === null) { + return false; // not an object or null + } + + var objType = obj.constructor; + if (type.$isInterface) { + // expecting the interface + // queueing interfaces from type and its base classes + var interfaces = []; + while (objType) { + if (objType.$interfaces) { + interfaces = interfaces.concat(objType.$interfaces); + } + objType = objType.$base; + } + while (interfaces.length > 0) { + var i = interfaces.shift(); + if (i === type) { + return true; + } + // wide search in base interfaces + if (i.$interfaces) { + interfaces = interfaces.concat(i.$interfaces); + } + } + return false; + } + + while (objType.hasOwnProperty("$base")) { + objType = objType.$base; + if (objType === type) { + return true; // object was found + } + } + + return false; + }; + + /** + * Defines the dimension of the display window in units of pixels. The size() function must + * be the first line in setup(). If size() is not called, the default size of the window is + * 100x100 pixels. The system variables width and height are set by the parameters passed to + * the size() function. + * + * @param {int} aWidth width of the display window in units of pixels + * @param {int} aHeight height of the display window in units of pixels + * @param {MODE} aMode Either P2D, P3D, JAVA2D, or OPENGL + * + * @see createGraphics + * @see screen + */ + DrawingShared.prototype.size = function(aWidth, aHeight, aMode) { + if (doStroke) { + p.stroke(0); + } + + if (doFill) { + p.fill(255); + } + + // The default 2d context has already been created in the p.init() stage if + // a 3d context was not specified. This is so that a 2d context will be + // available if size() was not called. + var savedProperties = { + fillStyle: curContext.fillStyle, + strokeStyle: curContext.strokeStyle, + lineCap: curContext.lineCap, + lineJoin: curContext.lineJoin + }; + // remove the style width and height properties to ensure that the canvas gets set to + // aWidth and aHeight coming in + if (curElement.style.length > 0 ) { + curElement.style.removeProperty("width"); + curElement.style.removeProperty("height"); + } + + curElement.width = p.width = aWidth || 100; + curElement.height = p.height = aHeight || 100; + + for (var prop in savedProperties) { + if (savedProperties.hasOwnProperty(prop)) { + curContext[prop] = savedProperties[prop]; + } + } + + // make sure to set the default font the first time round. + p.textFont(curTextFont); + + // Set the background to whatever it was called last as if background() was called before size() + // If background() hasn't been called before, set background() to a light gray + p.background(); + + // set 5% for pixels to cache (or 1000) + maxPixelsCached = Math.max(1000, aWidth * aHeight * 0.05); + + // Externalize the context + p.externals.context = curContext; + + for (var i = 0; i < PConstants.SINCOS_LENGTH; i++) { + sinLUT[i] = p.sin(i * (PConstants.PI / 180) * 0.5); + cosLUT[i] = p.cos(i * (PConstants.PI / 180) * 0.5); + } + }; + + Drawing2D.prototype.size = function(aWidth, aHeight, aMode) { + if (curContext === undef) { + // size() was called without p.init() default context, i.e. p.createGraphics() + curContext = curElement.getContext("2d"); + userMatrixStack = new PMatrixStack(); + userReverseMatrixStack = new PMatrixStack(); + modelView = new PMatrix2D(); + modelViewInv = new PMatrix2D(); + } + + DrawingShared.prototype.size.apply(this, arguments); + }; + + Drawing3D.prototype.size = (function() { + var size3DCalled = false; + + return function size(aWidth, aHeight, aMode) { + if (size3DCalled) { + throw "Multiple calls to size() for 3D renders are not allowed."; + } + size3DCalled = true; + + function getGLContext(canvas) { + var ctxNames = ['experimental-webgl', 'webgl', 'webkit-3d'], + gl; + + for (var i=0, l=ctxNames.length; i<l; i++) { + gl = canvas.getContext(ctxNames[i], {antialias: false, preserveDrawingBuffer: true}); + if (gl) { + break; + } + } + + return gl; + } + + // Get the 3D rendering context. + try { + // If the HTML <canvas> dimensions differ from the + // dimensions specified in the size() call in the sketch, for + // 3D sketches, browsers will either not render or render the + // scene incorrectly. To fix this, we need to adjust the + // width and height attributes of the canvas. + curElement.width = p.width = aWidth || 100; + curElement.height = p.height = aHeight || 100; + curContext = getGLContext(curElement); + canTex = curContext.createTexture(); + textTex = curContext.createTexture(); + } catch(e_size) { + Processing.debug(e_size); + } + + if (!curContext) { + throw "WebGL context is not supported on this browser."; + } + + // Set defaults + curContext.viewport(0, 0, curElement.width, curElement.height); + curContext.enable(curContext.DEPTH_TEST); + curContext.enable(curContext.BLEND); + curContext.blendFunc(curContext.SRC_ALPHA, curContext.ONE_MINUS_SRC_ALPHA); + + // Create the program objects to render 2D (points, lines) and + // 3D (spheres, boxes) shapes. Because 2D shapes are not lit, + // lighting calculations are ommitted from this program object. + programObject2D = createProgramObject(curContext, vertexShaderSrc2D, fragmentShaderSrc2D); + + programObjectUnlitShape = createProgramObject(curContext, vertexShaderSrcUnlitShape, fragmentShaderSrcUnlitShape); + + // Set the default point and line width for the 2D and unlit shapes. + p.strokeWeight(1); + + // Now that the programs have been compiled, we can set the default + // states for the lights. + programObject3D = createProgramObject(curContext, vertexShaderSrc3D, fragmentShaderSrc3D); + curContext.useProgram(programObject3D); + + // Assume we aren't using textures by default. + uniformi("usingTexture3d", programObject3D, "usingTexture", usingTexture); + + // Set some defaults. + p.lightFalloff(1, 0, 0); + p.shininess(1); + p.ambient(255, 255, 255); + p.specular(0, 0, 0); + p.emissive(0, 0, 0); + + // Create buffers for 3D primitives + boxBuffer = curContext.createBuffer(); + curContext.bindBuffer(curContext.ARRAY_BUFFER, boxBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, boxVerts, curContext.STATIC_DRAW); + + boxNormBuffer = curContext.createBuffer(); + curContext.bindBuffer(curContext.ARRAY_BUFFER, boxNormBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, boxNorms, curContext.STATIC_DRAW); + + boxOutlineBuffer = curContext.createBuffer(); + curContext.bindBuffer(curContext.ARRAY_BUFFER, boxOutlineBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, boxOutlineVerts, curContext.STATIC_DRAW); + + // used to draw the rectangle and the outline + rectBuffer = curContext.createBuffer(); + curContext.bindBuffer(curContext.ARRAY_BUFFER, rectBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, rectVerts, curContext.STATIC_DRAW); + + rectNormBuffer = curContext.createBuffer(); + curContext.bindBuffer(curContext.ARRAY_BUFFER, rectNormBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, rectNorms, curContext.STATIC_DRAW); + + // The sphere vertices are specified dynamically since the user + // can change the level of detail. Everytime the user does that + // using sphereDetail(), the new vertices are calculated. + sphereBuffer = curContext.createBuffer(); + + lineBuffer = curContext.createBuffer(); + + // Shape buffers + fillBuffer = curContext.createBuffer(); + fillColorBuffer = curContext.createBuffer(); + strokeColorBuffer = curContext.createBuffer(); + shapeTexVBO = curContext.createBuffer(); + + pointBuffer = curContext.createBuffer(); + curContext.bindBuffer(curContext.ARRAY_BUFFER, pointBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([0, 0, 0]), curContext.STATIC_DRAW); + + textBuffer = curContext.createBuffer(); + curContext.bindBuffer(curContext.ARRAY_BUFFER, textBuffer ); + curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([1,1,0,-1,1,0,-1,-1,0,1,-1,0]), curContext.STATIC_DRAW); + + textureBuffer = curContext.createBuffer(); + curContext.bindBuffer(curContext.ARRAY_BUFFER, textureBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([0,0,1,0,1,1,0,1]), curContext.STATIC_DRAW); + + indexBuffer = curContext.createBuffer(); + curContext.bindBuffer(curContext.ELEMENT_ARRAY_BUFFER, indexBuffer); + curContext.bufferData(curContext.ELEMENT_ARRAY_BUFFER, new Uint16Array([0,1,2,2,3,0]), curContext.STATIC_DRAW); + + cam = new PMatrix3D(); + cameraInv = new PMatrix3D(); + modelView = new PMatrix3D(); + modelViewInv = new PMatrix3D(); + projection = new PMatrix3D(); + p.camera(); + p.perspective(); + + userMatrixStack = new PMatrixStack(); + userReverseMatrixStack = new PMatrixStack(); + // used by both curve and bezier, so just init here + curveBasisMatrix = new PMatrix3D(); + curveToBezierMatrix = new PMatrix3D(); + curveDrawMatrix = new PMatrix3D(); + bezierDrawMatrix = new PMatrix3D(); + bezierBasisInverse = new PMatrix3D(); + bezierBasisMatrix = new PMatrix3D(); + bezierBasisMatrix.set(-1, 3, -3, 1, 3, -6, 3, 0, -3, 3, 0, 0, 1, 0, 0, 0); + + DrawingShared.prototype.size.apply(this, arguments); + }; + }()); + + //////////////////////////////////////////////////////////////////////////// + // Lights + //////////////////////////////////////////////////////////////////////////// + + /** + * Adds an ambient light. Ambient light doesn't come from a specific direction, + * the rays have light have bounced around so much that objects are evenly lit + * from all sides. Ambient lights are almost always used in combination with + * other types of lights. Lights need to be included in the <b>draw()</b> to + * remain persistent in a looping program. Placing them in the <b>setup()</b> + * of a looping program will cause them to only have an effect the first time + * through the loop. The effect of the parameters is determined by the current + * color mode. + * + * @param {int | float} r red or hue value + * @param {int | float} g green or hue value + * @param {int | float} b blue or hue value + * + * @param {int | float} x x position of light (used for falloff) + * @param {int | float} y y position of light (used for falloff) + * @param {int | float} z z position of light (used for falloff) + * + * @returns none + * + * @see lights + * @see directionalLight + * @see pointLight + * @see spotLight + */ + Drawing2D.prototype.ambientLight = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.ambientLight = function(r, g, b, x, y, z) { + if (lightCount === PConstants.MAX_LIGHTS) { + throw "can only create " + PConstants.MAX_LIGHTS + " lights"; + } + + var pos = new PVector(x, y, z); + var view = new PMatrix3D(); + view.scale(1, -1, 1); + view.apply(modelView.array()); + view.mult(pos, pos); + + // Instead of calling p.color, we do the calculations ourselves to + // reduce property lookups. + var col = color$4(r, g, b, 0); + var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, + ((col & PConstants.GREEN_MASK) >>> 8) / 255, + (col & PConstants.BLUE_MASK) / 255 ]; + + curContext.useProgram(programObject3D); + uniformf("uLights.color.3d." + lightCount, programObject3D, "uLights" + lightCount + ".color", normalizedCol); + uniformf("uLights.position.3d." + lightCount, programObject3D, "uLights" + lightCount + ".position", pos.array()); + uniformi("uLights.type.3d." + lightCount, programObject3D, "uLights" + lightCount + ".type", 0); + uniformi("uLightCount3d", programObject3D, "uLightCount", ++lightCount); + }; + + /** + * Adds a directional light. Directional light comes from one direction and + * is stronger when hitting a surface squarely and weaker if it hits at a + * gentle angle. After hitting a surface, a directional lights scatters in + * all directions. Lights need to be included in the <b>draw()</b> to remain + * persistent in a looping program. Placing them in the <b>setup()</b> of a + * looping program will cause them to only have an effect the first time + * through the loop. The affect of the <br>r</b>, <br>g</b>, and <br>b</b> + * parameters is determined by the current color mode. The <b>nx</b>, + * <b>ny</b>, and <b>nz</b> parameters specify the direction the light is + * facing. For example, setting <b>ny</b> to -1 will cause the geometry to be + * lit from below (the light is facing directly upward). + * + * @param {int | float} r red or hue value + * @param {int | float} g green or hue value + * @param {int | float} b blue or hue value + * + * @param {int | float} nx direction along the x axis + * @param {int | float} ny direction along the y axis + * @param {int | float} nz direction along the z axis + * + * @returns none + * + * @see lights + * @see ambientLight + * @see pointLight + * @see spotLight + */ + Drawing2D.prototype.directionalLight = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.directionalLight = function(r, g, b, nx, ny, nz) { + if (lightCount === PConstants.MAX_LIGHTS) { + throw "can only create " + PConstants.MAX_LIGHTS + " lights"; + } + + curContext.useProgram(programObject3D); + + var mvm = new PMatrix3D(); + mvm.scale(1, -1, 1); + mvm.apply(modelView.array()); + mvm = mvm.array(); + + // We need to multiply the direction by the model view matrix, but + // the mult function checks the w component of the vector, if it isn't + // present, it uses 1, so we manually multiply. + var dir = [ + mvm[0] * nx + mvm[4] * ny + mvm[8] * nz, + mvm[1] * nx + mvm[5] * ny + mvm[9] * nz, + mvm[2] * nx + mvm[6] * ny + mvm[10] * nz + ]; + + // Instead of calling p.color, we do the calculations ourselves to + // reduce property lookups. + var col = color$4(r, g, b, 0); + var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, + ((col & PConstants.GREEN_MASK) >>> 8) / 255, + (col & PConstants.BLUE_MASK) / 255 ]; + + uniformf("uLights.color.3d." + lightCount, programObject3D, "uLights" + lightCount + ".color", normalizedCol); + uniformf("uLights.position.3d." + lightCount, programObject3D, "uLights" + lightCount + ".position", dir); + uniformi("uLights.type.3d." + lightCount, programObject3D, "uLights" + lightCount + ".type", 1); + uniformi("uLightCount3d", programObject3D, "uLightCount", ++lightCount); + }; + + /** + * Sets the falloff rates for point lights, spot lights, and ambient lights. + * The parameters are used to determine the falloff with the following equation: + * + * d = distance from light position to vertex position + * falloff = 1 / (CONSTANT + d * LINEAR + (d*d) * QUADRATIC) + * + * Like <b>fill()</b>, it affects only the elements which are created after it in the + * code. The default value if <b>LightFalloff(1.0, 0.0, 0.0)</b>. Thinking about an + * ambient light with a falloff can be tricky. It is used, for example, if you + * wanted a region of your scene to be lit ambiently one color and another region + * to be lit ambiently by another color, you would use an ambient light with location + * and falloff. You can think of it as a point light that doesn't care which direction + * a surface is facing. + * + * @param {int | float} constant constant value for determining falloff + * @param {int | float} linear linear value for determining falloff + * @param {int | float} quadratic quadratic value for determining falloff + * + * @returns none + * + * @see lights + * @see ambientLight + * @see pointLight + * @see spotLight + * @see lightSpecular + */ + Drawing2D.prototype.lightFalloff = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.lightFalloff = function(constant, linear, quadratic) { + curContext.useProgram(programObject3D); + uniformf("uFalloff3d", programObject3D, "uFalloff", [constant, linear, quadratic]); + }; + + /** + * Sets the specular color for lights. Like <b>fill()</b>, it affects only the + * elements which are created after it in the code. Specular refers to light + * which bounces off a surface in a perferred direction (rather than bouncing + * in all directions like a diffuse light) and is used for creating highlights. + * The specular quality of a light interacts with the specular material qualities + * set through the <b>specular()</b> and <b>shininess()</b> functions. + * + * @param {int | float} r red or hue value + * @param {int | float} g green or hue value + * @param {int | float} b blue or hue value + * + * @returns none + * + * @see lights + * @see ambientLight + * @see pointLight + * @see spotLight + */ + Drawing2D.prototype.lightSpecular = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.lightSpecular = function(r, g, b) { + + // Instead of calling p.color, we do the calculations ourselves to + // reduce property lookups. + var col = color$4(r, g, b, 0); + var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, + ((col & PConstants.GREEN_MASK) >>> 8) / 255, + (col & PConstants.BLUE_MASK) / 255 ]; + + curContext.useProgram(programObject3D); + uniformf("uSpecular3d", programObject3D, "uSpecular", normalizedCol); + }; + + /** + * Sets the default ambient light, directional light, falloff, and specular + * values. The defaults are ambientLight(128, 128, 128) and + * directionalLight(128, 128, 128, 0, 0, -1), lightFalloff(1, 0, 0), and + * lightSpecular(0, 0, 0). Lights need to be included in the draw() to remain + * persistent in a looping program. Placing them in the setup() of a looping + * program will cause them to only have an effect the first time through the + * loop. + * + * @returns none + * + * @see ambientLight + * @see directionalLight + * @see pointLight + * @see spotLight + * @see noLights + * + */ + p.lights = function() { + p.ambientLight(128, 128, 128); + p.directionalLight(128, 128, 128, 0, 0, -1); + p.lightFalloff(1, 0, 0); + p.lightSpecular(0, 0, 0); + }; + + /** + * Adds a point light. Lights need to be included in the <b>draw()</b> to remain + * persistent in a looping program. Placing them in the <b>setup()</b> of a + * looping program will cause them to only have an effect the first time through + * the loop. The affect of the <b>r</b>, <b>g</b>, and <b>b</b> parameters + * is determined by the current color mode. The <b>x</b>, <b>y</b>, and <b>z</b> + * parameters set the position of the light. + * + * @param {int | float} r red or hue value + * @param {int | float} g green or hue value + * @param {int | float} b blue or hue value + * @param {int | float} x x coordinate of the light + * @param {int | float} y y coordinate of the light + * @param {int | float} z z coordinate of the light + * + * @returns none + * + * @see lights + * @see directionalLight + * @see ambientLight + * @see spotLight + */ + Drawing2D.prototype.pointLight = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.pointLight = function(r, g, b, x, y, z) { + if (lightCount === PConstants.MAX_LIGHTS) { + throw "can only create " + PConstants.MAX_LIGHTS + " lights"; + } + + // Place the point in view space once instead of once per vertex + // in the shader. + var pos = new PVector(x, y, z); + var view = new PMatrix3D(); + view.scale(1, -1, 1); + view.apply(modelView.array()); + view.mult(pos, pos); + + // Instead of calling p.color, we do the calculations ourselves to + // reduce property lookups. + var col = color$4(r, g, b, 0); + var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, + ((col & PConstants.GREEN_MASK) >>> 8) / 255, + (col & PConstants.BLUE_MASK) / 255 ]; + + curContext.useProgram(programObject3D); + uniformf("uLights.color.3d." + lightCount, programObject3D, "uLights" + lightCount + ".color", normalizedCol); + uniformf("uLights.position.3d." + lightCount, programObject3D, "uLights" + lightCount + ".position", pos.array()); + uniformi("uLights.type.3d." + lightCount, programObject3D, "uLights" + lightCount + ".type", 2); + uniformi("uLightCount3d", programObject3D, "uLightCount", ++lightCount); + }; + + /** + * Disable all lighting. Lighting is turned off by default and enabled with + * the lights() method. This function can be used to disable lighting so + * that 2D geometry (which does not require lighting) can be drawn after a + * set of lighted 3D geometry. + * + * @returns none + * + * @see lights + */ + Drawing2D.prototype.noLights = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.noLights = function() { + lightCount = 0; + curContext.useProgram(programObject3D); + uniformi("uLightCount3d", programObject3D, "uLightCount", lightCount); + }; + + /** + * Adds a spot light. Lights need to be included in the <b>draw()</b> to + * remain persistent in a looping program. Placing them in the <b>setup()</b> + * of a looping program will cause them to only have an effect the first time + * through the loop. The affect of the <b>r</b>, <b>g</b>, and <b>b</b> parameters + * is determined by the current color mode. The <b>x</b>, <b>y</b>, and <b>z</b> + * parameters specify the position of the light and <b>nx</b>, <b>ny</b>, <b>nz</b> + * specify the direction or light. The angle parameter affects <b>angle</b> of the + * spotlight cone. + * + * @param {int | float} r red or hue value + * @param {int | float} g green or hue value + * @param {int | float} b blue or hue value + * @param {int | float} x coordinate of the light + * @param {int | float} y coordinate of the light + * @param {int | float} z coordinate of the light + * @param {int | float} nx direction along the x axis + * @param {int | float} ny direction along the y axis + * @param {int | float} nz direction along the z axis + * @param {float} angle angle of the spotlight cone + * @param {float} concentration exponent determining the center bias of the cone + * + * @returns none + * + * @see lights + * @see directionalLight + * @see ambientLight + * @see pointLight + */ + Drawing2D.prototype.spotLight = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.spotLight = function(r, g, b, x, y, z, nx, ny, nz, angle, concentration) { + if (lightCount === PConstants.MAX_LIGHTS) { + throw "can only create " + PConstants.MAX_LIGHTS + " lights"; + } + + curContext.useProgram(programObject3D); + + // multiply the position and direction by the model view matrix + // once per object rather than once per vertex. + var pos = new PVector(x, y, z); + var mvm = new PMatrix3D(); + mvm.scale(1, -1, 1); + mvm.apply(modelView.array()); + mvm.mult(pos, pos); + + // Convert to array since we need to directly access the elements. + mvm = mvm.array(); + + // We need to multiply the direction by the model view matrix, but + // the mult function checks the w component of the vector, if it isn't + // present, it uses 1, so we use a very small value as a work around. + var dir = [ + mvm[0] * nx + mvm[4] * ny + mvm[8] * nz, + mvm[1] * nx + mvm[5] * ny + mvm[9] * nz, + mvm[2] * nx + mvm[6] * ny + mvm[10] * nz + ]; + + // Instead of calling p.color, we do the calculations ourselves to + // reduce property lookups. + var col = color$4(r, g, b, 0); + var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, + ((col & PConstants.GREEN_MASK) >>> 8) / 255, + (col & PConstants.BLUE_MASK) / 255 ]; + + uniformf("uLights.color.3d." + lightCount, programObject3D, "uLights" + lightCount + ".color", normalizedCol); + uniformf("uLights.position.3d." + lightCount, programObject3D, "uLights" + lightCount + ".position", pos.array()); + uniformf("uLights.direction.3d." + lightCount, programObject3D, "uLights" + lightCount + ".direction", dir); + uniformf("uLights.concentration.3d." + lightCount, programObject3D, "uLights" + lightCount + ".concentration", concentration); + uniformf("uLights.angle.3d." + lightCount, programObject3D, "uLights" + lightCount + ".angle", angle); + uniformi("uLights.type.3d." + lightCount, programObject3D, "uLights" + lightCount + ".type", 3); + uniformi("uLightCount3d", programObject3D, "uLightCount", ++lightCount); + }; + + //////////////////////////////////////////////////////////////////////////// + // Camera functions + //////////////////////////////////////////////////////////////////////////// + + /** + * The <b>beginCamera()</b> and <b>endCamera()</b> functions enable advanced customization of the camera space. + * The functions are useful if you want to more control over camera movement, however for most users, the <b>camera()</b> + * function will be sufficient.<br /><br />The camera functions will replace any transformations (such as <b>rotate()</b> + * or <b>translate()</b>) that occur before them in <b>draw()</b>, but they will not automatically replace the camera + * transform itself. For this reason, camera functions should be placed at the beginning of <b>draw()</b> (so that + * transformations happen afterwards), and the <b>camera()</b> function can be used after <b>beginCamera()</b> if + * you want to reset the camera before applying transformations.<br /><br />This function sets the matrix mode to the + * camera matrix so calls such as <b>translate()</b>, <b>rotate()</b>, applyMatrix() and resetMatrix() affect the camera. + * <b>beginCamera()</b> should always be used with a following <b>endCamera()</b> and pairs of <b>beginCamera()</b> and + * <b>endCamera()</b> cannot be nested. + * + * @see camera + * @see endCamera + * @see applyMatrix + * @see resetMatrix + * @see translate + * @see rotate + * @see scale + */ + Drawing2D.prototype.beginCamera = function() { + throw ("beginCamera() is not available in 2D mode"); + }; + + Drawing3D.prototype.beginCamera = function() { + if (manipulatingCamera) { + throw ("You cannot call beginCamera() again before calling endCamera()"); + } + manipulatingCamera = true; + modelView = cameraInv; + modelViewInv = cam; + }; + + /** + * The <b>beginCamera()</b> and <b>endCamera()</b> functions enable advanced customization of the camera space. + * Please see the reference for <b>beginCamera()</b> for a description of how the functions are used. + * + * @see beginCamera + */ + Drawing2D.prototype.endCamera = function() { + throw ("endCamera() is not available in 2D mode"); + }; + + Drawing3D.prototype.endCamera = function() { + if (!manipulatingCamera) { + throw ("You cannot call endCamera() before calling beginCamera()"); + } + modelView.set(cam); + modelViewInv.set(cameraInv); + manipulatingCamera = false; + }; + + /** + * Sets the position of the camera through setting the eye position, the center of the scene, and which axis is facing + * upward. Moving the eye position and the direction it is pointing (the center of the scene) allows the images to be + * seen from different angles. The version without any parameters sets the camera to the default position, pointing to + * the center of the display window with the Y axis as up. The default values are camera(width/2.0, height/2.0, + * (height/2.0) / tan(PI*60.0 / 360.0), width/2.0, height/2.0, 0, 0, 1, 0). This function is similar to gluLookAt() + * in OpenGL, but it first clears the current camera settings. + * + * @param {float} eyeX x-coordinate for the eye + * @param {float} eyeY y-coordinate for the eye + * @param {float} eyeZ z-coordinate for the eye + * @param {float} centerX x-coordinate for the center of the scene + * @param {float} centerY y-coordinate for the center of the scene + * @param {float} centerZ z-coordinate for the center of the scene + * @param {float} upX usually 0.0, 1.0, -1.0 + * @param {float} upY usually 0.0, 1.0, -1.0 + * @param {float} upZ usually 0.0, 1.0, -1.0 + * + * @see beginCamera + * @see endCamera + * @see frustum + */ + p.camera = function(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) { + if (eyeX === undef) { + // Workaround if createGraphics is used. + cameraX = p.width / 2; + cameraY = p.height / 2; + cameraZ = cameraY / Math.tan(cameraFOV / 2); + eyeX = cameraX; + eyeY = cameraY; + eyeZ = cameraZ; + centerX = cameraX; + centerY = cameraY; + centerZ = 0; + upX = 0; + upY = 1; + upZ = 0; + } + + var z = new PVector(eyeX - centerX, eyeY - centerY, eyeZ - centerZ); + var y = new PVector(upX, upY, upZ); + z.normalize(); + var x = PVector.cross(y, z); + y = PVector.cross(z, x); + x.normalize(); + y.normalize(); + + var xX = x.x, + xY = x.y, + xZ = x.z; + + var yX = y.x, + yY = y.y, + yZ = y.z; + + var zX = z.x, + zY = z.y, + zZ = z.z; + + cam.set(xX, xY, xZ, 0, yX, yY, yZ, 0, zX, zY, zZ, 0, 0, 0, 0, 1); + + cam.translate(-eyeX, -eyeY, -eyeZ); + + cameraInv.reset(); + cameraInv.invApply(xX, xY, xZ, 0, yX, yY, yZ, 0, zX, zY, zZ, 0, 0, 0, 0, 1); + + cameraInv.translate(eyeX, eyeY, eyeZ); + + modelView.set(cam); + modelViewInv.set(cameraInv); + }; + + /** + * Sets a perspective projection applying foreshortening, making distant objects appear smaller than closer ones. The + * parameters define a viewing volume with the shape of truncated pyramid. Objects near to the front of the volume appear + * their actual size, while farther objects appear smaller. This projection simulates the perspective of the world more + * accurately than orthographic projection. The version of perspective without parameters sets the default perspective and + * the version with four parameters allows the programmer to set the area precisely. The default values are: + * perspective(PI/3.0, width/height, cameraZ/10.0, cameraZ*10.0) where cameraZ is ((height/2.0) / tan(PI*60.0/360.0)); + * + * @param {float} fov field-of-view angle (in radians) for vertical direction + * @param {float} aspect ratio of width to height + * @param {float} zNear z-position of nearest clipping plane + * @param {float} zFar z-positions of farthest clipping plane + */ + p.perspective = function(fov, aspect, near, far) { + if (arguments.length === 0) { + //in case canvas is resized + cameraY = curElement.height / 2; + cameraZ = cameraY / Math.tan(cameraFOV / 2); + cameraNear = cameraZ / 10; + cameraFar = cameraZ * 10; + cameraAspect = p.width / p.height; + fov = cameraFOV; + aspect = cameraAspect; + near = cameraNear; + far = cameraFar; + } + + var yMax, yMin, xMax, xMin; + yMax = near * Math.tan(fov / 2); + yMin = -yMax; + xMax = yMax * aspect; + xMin = yMin * aspect; + p.frustum(xMin, xMax, yMin, yMax, near, far); + }; + + /** + * Sets a perspective matrix defined through the parameters. Works like glFrustum, except it wipes out the current + * perspective matrix rather than muliplying itself with it. + * + * @param {float} left left coordinate of the clipping plane + * @param {float} right right coordinate of the clipping plane + * @param {float} bottom bottom coordinate of the clipping plane + * @param {float} top top coordinate of the clipping plane + * @param {float} near near coordinate of the clipping plane + * @param {float} far far coordinate of the clipping plane + * + * @see beginCamera + * @see camera + * @see endCamera + * @see perspective + */ + Drawing2D.prototype.frustum = function() { + throw("Processing.js: frustum() is not supported in 2D mode"); + }; + + Drawing3D.prototype.frustum = function(left, right, bottom, top, near, far) { + frustumMode = true; + projection = new PMatrix3D(); + projection.set((2 * near) / (right - left), 0, (right + left) / (right - left), + 0, 0, (2 * near) / (top - bottom), (top + bottom) / (top - bottom), + 0, 0, 0, -(far + near) / (far - near), -(2 * far * near) / (far - near), + 0, 0, -1, 0); + var proj = new PMatrix3D(); + proj.set(projection); + proj.transpose(); + curContext.useProgram(programObject2D); + uniformMatrix("projection2d", programObject2D, "uProjection", false, proj.array()); + curContext.useProgram(programObject3D); + uniformMatrix("projection3d", programObject3D, "uProjection", false, proj.array()); + curContext.useProgram(programObjectUnlitShape); + uniformMatrix("uProjectionUS", programObjectUnlitShape, "uProjection", false, proj.array()); + }; + + /** + * Sets an orthographic projection and defines a parallel clipping volume. All objects with the same dimension appear + * the same size, regardless of whether they are near or far from the camera. The parameters to this function specify + * the clipping volume where left and right are the minimum and maximum x values, top and bottom are the minimum and + * maximum y values, and near and far are the minimum and maximum z values. If no parameters are given, the default + * is used: ortho(0, width, 0, height, -10, 10). + * + * @param {float} left left plane of the clipping volume + * @param {float} right right plane of the clipping volume + * @param {float} bottom bottom plane of the clipping volume + * @param {float} top top plane of the clipping volume + * @param {float} near maximum distance from the origin to the viewer + * @param {float} far maximum distance from the origin away from the viewer + */ + p.ortho = function(left, right, bottom, top, near, far) { + if (arguments.length === 0) { + left = 0; + right = p.width; + bottom = 0; + top = p.height; + near = -10; + far = 10; + } + + var x = 2 / (right - left); + var y = 2 / (top - bottom); + var z = -2 / (far - near); + + var tx = -(right + left) / (right - left); + var ty = -(top + bottom) / (top - bottom); + var tz = -(far + near) / (far - near); + + projection = new PMatrix3D(); + projection.set(x, 0, 0, tx, 0, y, 0, ty, 0, 0, z, tz, 0, 0, 0, 1); + + var proj = new PMatrix3D(); + proj.set(projection); + proj.transpose(); + curContext.useProgram(programObject2D); + uniformMatrix("projection2d", programObject2D, "uProjection", false, proj.array()); + curContext.useProgram(programObject3D); + uniformMatrix("projection3d", programObject3D, "uProjection", false, proj.array()); + curContext.useProgram(programObjectUnlitShape); + uniformMatrix("uProjectionUS", programObjectUnlitShape, "uProjection", false, proj.array()); + frustumMode = false; + }; + /** + * The printProjection() prints the current projection matrix to the text window. + */ + p.printProjection = function() { + projection.print(); + }; + /** + * The printCamera() function prints the current camera matrix. + */ + p.printCamera = function() { + cam.print(); + }; + + //////////////////////////////////////////////////////////////////////////// + // Shapes + //////////////////////////////////////////////////////////////////////////// + /** + * The box() function renders a box. A box is an extruded rectangle. A box with equal dimension on all sides is a cube. + * Calling this function with only one parameter will create a cube. + * + * @param {int|float} w dimension of the box in the x-dimension + * @param {int|float} h dimension of the box in the y-dimension + * @param {int|float} d dimension of the box in the z-dimension + */ + Drawing2D.prototype.box = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.box = function(w, h, d) { + // user can uniformly scale the box by + // passing in only one argument. + if (!h || !d) { + h = d = w; + } + + // Modeling transformation + var model = new PMatrix3D(); + model.scale(w, h, d); + + // Viewing transformation needs to have Y flipped + // becuase that's what Processing does. + var view = new PMatrix3D(); + view.scale(1, -1, 1); + view.apply(modelView.array()); + view.transpose(); + + if (doFill) { + curContext.useProgram(programObject3D); + uniformMatrix("model3d", programObject3D, "uModel", false, model.array()); + uniformMatrix("view3d", programObject3D, "uView", false, view.array()); + // Fix stitching problems. (lines get occluded by triangles + // since they share the same depth values). This is not entirely + // working, but it's a start for drawing the outline. So + // developers can start playing around with styles. + curContext.enable(curContext.POLYGON_OFFSET_FILL); + curContext.polygonOffset(1, 1); + uniformf("color3d", programObject3D, "uColor", fillStyle); + + // Calculating the normal matrix can be expensive, so only + // do it if it's necessary. + if(lightCount > 0){ + // Create the normal transformation matrix. + var v = new PMatrix3D(); + v.set(view); + + var m = new PMatrix3D(); + m.set(model); + + v.mult(m); + + var normalMatrix = new PMatrix3D(); + normalMatrix.set(v); + normalMatrix.invert(); + normalMatrix.transpose(); + + uniformMatrix("uNormalTransform3d", programObject3D, "uNormalTransform", false, normalMatrix.array()); + vertexAttribPointer("aNormal3d", programObject3D, "aNormal", 3, boxNormBuffer); + } + else{ + disableVertexAttribPointer("aNormal3d", programObject3D, "aNormal"); + } + + vertexAttribPointer("aVertex3d", programObject3D, "aVertex", 3, boxBuffer); + + // Turn off per vertex colors. + disableVertexAttribPointer("aColor3d", programObject3D, "aColor"); + disableVertexAttribPointer("aTexture3d", programObject3D, "aTexture"); + + curContext.drawArrays(curContext.TRIANGLES, 0, boxVerts.length / 3); + curContext.disable(curContext.POLYGON_OFFSET_FILL); + } + + // Draw the box outline. + if (lineWidth > 0 && doStroke) { + curContext.useProgram(programObject2D); + uniformMatrix("uModel2d", programObject2D, "uModel", false, model.array()); + uniformMatrix("uView2d", programObject2D, "uView", false, view.array()); + uniformf("uColor2d", programObject2D, "uColor", strokeStyle); + uniformi("uIsDrawingText2d", programObject2D, "uIsDrawingText", false); + vertexAttribPointer("vertex2d", programObject2D, "aVertex", 3, boxOutlineBuffer); + disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); + curContext.drawArrays(curContext.LINES, 0, boxOutlineVerts.length / 3); + } + }; + + /** + * The initSphere() function is a helper function used by <b>sphereDetail()</b> + * This function creates and stores sphere vertices every time the user changes sphere detail. + * + * @see #sphereDetail + */ + var initSphere = function() { + var i; + sphereVerts = []; + + for (i = 0; i < sphereDetailU; i++) { + sphereVerts.push(0); + sphereVerts.push(-1); + sphereVerts.push(0); + sphereVerts.push(sphereX[i]); + sphereVerts.push(sphereY[i]); + sphereVerts.push(sphereZ[i]); + } + sphereVerts.push(0); + sphereVerts.push(-1); + sphereVerts.push(0); + sphereVerts.push(sphereX[0]); + sphereVerts.push(sphereY[0]); + sphereVerts.push(sphereZ[0]); + + var v1, v11, v2; + + // middle rings + var voff = 0; + for (i = 2; i < sphereDetailV; i++) { + v1 = v11 = voff; + voff += sphereDetailU; + v2 = voff; + for (var j = 0; j < sphereDetailU; j++) { + sphereVerts.push(sphereX[v1]); + sphereVerts.push(sphereY[v1]); + sphereVerts.push(sphereZ[v1++]); + sphereVerts.push(sphereX[v2]); + sphereVerts.push(sphereY[v2]); + sphereVerts.push(sphereZ[v2++]); + } + + // close each ring + v1 = v11; + v2 = voff; + + sphereVerts.push(sphereX[v1]); + sphereVerts.push(sphereY[v1]); + sphereVerts.push(sphereZ[v1]); + sphereVerts.push(sphereX[v2]); + sphereVerts.push(sphereY[v2]); + sphereVerts.push(sphereZ[v2]); + } + + // add the northern cap + for (i = 0; i < sphereDetailU; i++) { + v2 = voff + i; + + sphereVerts.push(sphereX[v2]); + sphereVerts.push(sphereY[v2]); + sphereVerts.push(sphereZ[v2]); + sphereVerts.push(0); + sphereVerts.push(1); + sphereVerts.push(0); + } + + sphereVerts.push(sphereX[voff]); + sphereVerts.push(sphereY[voff]); + sphereVerts.push(sphereZ[voff]); + sphereVerts.push(0); + sphereVerts.push(1); + sphereVerts.push(0); + + //set the buffer data + curContext.bindBuffer(curContext.ARRAY_BUFFER, sphereBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(sphereVerts), curContext.STATIC_DRAW); + }; + + /** + * The sphereDetail() function controls the detail used to render a sphere by adjusting the number of + * vertices of the sphere mesh. The default resolution is 30, which creates + * a fairly detailed sphere definition with vertices every 360/30 = 12 + * degrees. If you're going to render a great number of spheres per frame, + * it is advised to reduce the level of detail using this function. + * The setting stays active until <b>sphereDetail()</b> is called again with + * a new parameter and so should <i>not</i> be called prior to every + * <b>sphere()</b> statement, unless you wish to render spheres with + * different settings, e.g. using less detail for smaller spheres or ones + * further away from the camera. To control the detail of the horizontal + * and vertical resolution independently, use the version of the functions + * with two parameters. Calling this function with one parameter sets the number of segments + *(minimum of 3) used per full circle revolution. This is equivalent to calling the function with + * two identical values. + * + * @param {int} ures number of segments used horizontally (longitudinally) per full circle revolution + * @param {int} vres number of segments used vertically (latitudinally) from top to bottom + * + * @see #sphere() + */ + p.sphereDetail = function(ures, vres) { + var i; + + if (arguments.length === 1) { + ures = vres = arguments[0]; + } + + if (ures < 3) { + ures = 3; + } // force a minimum res + if (vres < 2) { + vres = 2; + } // force a minimum res + // if it hasn't changed do nothing + if ((ures === sphereDetailU) && (vres === sphereDetailV)) { + return; + } + + var delta = PConstants.SINCOS_LENGTH / ures; + var cx = new Float32Array(ures); + var cz = new Float32Array(ures); + // calc unit circle in XZ plane + for (i = 0; i < ures; i++) { + cx[i] = cosLUT[((i * delta) % PConstants.SINCOS_LENGTH) | 0]; + cz[i] = sinLUT[((i * delta) % PConstants.SINCOS_LENGTH) | 0]; + } + + // computing vertexlist + // vertexlist starts at south pole + var vertCount = ures * (vres - 1) + 2; + var currVert = 0; + + // re-init arrays to store vertices + sphereX = new Float32Array(vertCount); + sphereY = new Float32Array(vertCount); + sphereZ = new Float32Array(vertCount); + + var angle_step = (PConstants.SINCOS_LENGTH * 0.5) / vres; + var angle = angle_step; + + // step along Y axis + for (i = 1; i < vres; i++) { + var curradius = sinLUT[(angle % PConstants.SINCOS_LENGTH) | 0]; + var currY = -cosLUT[(angle % PConstants.SINCOS_LENGTH) | 0]; + for (var j = 0; j < ures; j++) { + sphereX[currVert] = cx[j] * curradius; + sphereY[currVert] = currY; + sphereZ[currVert++] = cz[j] * curradius; + } + angle += angle_step; + } + sphereDetailU = ures; + sphereDetailV = vres; + + // make the sphere verts and norms + initSphere(); + }; + + /** + * The sphere() function draws a sphere with radius r centered at coordinate 0, 0, 0. + * A sphere is a hollow ball made from tessellated triangles. + * + * @param {int|float} r the radius of the sphere + */ + Drawing2D.prototype.sphere = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.sphere = function() { + var sRad = arguments[0]; + + if ((sphereDetailU < 3) || (sphereDetailV < 2)) { + p.sphereDetail(30); + } + + // Modeling transformation. + var model = new PMatrix3D(); + model.scale(sRad, sRad, sRad); + + // viewing transformation needs to have Y flipped + // becuase that's what Processing does. + var view = new PMatrix3D(); + view.scale(1, -1, 1); + view.apply(modelView.array()); + view.transpose(); + + if (doFill) { + // Calculating the normal matrix can be expensive, so only + // do it if it's necessary. + if(lightCount > 0){ + // Create a normal transformation matrix. + var v = new PMatrix3D(); + v.set(view); + + var m = new PMatrix3D(); + m.set(model); + + v.mult(m); + + var normalMatrix = new PMatrix3D(); + normalMatrix.set(v); + normalMatrix.invert(); + normalMatrix.transpose(); + + uniformMatrix("uNormalTransform3d", programObject3D, "uNormalTransform", false, normalMatrix.array()); + vertexAttribPointer("aNormal3d", programObject3D, "aNormal", 3, sphereBuffer); + } + else{ + disableVertexAttribPointer("aNormal3d", programObject3D, "aNormal"); + } + + curContext.useProgram(programObject3D); + disableVertexAttribPointer("aTexture3d", programObject3D, "aTexture"); + + uniformMatrix("uModel3d", programObject3D, "uModel", false, model.array()); + uniformMatrix("uView3d", programObject3D, "uView", false, view.array()); + vertexAttribPointer("aVertex3d", programObject3D, "aVertex", 3, sphereBuffer); + + // Turn off per vertex colors. + disableVertexAttribPointer("aColor3d", programObject3D, "aColor"); + + // fix stitching problems. (lines get occluded by triangles + // since they share the same depth values). This is not entirely + // working, but it's a start for drawing the outline. So + // developers can start playing around with styles. + curContext.enable(curContext.POLYGON_OFFSET_FILL); + curContext.polygonOffset(1, 1); + uniformf("uColor3d", programObject3D, "uColor", fillStyle); + curContext.drawArrays(curContext.TRIANGLE_STRIP, 0, sphereVerts.length / 3); + curContext.disable(curContext.POLYGON_OFFSET_FILL); + } + + // Draw the sphere outline. + if (lineWidth > 0 && doStroke) { + curContext.useProgram(programObject2D); + uniformMatrix("uModel2d", programObject2D, "uModel", false, model.array()); + uniformMatrix("uView2d", programObject2D, "uView", false, view.array()); + vertexAttribPointer("aVertex2d", programObject2D, "aVertex", 3, sphereBuffer); + disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); + uniformf("uColor2d", programObject2D, "uColor", strokeStyle); + uniformi("uIsDrawingText", programObject2D, "uIsDrawingText", false); + curContext.drawArrays(curContext.LINE_STRIP, 0, sphereVerts.length / 3); + } + }; + + //////////////////////////////////////////////////////////////////////////// + // Coordinates + //////////////////////////////////////////////////////////////////////////// + + /** + * Returns the three-dimensional X, Y, Z position in model space. This returns + * the X value for a given coordinate based on the current set of transformations + * (scale, rotate, translate, etc.) The X value can be used to place an object + * in space relative to the location of the original point once the transformations + * are no longer in use.<br /> + * <br /> + * + * @param {int | float} x 3D x coordinate to be mapped + * @param {int | float} y 3D y coordinate to be mapped + * @param {int | float} z 3D z coordinate to be mapped + * + * @returns {float} + * + * @see modelY + * @see modelZ + */ + p.modelX = function(x, y, z) { + var mv = modelView.array(); + var ci = cameraInv.array(); + + var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3]; + var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7]; + var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11]; + var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15]; + + var ox = ci[0] * ax + ci[1] * ay + ci[2] * az + ci[3] * aw; + var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw; + + return (ow !== 0) ? ox / ow : ox; + }; + + /** + * Returns the three-dimensional X, Y, Z position in model space. This returns + * the Y value for a given coordinate based on the current set of transformations + * (scale, rotate, translate, etc.) The Y value can be used to place an object in + * space relative to the location of the original point once the transformations + * are no longer in use.<br /> + * <br /> + * + * @param {int | float} x 3D x coordinate to be mapped + * @param {int | float} y 3D y coordinate to be mapped + * @param {int | float} z 3D z coordinate to be mapped + * + * @returns {float} + * + * @see modelX + * @see modelZ + */ + p.modelY = function(x, y, z) { + var mv = modelView.array(); + var ci = cameraInv.array(); + + var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3]; + var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7]; + var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11]; + var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15]; + + var oy = ci[4] * ax + ci[5] * ay + ci[6] * az + ci[7] * aw; + var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw; + + return (ow !== 0) ? oy / ow : oy; + }; + + /** + * Returns the three-dimensional X, Y, Z position in model space. This returns + * the Z value for a given coordinate based on the current set of transformations + * (scale, rotate, translate, etc.) The Z value can be used to place an object in + * space relative to the location of the original point once the transformations + * are no longer in use. + * + * @param {int | float} x 3D x coordinate to be mapped + * @param {int | float} y 3D y coordinate to be mapped + * @param {int | float} z 3D z coordinate to be mapped + * + * @returns {float} + * + * @see modelX + * @see modelY + */ + p.modelZ = function(x, y, z) { + var mv = modelView.array(); + var ci = cameraInv.array(); + + var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3]; + var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7]; + var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11]; + var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15]; + + var oz = ci[8] * ax + ci[9] * ay + ci[10] * az + ci[11] * aw; + var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw; + + return (ow !== 0) ? oz / ow : oz; + }; + + //////////////////////////////////////////////////////////////////////////// + // Material Properties + //////////////////////////////////////////////////////////////////////////// + + /** + * Sets the ambient reflectance for shapes drawn to the screen. This is + * combined with the ambient light component of environment. The color + * components set through the parameters define the reflectance. For example in + * the default color mode, setting v1=255, v2=126, v3=0, would cause all the + * red light to reflect and half of the green light to reflect. Used in combination + * with <b>emissive()</b>, <b>specular()</b>, and <b>shininess()</b> in setting + * the materal properties of shapes. + * + * @param {int | float} gray + * + * @returns none + * + * @see emissive + * @see specular + * @see shininess + */ + Drawing2D.prototype.ambient = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.ambient = function(v1, v2, v3) { + curContext.useProgram(programObject3D); + uniformi("uUsingMat3d", programObject3D, "uUsingMat", true); + var col = p.color(v1, v2, v3); + uniformf("uMaterialAmbient3d", programObject3D, "uMaterialAmbient", p.color.toGLArray(col).slice(0, 3)); + }; + + /** + * Sets the emissive color of the material used for drawing shapes + * drawn to the screen. Used in combination with ambient(), specular(), + * and shininess() in setting the material properties of shapes. + * + * Can be called in the following ways: + * + * emissive(gray) + * @param {int | float} gray number specifying value between white and black + * + * emissive(color) + * @param {color} color any value of the color datatype + * + * emissive(v1, v2, v3) + * @param {int | float} v1 red or hue value + * @param {int | float} v2 green or saturation value + * @param {int | float} v3 blue or brightness value + * + * @returns none + * + * @see ambient + * @see specular + * @see shininess + */ + Drawing2D.prototype.emissive = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.emissive = function(v1, v2, v3) { + curContext.useProgram(programObject3D); + uniformi("uUsingMat3d", programObject3D, "uUsingMat", true); + var col = p.color(v1, v2, v3); + uniformf("uMaterialEmissive3d", programObject3D, "uMaterialEmissive", p.color.toGLArray(col).slice(0, 3)); + }; + + /** + * Sets the amount of gloss in the surface of shapes. Used in combination with + * <b>ambient()</b>, <b>specular()</b>, and <b>emissive()</b> in setting the + * material properties of shapes. + * + * @param {float} shine degree of shininess + * + * @returns none + */ + Drawing2D.prototype.shininess = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.shininess = function(shine) { + curContext.useProgram(programObject3D); + uniformi("uUsingMat3d", programObject3D, "uUsingMat", true); + uniformf("uShininess3d", programObject3D, "uShininess", shine); + }; + + /** + * Sets the specular color of the materials used for shapes drawn to the screen, + * which sets the color of hightlights. Specular refers to light which bounces + * off a surface in a perferred direction (rather than bouncing in all directions + * like a diffuse light). Used in combination with emissive(), ambient(), and + * shininess() in setting the material properties of shapes. + * + * Can be called in the following ways: + * + * specular(gray) + * @param {int | float} gray number specifying value between white and black + * + * specular(gray, alpha) + * @param {int | float} gray number specifying value between white and black + * @param {int | float} alpha opacity + * + * specular(color) + * @param {color} color any value of the color datatype + * + * specular(v1, v2, v3) + * @param {int | float} v1 red or hue value + * @param {int | float} v2 green or saturation value + * @param {int | float} v3 blue or brightness value + * + * specular(v1, v2, v3, alpha) + * @param {int | float} v1 red or hue value + * @param {int | float} v2 green or saturation value + * @param {int | float} v3 blue or brightness value + * @param {int | float} alpha opacity + * + * @returns none + * + * @see ambient + * @see emissive + * @see shininess + */ + Drawing2D.prototype.specular = DrawingShared.prototype.a3DOnlyFunction; + + Drawing3D.prototype.specular = function(v1, v2, v3) { + curContext.useProgram(programObject3D); + uniformi("uUsingMat3d", programObject3D, "uUsingMat", true); + var col = p.color(v1, v2, v3); + uniformf("uMaterialSpecular3d", programObject3D, "uMaterialSpecular", p.color.toGLArray(col).slice(0, 3)); + }; + + //////////////////////////////////////////////////////////////////////////// + // Coordinates + //////////////////////////////////////////////////////////////////////////// + + /** + * Takes a three-dimensional X, Y, Z position and returns the X value for + * where it will appear on a (two-dimensional) screen. + * + * @param {int | float} x 3D x coordinate to be mapped + * @param {int | float} y 3D y coordinate to be mapped + * @param {int | float} z 3D z optional coordinate to be mapped + * + * @returns {float} + * + * @see screenY + * @see screenZ + */ + p.screenX = function( x, y, z ) { + var mv = modelView.array(); + if( mv.length === 16 ) + { + var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3]; + var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7]; + var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11]; + var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15]; + + var pj = projection.array(); + + var ox = pj[ 0]*ax + pj[ 1]*ay + pj[ 2]*az + pj[ 3]*aw; + var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw; + + if ( ow !== 0 ){ + ox /= ow; + } + return p.width * ( 1 + ox ) / 2.0; + } + // We assume that we're in 2D + return modelView.multX(x, y); + }; + + /** + * Takes a three-dimensional X, Y, Z position and returns the Y value for + * where it will appear on a (two-dimensional) screen. + * + * @param {int | float} x 3D x coordinate to be mapped + * @param {int | float} y 3D y coordinate to be mapped + * @param {int | float} z 3D z optional coordinate to be mapped + * + * @returns {float} + * + * @see screenX + * @see screenZ + */ + p.screenY = function screenY( x, y, z ) { + var mv = modelView.array(); + if( mv.length === 16 ) { + var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3]; + var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7]; + var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11]; + var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15]; + + var pj = projection.array(); + + var oy = pj[ 4]*ax + pj[ 5]*ay + pj[ 6]*az + pj[ 7]*aw; + var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw; + + if ( ow !== 0 ){ + oy /= ow; + } + return p.height * ( 1 + oy ) / 2.0; + } + // We assume that we're in 2D + return modelView.multY(x, y); + }; + + /** + * Takes a three-dimensional X, Y, Z position and returns the Z value for + * where it will appear on a (two-dimensional) screen. + * + * @param {int | float} x 3D x coordinate to be mapped + * @param {int | float} y 3D y coordinate to be mapped + * @param {int | float} z 3D z coordinate to be mapped + * + * @returns {float} + * + * @see screenX + * @see screenY + */ + p.screenZ = function screenZ( x, y, z ) { + var mv = modelView.array(); + if( mv.length !== 16 ) { + return 0; + } + + var pj = projection.array(); + + var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3]; + var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7]; + var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11]; + var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15]; + + var oz = pj[ 8]*ax + pj[ 9]*ay + pj[10]*az + pj[11]*aw; + var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw; + + if ( ow !== 0 ) { + oz /= ow; + } + return ( oz + 1 ) / 2.0; + }; + + //////////////////////////////////////////////////////////////////////////// + // Style functions + //////////////////////////////////////////////////////////////////////////// + /** + * The fill() function sets the color used to fill shapes. For example, if you run <b>fill(204, 102, 0)</b>, all subsequent shapes will be filled with orange. + * This color is either specified in terms of the RGB or HSB color depending on the current <b>colorMode()</b> + *(the default color space is RGB, with each value in the range from 0 to 255). + * <br><br>When using hexadecimal notation to specify a color, use "#" or "0x" before the values (e.g. #CCFFAA, 0xFFCCFFAA). + * The # syntax uses six digits to specify a color (the way colors are specified in HTML and CSS). When using the hexadecimal notation starting with "0x", + * the hexadecimal value must be specified with eight characters; the first two characters define the alpha component and the remainder the red, green, and blue components. + * <br><br>The value for the parameter "gray" must be less than or equal to the current maximum value as specified by <b>colorMode()</b>. The default maximum value is 255. + * <br><br>To change the color of an image (or a texture), use tint(). + * + * @param {int|float} gray number specifying value between white and black + * @param {int|float} value1 red or hue value + * @param {int|float} value2 green or saturation value + * @param {int|float} value3 blue or brightness value + * @param {int|float} alpha opacity of the fill + * @param {Color} color any value of the color datatype + * @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) + * + * @see #noFill() + * @see #stroke() + * @see #tint() + * @see #background() + * @see #colorMode() + */ + DrawingShared.prototype.fill = function() { + var color = p.color.apply(this, arguments); + if(color === currentFillColor && doFill) { + return; + } + doFill = true; + currentFillColor = color; + }; + + Drawing2D.prototype.fill = function() { + DrawingShared.prototype.fill.apply(this, arguments); + isFillDirty = true; + }; + + Drawing3D.prototype.fill = function() { + DrawingShared.prototype.fill.apply(this, arguments); + fillStyle = p.color.toGLArray(currentFillColor); + }; + + function executeContextFill() { + if(doFill) { + if(isFillDirty) { + curContext.fillStyle = p.color.toString(currentFillColor); + isFillDirty = false; + } + curContext.fill(); + } + } + + /** + * The noFill() function disables filling geometry. If both <b>noStroke()</b> and <b>noFill()</b> + * are called, no shapes will be drawn to the screen. + * + * @see #fill() + * + */ + p.noFill = function() { + doFill = false; + }; + + /** + * The stroke() function sets the color used to draw lines and borders around shapes. This color + * is either specified in terms of the RGB or HSB color depending on the + * current <b>colorMode()</b> (the default color space is RGB, with each + * value in the range from 0 to 255). + * <br><br>When using hexadecimal notation to specify a color, use "#" or + * "0x" before the values (e.g. #CCFFAA, 0xFFCCFFAA). The # syntax uses six + * digits to specify a color (the way colors are specified in HTML and CSS). + * When using the hexadecimal notation starting with "0x", the hexadecimal + * value must be specified with eight characters; the first two characters + * define the alpha component and the remainder the red, green, and blue + * components. + * <br><br>The value for the parameter "gray" must be less than or equal + * to the current maximum value as specified by <b>colorMode()</b>. + * The default maximum value is 255. + * + * @param {int|float} gray number specifying value between white and black + * @param {int|float} value1 red or hue value + * @param {int|float} value2 green or saturation value + * @param {int|float} value3 blue or brightness value + * @param {int|float} alpha opacity of the stroke + * @param {Color} color any value of the color datatype + * @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) + * + * @see #fill() + * @see #noStroke() + * @see #tint() + * @see #background() + * @see #colorMode() + */ + DrawingShared.prototype.stroke = function() { + var color = p.color.apply(this, arguments); + if(color === currentStrokeColor && doStroke) { + return; + } + doStroke = true; + currentStrokeColor = color; + }; + + Drawing2D.prototype.stroke = function() { + DrawingShared.prototype.stroke.apply(this, arguments); + isStrokeDirty = true; + }; + + Drawing3D.prototype.stroke = function() { + DrawingShared.prototype.stroke.apply(this, arguments); + strokeStyle = p.color.toGLArray(currentStrokeColor); + }; + + function executeContextStroke() { + if(doStroke) { + if(isStrokeDirty) { + curContext.strokeStyle = p.color.toString(currentStrokeColor); + isStrokeDirty = false; + } + curContext.stroke(); + } + } + + /** + * The noStroke() function disables drawing the stroke (outline). If both <b>noStroke()</b> and + * <b>noFill()</b> are called, no shapes will be drawn to the screen. + * + * @see #stroke() + */ + p.noStroke = function() { + doStroke = false; + }; + + /** + * The strokeWeight() function sets the width of the stroke used for lines, points, and the border around shapes. + * All widths are set in units of pixels. + * + * @param {int|float} w the weight (in pixels) of the stroke + */ + DrawingShared.prototype.strokeWeight = function(w) { + lineWidth = w; + }; + + Drawing2D.prototype.strokeWeight = function(w) { + DrawingShared.prototype.strokeWeight.apply(this, arguments); + curContext.lineWidth = w; + }; + + Drawing3D.prototype.strokeWeight = function(w) { + DrawingShared.prototype.strokeWeight.apply(this, arguments); + + // Processing groups the weight of points and lines under this one function, + // but for WebGL, we need to set a uniform for points and call a function for line. + + curContext.useProgram(programObject2D); + uniformf("pointSize2d", programObject2D, "uPointSize", w); + + curContext.useProgram(programObjectUnlitShape); + uniformf("pointSizeUnlitShape", programObjectUnlitShape, "uPointSize", w); + + curContext.lineWidth(w); + }; + + /** + * The strokeCap() function sets the style for rendering line endings. These ends are either squared, extended, or rounded and + * specified with the corresponding parameters SQUARE, PROJECT, and ROUND. The default cap is ROUND. + * This function is not available with the P2D, P3D, or OPENGL renderers + * + * @param {int} value Either SQUARE, PROJECT, or ROUND + */ + p.strokeCap = function(value) { + drawing.$ensureContext().lineCap = value; + }; + + /** + * The strokeJoin() function sets the style of the joints which connect line segments. + * These joints are either mitered, beveled, or rounded and specified with the corresponding parameters MITER, BEVEL, and ROUND. The default joint is MITER. + * This function is not available with the P2D, P3D, or OPENGL renderers + * + * @param {int} value Either SQUARE, PROJECT, or ROUND + */ + p.strokeJoin = function(value) { + drawing.$ensureContext().lineJoin = value; + }; + + /** + * The smooth() function draws all geometry with smooth (anti-aliased) edges. This will slow down the frame rate of the application, + * but will enhance the visual refinement. <br/><br/> + * Note that smooth() will also improve image quality of resized images, and noSmooth() will disable image (and font) smoothing altogether. + * When working with a 3D sketch, smooth will draw points as circles rather than squares. + * + * @see #noSmooth() + * @see #hint() + * @see #size() + */ + + Drawing2D.prototype.smooth = function() { + renderSmooth = true; + var style = curElement.style; + style.setProperty("image-rendering", "optimizeQuality", "important"); + style.setProperty("-ms-interpolation-mode", "bicubic", "important"); + if (curContext.hasOwnProperty("mozImageSmoothingEnabled")) { + curContext.mozImageSmoothingEnabled = true; + } + }; + + Drawing3D.prototype.smooth = function(){ + renderSmooth = true; + }; + + /** + * The noSmooth() function draws all geometry with jagged (aliased) edges. + * + * @see #smooth() + */ + + Drawing2D.prototype.noSmooth = function() { + renderSmooth = false; + var style = curElement.style; + style.setProperty("image-rendering", "optimizeSpeed", "important"); + style.setProperty("image-rendering", "-moz-crisp-edges", "important"); + style.setProperty("image-rendering", "-webkit-optimize-contrast", "important"); + style.setProperty("image-rendering", "optimize-contrast", "important"); + style.setProperty("-ms-interpolation-mode", "nearest-neighbor", "important"); + if (curContext.hasOwnProperty("mozImageSmoothingEnabled")) { + curContext.mozImageSmoothingEnabled = false; + } + }; + + Drawing3D.prototype.noSmooth = function(){ + renderSmooth = false; + }; + + //////////////////////////////////////////////////////////////////////////// + // Vector drawing functions + //////////////////////////////////////////////////////////////////////////// + /** + * The point() function draws a point, a coordinate in space at the dimension of one pixel. + * The first parameter is the horizontal value for the point, the second + * value is the vertical value for the point, and the optional third value + * is the depth value. Drawing this shape in 3D using the <b>z</b> + * parameter requires the P3D or OPENGL parameter in combination with + * size as shown in the above example. + * + * @param {int|float} x x-coordinate of the point + * @param {int|float} y y-coordinate of the point + * @param {int|float} z z-coordinate of the point + * + * @see #beginShape() + */ + Drawing2D.prototype.point = function(x, y) { + if (!doStroke) { + return; + } + if (!renderSmooth) { + x = Math.round(x); + y = Math.round(y); + } + curContext.fillStyle = p.color.toString(currentStrokeColor); + isFillDirty = true; + // Draw a circle for any point larger than 1px + if (lineWidth > 1) { + curContext.beginPath(); + curContext.arc(x, y, lineWidth / 2, 0, PConstants.TWO_PI, false); + curContext.fill(); + } else { + curContext.fillRect(x, y, 1, 1); + } + }; + + Drawing3D.prototype.point = function(x, y, z) { + var model = new PMatrix3D(); + + // move point to position + model.translate(x, y, z || 0); + model.transpose(); + + var view = new PMatrix3D(); + view.scale(1, -1, 1); + view.apply(modelView.array()); + view.transpose(); + + curContext.useProgram(programObject2D); + uniformMatrix("uModel2d", programObject2D, "uModel", false, model.array()); + uniformMatrix("uView2d", programObject2D, "uView", false, view.array()); + + if (lineWidth > 0 && doStroke) { + // this will be replaced with the new bit shifting color code + uniformf("uColor2d", programObject2D, "uColor", strokeStyle); + uniformi("uIsDrawingText2d", programObject2D, "uIsDrawingText", false); + uniformi("uSmooth2d", programObject2D, "uSmooth", renderSmooth); + vertexAttribPointer("aVertex2d", programObject2D, "aVertex", 3, pointBuffer); + disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); + curContext.drawArrays(curContext.POINTS, 0, 1); + } + }; + + /** + * Using the <b>beginShape()</b> and <b>endShape()</b> functions allow creating more complex forms. + * <b>beginShape()</b> begins recording vertices for a shape and <b>endShape()</b> stops recording. + * The value of the <b>MODE</b> parameter tells it which types of shapes to create from the provided vertices. + * With no mode specified, the shape can be any irregular polygon. After calling the <b>beginShape()</b> function, + * a series of <b>vertex()</b> commands must follow. To stop drawing the shape, call <b>endShape()</b>. + * The <b>vertex()</b> function with two parameters specifies a position in 2D and the <b>vertex()</b> + * function with three parameters specifies a position in 3D. Each shape will be outlined with the current + * stroke color and filled with the fill color. + * + * @param {int} MODE either POINTS, LINES, TRIANGLES, TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, and QUAD_STRIP. + * + * @see endShape + * @see vertex + * @see curveVertex + * @see bezierVertex + */ + p.beginShape = function(type) { + curShape = type; + curvePoints = []; + vertArray = []; + }; + + /** + * All shapes are constructed by connecting a series of vertices. <b>vertex()</b> is used to specify the vertex + * coordinates for points, lines, triangles, quads, and polygons and is used exclusively within the <b>beginShape()</b> + * and <b>endShape()</b> function. <br /><br />Drawing a vertex in 3D using the <b>z</b> parameter requires the P3D or + * OPENGL parameter in combination with size as shown in the above example.<br /><br />This function is also used to map a + * texture onto the geometry. The <b>texture()</b> function declares the texture to apply to the geometry and the <b>u</b> + * and <b>v</b> coordinates set define the mapping of this texture to the form. By default, the coordinates used for + * <b>u</b> and <b>v</b> are specified in relation to the image's size in pixels, but this relation can be changed with + * <b>textureMode()</b>. + * + * @param {int | float} x x-coordinate of the vertex + * @param {int | float} y y-coordinate of the vertex + * @param {boolean} moveto flag to indicate whether this is a new subpath + * + * @see beginShape + * @see endShape + * @see bezierVertex + * @see curveVertex + * @see texture + */ + + Drawing2D.prototype.vertex = function(x, y, moveTo) { + var vert = []; + + if (firstVert) { firstVert = false; } + vert.isVert = true; + + vert[0] = x; + vert[1] = y; + vert[2] = 0; + vert[3] = 0; + vert[4] = 0; + + // fill and stroke color + vert[5] = currentFillColor; + vert[6] = currentStrokeColor; + + vertArray.push(vert); + if (moveTo) { + vertArray[vertArray.length-1].moveTo = moveTo; + } + }; + + Drawing3D.prototype.vertex = function(x, y, z, u, v) { + var vert = []; + + if (firstVert) { firstVert = false; } + vert.isVert = true; + + if (v === undef && usingTexture) { + v = u; + u = z; + z = 0; + } + + // Convert u and v to normalized coordinates + if (u !== undef && v !== undef) { + if (curTextureMode === PConstants.IMAGE) { + u /= curTexture.width; + v /= curTexture.height; + } + u = u > 1 ? 1 : u; + u = u < 0 ? 0 : u; + v = v > 1 ? 1 : v; + v = v < 0 ? 0 : v; + } + + vert[0] = x; + vert[1] = y; + vert[2] = z || 0; + vert[3] = u || 0; + vert[4] = v || 0; + + // fill rgba + vert[5] = fillStyle[0]; + vert[6] = fillStyle[1]; + vert[7] = fillStyle[2]; + vert[8] = fillStyle[3]; + // stroke rgba + vert[9] = strokeStyle[0]; + vert[10] = strokeStyle[1]; + vert[11] = strokeStyle[2]; + vert[12] = strokeStyle[3]; + //normals + vert[13] = normalX; + vert[14] = normalY; + vert[15] = normalZ; + + vertArray.push(vert); + }; + + /** + * @private + * Renders 3D points created from calls to vertex and beginShape/endShape + * + * @param {Array} vArray an array of vertex coordinate + * @param {Array} cArray an array of colours used for the vertices + * + * @see beginShape + * @see endShape + * @see vertex + */ + var point3D = function(vArray, cArray){ + var view = new PMatrix3D(); + view.scale(1, -1, 1); + view.apply(modelView.array()); + view.transpose(); + + curContext.useProgram(programObjectUnlitShape); + + uniformMatrix("uViewUS", programObjectUnlitShape, "uView", false, view.array()); + uniformi("uSmoothUS", programObjectUnlitShape, "uSmooth", renderSmooth); + + vertexAttribPointer("aVertexUS", programObjectUnlitShape, "aVertex", 3, pointBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW); + + vertexAttribPointer("aColorUS", programObjectUnlitShape, "aColor", 4, fillColorBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW); + + curContext.drawArrays(curContext.POINTS, 0, vArray.length/3); + }; + + /** + * @private + * Renders 3D lines created from calls to beginShape/vertex/endShape - based on the mode specified LINES, LINE_LOOP, etc. + * + * @param {Array} vArray an array of vertex coordinate + * @param {String} mode either LINES, LINE_LOOP, or LINE_STRIP + * @param {Array} cArray an array of colours used for the vertices + * + * @see beginShape + * @see endShape + * @see vertex + */ + var line3D = function(vArray, mode, cArray){ + var ctxMode; + if (mode === "LINES"){ + ctxMode = curContext.LINES; + } + else if(mode === "LINE_LOOP"){ + ctxMode = curContext.LINE_LOOP; + } + else{ + ctxMode = curContext.LINE_STRIP; + } + + var view = new PMatrix3D(); + view.scale(1, -1, 1); + view.apply(modelView.array()); + view.transpose(); + + curContext.useProgram(programObjectUnlitShape); + uniformMatrix("uViewUS", programObjectUnlitShape, "uView", false, view.array()); + vertexAttribPointer("aVertexUS", programObjectUnlitShape, "aVertex", 3, lineBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW); + vertexAttribPointer("aColorUS", programObjectUnlitShape, "aColor", 4, strokeColorBuffer); + curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW); + curContext.drawArrays(ctxMode, 0, vArray.length/3); + }; + + /** + * @private + * Render filled shapes created from calls to beginShape/vertex/endShape - based on the mode specified TRIANGLES, etc. + * + * @param {Array} vArray an array of vertex coordinate + * @param {String} mode either LINES, LINE_LOOP, or LINE_STRIP + * @param {Array} cArray an array of colours used for the vertices + * @param {Array} tArray an array of u,v coordinates for textures + * + * @see beginShape + * @see endShape + * @see vertex + */ + var fill3D = function(vArray, mode, cArray, tArray){ + var ctxMode; + if (mode === "TRIANGLES") { + ctxMode = curContext.TRIANGLES; + } else if(mode === "TRIANGLE_FAN") { + ctxMode = curContext.TRIANGLE_FAN; + } else { + ctxMode = curContext.TRIANGLE_STRIP; + } + + var view = new PMatrix3D(); + view.scale( 1, -1, 1 ); + view.apply( modelView.array() ); + view.transpose(); + + curContext.useProgram( programObject3D ); + uniformMatrix( "model3d", programObject3D, "uModel", false, [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1] ); + uniformMatrix( "view3d", programObject3D, "uView", false, view.array() ); + curContext.enable( curContext.POLYGON_OFFSET_FILL ); + curContext.polygonOffset( 1, 1 ); + uniformf( "color3d", programObject3D, "uColor", [-1,0,0,0] ); + vertexAttribPointer( "vertex3d", programObject3D, "aVertex", 3, fillBuffer ); + curContext.bufferData( curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW ); + + // if we are using a texture and a tint, then overwrite the + // contents of the color buffer with the current tint + if ( usingTexture && curTint !== null ){ + curTint3d( cArray ); + } + + vertexAttribPointer( "aColor3d", programObject3D, "aColor", 4, fillColorBuffer ); + curContext.bufferData( curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW ); + + // No support for lights....yet + disableVertexAttribPointer( "aNormal3d", programObject3D, "aNormal" ); + + if ( usingTexture ) { + uniformi( "uUsingTexture3d", programObject3D, "uUsingTexture", usingTexture ); + vertexAttribPointer( "aTexture3d", programObject3D, "aTexture", 2, shapeTexVBO ); + curContext.bufferData( curContext.ARRAY_BUFFER, new Float32Array(tArray), curContext.STREAM_DRAW ); + } + + curContext.drawArrays( ctxMode, 0, vArray.length/3 ); + curContext.disable( curContext.POLYGON_OFFSET_FILL ); + }; + + /** + * this series of three operations is used a lot in Drawing2D.prototype.endShape + * and has been split off as its own function, to tighten the code and allow for + * fewer bugs. + */ + function fillStrokeClose() { + executeContextFill(); + executeContextStroke(); + curContext.closePath(); + } + + /** + * The endShape() function is the companion to beginShape() and may only be called after beginShape(). + * When endshape() is called, all of image data defined since the previous call to beginShape() is written + * into the image buffer. + * + * @param {int} MODE Use CLOSE to close the shape + * + * @see beginShape + */ + Drawing2D.prototype.endShape = function(mode) { + // Duplicated in Drawing3D; too many variables used + if (vertArray.length === 0) { return; } + + var closeShape = mode === PConstants.CLOSE; + + // if the shape is closed, the first element is also the last element + if (closeShape) { + vertArray.push(vertArray[0]); + } + + var lineVertArray = []; + var fillVertArray = []; + var colorVertArray = []; + var strokeVertArray = []; + var texVertArray = []; + var cachedVertArray; + + firstVert = true; + var i, j, k; + var vertArrayLength = vertArray.length; + + for (i = 0; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + for (j = 0; j < 3; j++) { + fillVertArray.push(cachedVertArray[j]); + } + } + + // 5,6,7,8 + // R,G,B,A - fill colour + for (i = 0; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + for (j = 5; j < 9; j++) { + colorVertArray.push(cachedVertArray[j]); + } + } + + // 9,10,11,12 + // R, G, B, A - stroke colour + for (i = 0; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + for (j = 9; j < 13; j++) { + strokeVertArray.push(cachedVertArray[j]); + } + } + + // texture u,v + for (i = 0; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + texVertArray.push(cachedVertArray[3]); + texVertArray.push(cachedVertArray[4]); + } + + // curveVertex + if ( isCurve && (curShape === PConstants.POLYGON || curShape === undef) ) { + if (vertArrayLength > 3) { + var b = [], + s = 1 - curTightness; + curContext.beginPath(); + curContext.moveTo(vertArray[1][0], vertArray[1][1]); + /* + * Matrix to convert from Catmull-Rom to cubic Bezier + * where t = curTightness + * |0 1 0 0 | + * |(t-1)/6 1 (1-t)/6 0 | + * |0 (1-t)/6 1 (t-1)/6 | + * |0 0 0 0 | + */ + for (i = 1; (i+2) < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + b[0] = [cachedVertArray[0], cachedVertArray[1]]; + b[1] = [cachedVertArray[0] + (s * vertArray[i+1][0] - s * vertArray[i-1][0]) / 6, + cachedVertArray[1] + (s * vertArray[i+1][1] - s * vertArray[i-1][1]) / 6]; + b[2] = [vertArray[i+1][0] + (s * vertArray[i][0] - s * vertArray[i+2][0]) / 6, + vertArray[i+1][1] + (s * vertArray[i][1] - s * vertArray[i+2][1]) / 6]; + b[3] = [vertArray[i+1][0], vertArray[i+1][1]]; + curContext.bezierCurveTo(b[1][0], b[1][1], b[2][0], b[2][1], b[3][0], b[3][1]); + } + fillStrokeClose(); + } + } + + // bezierVertex + else if ( isBezier && (curShape === PConstants.POLYGON || curShape === undef) ) { + curContext.beginPath(); + for (i = 0; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + if (vertArray[i].isVert) { //if it is a vertex move to the position + if (vertArray[i].moveTo) { + curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); + } else { + curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); + } + } else { //otherwise continue drawing bezier + curContext.bezierCurveTo(vertArray[i][0], vertArray[i][1], vertArray[i][2], vertArray[i][3], vertArray[i][4], vertArray[i][5]); + } + } + fillStrokeClose(); + } + + // render the vertices provided + else { + if (curShape === PConstants.POINTS) { + for (i = 0; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + if (doStroke) { + p.stroke(cachedVertArray[6]); + } + p.point(cachedVertArray[0], cachedVertArray[1]); + } + } else if (curShape === PConstants.LINES) { + for (i = 0; (i + 1) < vertArrayLength; i+=2) { + cachedVertArray = vertArray[i]; + if (doStroke) { + p.stroke(vertArray[i+1][6]); + } + p.line(cachedVertArray[0], cachedVertArray[1], vertArray[i+1][0], vertArray[i+1][1]); + } + } else if (curShape === PConstants.TRIANGLES) { + for (i = 0; (i + 2) < vertArrayLength; i+=3) { + cachedVertArray = vertArray[i]; + curContext.beginPath(); + curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); + curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]); + curContext.lineTo(vertArray[i+2][0], vertArray[i+2][1]); + curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); + + if (doFill) { + p.fill(vertArray[i+2][5]); + executeContextFill(); + } + if (doStroke) { + p.stroke(vertArray[i+2][6]); + executeContextStroke(); + } + + curContext.closePath(); + } + } else if (curShape === PConstants.TRIANGLE_STRIP) { + for (i = 0; (i+1) < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + curContext.beginPath(); + curContext.moveTo(vertArray[i+1][0], vertArray[i+1][1]); + curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); + + if (doStroke) { + p.stroke(vertArray[i+1][6]); + } + if (doFill) { + p.fill(vertArray[i+1][5]); + } + + if (i + 2 < vertArrayLength) { + curContext.lineTo(vertArray[i+2][0], vertArray[i+2][1]); + if (doStroke) { + p.stroke(vertArray[i+2][6]); + } + if (doFill) { + p.fill(vertArray[i+2][5]); + } + } + fillStrokeClose(); + } + } else if (curShape === PConstants.TRIANGLE_FAN) { + if (vertArrayLength > 2) { + curContext.beginPath(); + curContext.moveTo(vertArray[0][0], vertArray[0][1]); + curContext.lineTo(vertArray[1][0], vertArray[1][1]); + curContext.lineTo(vertArray[2][0], vertArray[2][1]); + + if (doFill) { + p.fill(vertArray[2][5]); + executeContextFill(); + } + if (doStroke) { + p.stroke(vertArray[2][6]); + executeContextStroke(); + } + + curContext.closePath(); + for (i = 3; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + curContext.beginPath(); + curContext.moveTo(vertArray[0][0], vertArray[0][1]); + curContext.lineTo(vertArray[i-1][0], vertArray[i-1][1]); + curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); + + if (doFill) { + p.fill(cachedVertArray[5]); + executeContextFill(); + } + if (doStroke) { + p.stroke(cachedVertArray[6]); + executeContextStroke(); + } + + curContext.closePath(); + } + } + } else if (curShape === PConstants.QUADS) { + for (i = 0; (i + 3) < vertArrayLength; i+=4) { + cachedVertArray = vertArray[i]; + curContext.beginPath(); + curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); + for (j = 1; j < 4; j++) { + curContext.lineTo(vertArray[i+j][0], vertArray[i+j][1]); + } + curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); + + if (doFill) { + p.fill(vertArray[i+3][5]); + executeContextFill(); + } + if (doStroke) { + p.stroke(vertArray[i+3][6]); + executeContextStroke(); + } + + curContext.closePath(); + } + } else if (curShape === PConstants.QUAD_STRIP) { + if (vertArrayLength > 3) { + for (i = 0; (i+1) < vertArrayLength; i+=2) { + cachedVertArray = vertArray[i]; + curContext.beginPath(); + if (i+3 < vertArrayLength) { + curContext.moveTo(vertArray[i+2][0], vertArray[i+2][1]); + curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); + curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]); + curContext.lineTo(vertArray[i+3][0], vertArray[i+3][1]); + + if (doFill) { + p.fill(vertArray[i+3][5]); + } + if (doStroke) { + p.stroke(vertArray[i+3][6]); + } + } else { + curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); + curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]); + } + fillStrokeClose(); + } + } + } else { + curContext.beginPath(); + curContext.moveTo(vertArray[0][0], vertArray[0][1]); + for (i = 1; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + if (cachedVertArray.isVert) { //if it is a vertex move to the position + if (cachedVertArray.moveTo) { + curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); + } else { + curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); + } + } + } + fillStrokeClose(); + } + } + + // Reset some settings + isCurve = false; + isBezier = false; + curveVertArray = []; + curveVertCount = 0; + + // If the shape is closed, the first element was added as last element. + // We must remove it again to prevent the list of vertices from growing + // over successive calls to endShape(CLOSE) + if (closeShape) { + vertArray.pop(); + } + }; + + Drawing3D.prototype.endShape = function(mode) { + // Duplicated in Drawing3D; too many variables used + if (vertArray.length === 0) { return; } + + var closeShape = mode === PConstants.CLOSE; + var lineVertArray = []; + var fillVertArray = []; + var colorVertArray = []; + var strokeVertArray = []; + var texVertArray = []; + var cachedVertArray; + + firstVert = true; + var i, j, k; + var vertArrayLength = vertArray.length; + + for (i = 0; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + for (j = 0; j < 3; j++) { + fillVertArray.push(cachedVertArray[j]); + } + } + + // 5,6,7,8 + // R,G,B,A - fill colour + for (i = 0; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + for (j = 5; j < 9; j++) { + colorVertArray.push(cachedVertArray[j]); + } + } + + // 9,10,11,12 + // R, G, B, A - stroke colour + for (i = 0; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + for (j = 9; j < 13; j++) { + strokeVertArray.push(cachedVertArray[j]); + } + } + + // texture u,v + for (i = 0; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + texVertArray.push(cachedVertArray[3]); + texVertArray.push(cachedVertArray[4]); + } + + // if shape is closed, push the first point into the last point (including colours) + if (closeShape) { + fillVertArray.push(vertArray[0][0]); + fillVertArray.push(vertArray[0][1]); + fillVertArray.push(vertArray[0][2]); + + for (i = 5; i < 9; i++) { + colorVertArray.push(vertArray[0][i]); + } + + for (i = 9; i < 13; i++) { + strokeVertArray.push(vertArray[0][i]); + } + + texVertArray.push(vertArray[0][3]); + texVertArray.push(vertArray[0][4]); + } + // End duplication + + // curveVertex + if ( isCurve && (curShape === PConstants.POLYGON || curShape === undef) ) { + lineVertArray = fillVertArray; + if (doStroke) { + line3D(lineVertArray, null, strokeVertArray); + } + if (doFill) { + fill3D(fillVertArray, null, colorVertArray); + } + } + // bezierVertex + else if ( isBezier && (curShape === PConstants.POLYGON || curShape === undef) ) { + lineVertArray = fillVertArray; + lineVertArray.splice(lineVertArray.length - 3); + strokeVertArray.splice(strokeVertArray.length - 4); + if (doStroke) { + line3D(lineVertArray, null, strokeVertArray); + } + if (doFill) { + fill3D(fillVertArray, "TRIANGLES", colorVertArray); + } + } + + // render the vertices provided + else { + if (curShape === PConstants.POINTS) { // if POINTS was the specified parameter in beginShape + for (i = 0; i < vertArrayLength; i++) { // loop through and push the point location information to the array + cachedVertArray = vertArray[i]; + for (j = 0; j < 3; j++) { + lineVertArray.push(cachedVertArray[j]); + } + } + point3D(lineVertArray, strokeVertArray); // render function for points + } else if (curShape === PConstants.LINES) { // if LINES was the specified parameter in beginShape + for (i = 0; i < vertArrayLength; i++) { // loop through and push the point location information to the array + cachedVertArray = vertArray[i]; + for (j = 0; j < 3; j++) { + lineVertArray.push(cachedVertArray[j]); + } + } + for (i = 0; i < vertArrayLength; i++) { // loop through and push the color information to the array + cachedVertArray = vertArray[i]; + for (j = 5; j < 9; j++) { + colorVertArray.push(cachedVertArray[j]); + } + } + line3D(lineVertArray, "LINES", strokeVertArray); // render function for lines + } else if (curShape === PConstants.TRIANGLES) { // if TRIANGLES was the specified parameter in beginShape + if (vertArrayLength > 2) { + for (i = 0; (i+2) < vertArrayLength; i+=3) { // loop through the array per triangle + fillVertArray = []; + texVertArray = []; + lineVertArray = []; + colorVertArray = []; + strokeVertArray = []; + for (j = 0; j < 3; j++) { + for (k = 0; k < 3; k++) { // loop through and push + lineVertArray.push(vertArray[i+j][k]); // the line point location information + fillVertArray.push(vertArray[i+j][k]); // and fill point location information + } + } + for (j = 0; j < 3; j++) { // loop through and push the texture information + for (k = 3; k < 5; k++) { + texVertArray.push(vertArray[i+j][k]); + } + } + for (j = 0; j < 3; j++) { + for (k = 5; k < 9; k++) { // loop through and push + colorVertArray.push(vertArray[i+j][k]); // the colour information + strokeVertArray.push(vertArray[i+j][k+4]);// and the stroke information + } + } + if (doStroke) { + line3D(lineVertArray, "LINE_LOOP", strokeVertArray ); // line render function + } + if (doFill || usingTexture) { + fill3D(fillVertArray, "TRIANGLES", colorVertArray, texVertArray); // fill shape render function + } + } + } + } else if (curShape === PConstants.TRIANGLE_STRIP) { // if TRIANGLE_STRIP was the specified parameter in beginShape + if (vertArrayLength > 2) { + for (i = 0; (i+2) < vertArrayLength; i++) { + lineVertArray = []; + fillVertArray = []; + strokeVertArray = []; + colorVertArray = []; + texVertArray = []; + for (j = 0; j < 3; j++) { + for (k = 0; k < 3; k++) { + lineVertArray.push(vertArray[i+j][k]); + fillVertArray.push(vertArray[i+j][k]); + } + } + for (j = 0; j < 3; j++) { + for (k = 3; k < 5; k++) { + texVertArray.push(vertArray[i+j][k]); + } + } + for (j = 0; j < 3; j++) { + for (k = 5; k < 9; k++) { + strokeVertArray.push(vertArray[i+j][k+4]); + colorVertArray.push(vertArray[i+j][k]); + } + } + + if (doFill || usingTexture) { + fill3D(fillVertArray, "TRIANGLE_STRIP", colorVertArray, texVertArray); + } + if (doStroke) { + line3D(lineVertArray, "LINE_LOOP", strokeVertArray); + } + } + } + } else if (curShape === PConstants.TRIANGLE_FAN) { + if (vertArrayLength > 2) { + for (i = 0; i < 3; i++) { + cachedVertArray = vertArray[i]; + for (j = 0; j < 3; j++) { + lineVertArray.push(cachedVertArray[j]); + } + } + for (i = 0; i < 3; i++) { + cachedVertArray = vertArray[i]; + for (j = 9; j < 13; j++) { + strokeVertArray.push(cachedVertArray[j]); + } + } + if (doStroke) { + line3D(lineVertArray, "LINE_LOOP", strokeVertArray); + } + + for (i = 2; (i+1) < vertArrayLength; i++) { + lineVertArray = []; + strokeVertArray = []; + lineVertArray.push(vertArray[0][0]); + lineVertArray.push(vertArray[0][1]); + lineVertArray.push(vertArray[0][2]); + + strokeVertArray.push(vertArray[0][9]); + strokeVertArray.push(vertArray[0][10]); + strokeVertArray.push(vertArray[0][11]); + strokeVertArray.push(vertArray[0][12]); + + for (j = 0; j < 2; j++) { + for (k = 0; k < 3; k++) { + lineVertArray.push(vertArray[i+j][k]); + } + } + for (j = 0; j < 2; j++) { + for (k = 9; k < 13; k++) { + strokeVertArray.push(vertArray[i+j][k]); + } + } + if (doStroke) { + line3D(lineVertArray, "LINE_STRIP",strokeVertArray); + } + } + if (doFill || usingTexture) { + fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray, texVertArray); + } + } + } else if (curShape === PConstants.QUADS) { + for (i = 0; (i + 3) < vertArrayLength; i+=4) { + lineVertArray = []; + for (j = 0; j < 4; j++) { + cachedVertArray = vertArray[i+j]; + for (k = 0; k < 3; k++) { + lineVertArray.push(cachedVertArray[k]); + } + } + if (doStroke) { + line3D(lineVertArray, "LINE_LOOP",strokeVertArray); + } + + if (doFill) { + fillVertArray = []; + colorVertArray = []; + texVertArray = []; + for (j = 0; j < 3; j++) { + fillVertArray.push(vertArray[i][j]); + } + for (j = 5; j < 9; j++) { + colorVertArray.push(vertArray[i][j]); + } + + for (j = 0; j < 3; j++) { + fillVertArray.push(vertArray[i+1][j]); + } + for (j = 5; j < 9; j++) { + colorVertArray.push(vertArray[i+1][j]); + } + + for (j = 0; j < 3; j++) { + fillVertArray.push(vertArray[i+3][j]); + } + for (j = 5; j < 9; j++) { + colorVertArray.push(vertArray[i+3][j]); + } + + for (j = 0; j < 3; j++) { + fillVertArray.push(vertArray[i+2][j]); + } + for (j = 5; j < 9; j++) { + colorVertArray.push(vertArray[i+2][j]); + } + + if (usingTexture) { + texVertArray.push(vertArray[i+0][3]); + texVertArray.push(vertArray[i+0][4]); + texVertArray.push(vertArray[i+1][3]); + texVertArray.push(vertArray[i+1][4]); + texVertArray.push(vertArray[i+3][3]); + texVertArray.push(vertArray[i+3][4]); + texVertArray.push(vertArray[i+2][3]); + texVertArray.push(vertArray[i+2][4]); + } + + fill3D(fillVertArray, "TRIANGLE_STRIP", colorVertArray, texVertArray); + } + } + } else if (curShape === PConstants.QUAD_STRIP) { + var tempArray = []; + if (vertArrayLength > 3) { + for (i = 0; i < 2; i++) { + cachedVertArray = vertArray[i]; + for (j = 0; j < 3; j++) { + lineVertArray.push(cachedVertArray[j]); + } + } + + for (i = 0; i < 2; i++) { + cachedVertArray = vertArray[i]; + for (j = 9; j < 13; j++) { + strokeVertArray.push(cachedVertArray[j]); + } + } + + line3D(lineVertArray, "LINE_STRIP", strokeVertArray); + if (vertArrayLength > 4 && vertArrayLength % 2 > 0) { + tempArray = fillVertArray.splice(fillVertArray.length - 3); + vertArray.pop(); + } + for (i = 0; (i+3) < vertArrayLength; i+=2) { + lineVertArray = []; + strokeVertArray = []; + for (j = 0; j < 3; j++) { + lineVertArray.push(vertArray[i+1][j]); + } + for (j = 0; j < 3; j++) { + lineVertArray.push(vertArray[i+3][j]); + } + for (j = 0; j < 3; j++) { + lineVertArray.push(vertArray[i+2][j]); + } + for (j = 0; j < 3; j++) { + lineVertArray.push(vertArray[i+0][j]); + } + for (j = 9; j < 13; j++) { + strokeVertArray.push(vertArray[i+1][j]); + } + for (j = 9; j < 13; j++) { + strokeVertArray.push(vertArray[i+3][j]); + } + for (j = 9; j < 13; j++) { + strokeVertArray.push(vertArray[i+2][j]); + } + for (j = 9; j < 13; j++) { + strokeVertArray.push(vertArray[i+0][j]); + } + if (doStroke) { + line3D(lineVertArray, "LINE_STRIP", strokeVertArray); + } + } + + if (doFill || usingTexture) { + fill3D(fillVertArray, "TRIANGLE_LIST", colorVertArray, texVertArray); + } + } + } + // If the user didn't specify a type (LINES, TRIANGLES, etc) + else { + // If only one vertex was specified, it must be a point + if (vertArrayLength === 1) { + for (j = 0; j < 3; j++) { + lineVertArray.push(vertArray[0][j]); + } + for (j = 9; j < 13; j++) { + strokeVertArray.push(vertArray[0][j]); + } + point3D(lineVertArray,strokeVertArray); + } else { + for (i = 0; i < vertArrayLength; i++) { + cachedVertArray = vertArray[i]; + for (j = 0; j < 3; j++) { + lineVertArray.push(cachedVertArray[j]); + } + for (j = 5; j < 9; j++) { + strokeVertArray.push(cachedVertArray[j]); + } + } + if (doStroke && closeShape) { + line3D(lineVertArray, "LINE_LOOP", strokeVertArray); + } else if (doStroke && !closeShape) { + line3D(lineVertArray, "LINE_STRIP", strokeVertArray); + } + + // fill is ignored if textures are used + if (doFill || usingTexture) { + fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray, texVertArray); + } + } + } + // everytime beginShape is followed by a call to + // texture(), texturing it turned back on. We do this to + // figure out if the shape should be textured or filled + // with a color. + usingTexture = false; + curContext.useProgram(programObject3D); + uniformi("usingTexture3d", programObject3D, "uUsingTexture", usingTexture); + } + + // Reset some settings + isCurve = false; + isBezier = false; + curveVertArray = []; + curveVertCount = 0; + }; + + /** + * The function splineForward() setup forward-differencing matrix to be used for speedy + * curve rendering. It's based on using a specific number + * of curve segments and just doing incremental adds for each + * vertex of the segment, rather than running the mathematically + * expensive cubic equation. This function is used by both curveDetail and bezierDetail. + * + * @param {int} segments number of curve segments to use when drawing + * @param {PMatrix3D} matrix target object for the new matrix + */ + var splineForward = function(segments, matrix) { + var f = 1.0 / segments; + var ff = f * f; + var fff = ff * f; + + matrix.set(0, 0, 0, 1, fff, ff, f, 0, 6 * fff, 2 * ff, 0, 0, 6 * fff, 0, 0, 0); + }; + + /** + * The curveInit() function set the number of segments to use when drawing a Catmull-Rom + * curve, and setting the s parameter, which defines how tightly + * the curve fits to each vertex. Catmull-Rom curves are actually + * a subset of this curve type where the s is set to zero. + * This in an internal function used by curveDetail() and curveTightness(). + */ + var curveInit = function() { + // allocate only if/when used to save startup time + if (!curveDrawMatrix) { + curveBasisMatrix = new PMatrix3D(); + curveDrawMatrix = new PMatrix3D(); + curveInited = true; + } + + var s = curTightness; + curveBasisMatrix.set((s - 1) / 2, (s + 3) / 2, (-3 - s) / 2, (1 - s) / 2, + (1 - s), (-5 - s) / 2, (s + 2), (s - 1) / 2, + (s - 1) / 2, 0, (1 - s) / 2, 0, 0, 1, 0, 0); + + splineForward(curveDet, curveDrawMatrix); + + if (!bezierBasisInverse) { + //bezierBasisInverse = bezierBasisMatrix.get(); + //bezierBasisInverse.invert(); + curveToBezierMatrix = new PMatrix3D(); + } + + // TODO only needed for PGraphicsJava2D? if so, move it there + // actually, it's generally useful for other renderers, so keep it + // or hide the implementation elsewhere. + curveToBezierMatrix.set(curveBasisMatrix); + curveToBezierMatrix.preApply(bezierBasisInverse); + + // multiply the basis and forward diff matrices together + // saves much time since this needn't be done for each curve + curveDrawMatrix.apply(curveBasisMatrix); + }; + + /** + * Specifies vertex coordinates for Bezier curves. Each call to <b>bezierVertex()</b> defines the position of two control + * points and one anchor point of a Bezier curve, adding a new segment to a line or shape. The first time + * <b>bezierVertex()</b> is used within a <b>beginShape()</b> call, it must be prefaced with a call to <b>vertex()</b> + * to set the first anchor point. This function must be used between <b>beginShape()</b> and <b>endShape()</b> and only + * when there is no MODE parameter specified to <b>beginShape()</b>. Using the 3D version of requires rendering with P3D + * or OPENGL (see the Environment reference for more information). <br /> <br /> <b>NOTE: </b> Fill does not work properly yet. + * + * @param {float | int} cx1 The x-coordinate of 1st control point + * @param {float | int} cy1 The y-coordinate of 1st control point + * @param {float | int} cz1 The z-coordinate of 1st control point + * @param {float | int} cx2 The x-coordinate of 2nd control point + * @param {float | int} cy2 The y-coordinate of 2nd control point + * @param {float | int} cz2 The z-coordinate of 2nd control point + * @param {float | int} x The x-coordinate of the anchor point + * @param {float | int} y The y-coordinate of the anchor point + * @param {float | int} z The z-coordinate of the anchor point + * + * @see curveVertex + * @see vertex + * @see bezier + */ + Drawing2D.prototype.bezierVertex = function() { + isBezier = true; + var vert = []; + if (firstVert) { + throw ("vertex() must be used at least once before calling bezierVertex()"); + } + + for (var i = 0; i < arguments.length; i++) { + vert[i] = arguments[i]; + } + vertArray.push(vert); + vertArray[vertArray.length -1].isVert = false; + }; + + Drawing3D.prototype.bezierVertex = function() { + isBezier = true; + var vert = []; + if (firstVert) { + throw ("vertex() must be used at least once before calling bezierVertex()"); + } + + if (arguments.length === 9) { + if (bezierDrawMatrix === undef) { + bezierDrawMatrix = new PMatrix3D(); + } + // setup matrix for forward differencing to speed up drawing + var lastPoint = vertArray.length - 1; + splineForward( bezDetail, bezierDrawMatrix ); + bezierDrawMatrix.apply( bezierBasisMatrix ); + var draw = bezierDrawMatrix.array(); + var x1 = vertArray[lastPoint][0], + y1 = vertArray[lastPoint][1], + z1 = vertArray[lastPoint][2]; + var xplot1 = draw[4] * x1 + draw[5] * arguments[0] + draw[6] * arguments[3] + draw[7] * arguments[6]; + var xplot2 = draw[8] * x1 + draw[9] * arguments[0] + draw[10]* arguments[3] + draw[11]* arguments[6]; + var xplot3 = draw[12]* x1 + draw[13]* arguments[0] + draw[14]* arguments[3] + draw[15]* arguments[6]; + + var yplot1 = draw[4] * y1 + draw[5] * arguments[1] + draw[6] * arguments[4] + draw[7] * arguments[7]; + var yplot2 = draw[8] * y1 + draw[9] * arguments[1] + draw[10]* arguments[4] + draw[11]* arguments[7]; + var yplot3 = draw[12]* y1 + draw[13]* arguments[1] + draw[14]* arguments[4] + draw[15]* arguments[7]; + + var zplot1 = draw[4] * z1 + draw[5] * arguments[2] + draw[6] * arguments[5] + draw[7] * arguments[8]; + var zplot2 = draw[8] * z1 + draw[9] * arguments[2] + draw[10]* arguments[5] + draw[11]* arguments[8]; + var zplot3 = draw[12]* z1 + draw[13]* arguments[2] + draw[14]* arguments[5] + draw[15]* arguments[8]; + for (var j = 0; j < bezDetail; j++) { + x1 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + z1 += zplot1; zplot1 += zplot2; zplot2 += zplot3; + p.vertex(x1, y1, z1); + } + p.vertex(arguments[6], arguments[7], arguments[8]); + } + }; + + /** + * Sets a texture to be applied to vertex points. The <b>texture()</b> function + * must be called between <b>beginShape()</b> and <b>endShape()</b> and before + * any calls to vertex(). + * + * When textures are in use, the fill color is ignored. Instead, use tint() to + * specify the color of the texture as it is applied to the shape. + * + * @param {PImage} pimage the texture to apply + * + * @returns none + * + * @see textureMode + * @see beginShape + * @see endShape + * @see vertex + */ + p.texture = function(pimage) { + var curContext = drawing.$ensureContext(); + + if (pimage.__texture) { + curContext.bindTexture(curContext.TEXTURE_2D, pimage.__texture); + } else if (pimage.localName === "canvas") { + curContext.bindTexture(curContext.TEXTURE_2D, canTex); + curContext.texImage2D(curContext.TEXTURE_2D, 0, curContext.RGBA, curContext.RGBA, curContext.UNSIGNED_BYTE, pimage); + curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR); + curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR); + curContext.generateMipmap(curContext.TEXTURE_2D); + curTexture.width = pimage.width; + curTexture.height = pimage.height; + } else { + var texture = curContext.createTexture(), + cvs = document.createElement('canvas'), + cvsTextureCtx = cvs.getContext('2d'), + pot; + + // WebGL requires power of two textures + if (pimage.width & (pimage.width-1) === 0) { + cvs.width = pimage.width; + } else { + pot = 1; + while (pot < pimage.width) { + pot *= 2; + } + cvs.width = pot; + } + + if (pimage.height & (pimage.height-1) === 0) { + cvs.height = pimage.height; + } else { + pot = 1; + while (pot < pimage.height) { + pot *= 2; + } + cvs.height = pot; + } + + cvsTextureCtx.drawImage(pimage.sourceImg, 0, 0, pimage.width, pimage.height, 0, 0, cvs.width, cvs.height); + + curContext.bindTexture(curContext.TEXTURE_2D, texture); + curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR_MIPMAP_LINEAR); + curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR); + curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_T, curContext.CLAMP_TO_EDGE); + curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_S, curContext.CLAMP_TO_EDGE); + curContext.texImage2D(curContext.TEXTURE_2D, 0, curContext.RGBA, curContext.RGBA, curContext.UNSIGNED_BYTE, cvs); + curContext.generateMipmap(curContext.TEXTURE_2D); + + pimage.__texture = texture; + curTexture.width = pimage.width; + curTexture.height = pimage.height; + } + + usingTexture = true; + curContext.useProgram(programObject3D); + uniformi("usingTexture3d", programObject3D, "uUsingTexture", usingTexture); + }; + + /** + * Sets the coordinate space for texture mapping. There are two options, IMAGE, + * which refers to the actual coordinates of the image, and NORMALIZED, which + * refers to a normalized space of values ranging from 0 to 1. The default mode + * is IMAGE. In IMAGE, if an image is 100 x 200 pixels, mapping the image onto + * the entire size of a quad would require the points (0,0) (0,100) (100,200) (0,200). + * The same mapping in NORMAL_SPACE is (0,0) (0,1) (1,1) (0,1). + * + * @param MODE either IMAGE or NORMALIZED + * + * @returns none + * + * @see texture + */ + p.textureMode = function(mode){ + curTextureMode = mode; + }; + /** + * The curveVertexSegment() function handle emitting a specific segment of Catmull-Rom curve. Internal helper function used by <b>curveVertex()</b>. + */ + var curveVertexSegment = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) { + var x0 = x2; + var y0 = y2; + var z0 = z2; + + var draw = curveDrawMatrix.array(); + + var xplot1 = draw[4] * x1 + draw[5] * x2 + draw[6] * x3 + draw[7] * x4; + var xplot2 = draw[8] * x1 + draw[9] * x2 + draw[10] * x3 + draw[11] * x4; + var xplot3 = draw[12] * x1 + draw[13] * x2 + draw[14] * x3 + draw[15] * x4; + + var yplot1 = draw[4] * y1 + draw[5] * y2 + draw[6] * y3 + draw[7] * y4; + var yplot2 = draw[8] * y1 + draw[9] * y2 + draw[10] * y3 + draw[11] * y4; + var yplot3 = draw[12] * y1 + draw[13] * y2 + draw[14] * y3 + draw[15] * y4; + + var zplot1 = draw[4] * z1 + draw[5] * z2 + draw[6] * z3 + draw[7] * z4; + var zplot2 = draw[8] * z1 + draw[9] * z2 + draw[10] * z3 + draw[11] * z4; + var zplot3 = draw[12] * z1 + draw[13] * z2 + draw[14] * z3 + draw[15] * z4; + + p.vertex(x0, y0, z0); + for (var j = 0; j < curveDet; j++) { + x0 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y0 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + z0 += zplot1; zplot1 += zplot2; zplot2 += zplot3; + p.vertex(x0, y0, z0); + } + }; + + /** + * Specifies vertex coordinates for curves. This function may only be used between <b>beginShape()</b> and + * <b>endShape()</b> and only when there is no MODE parameter specified to <b>beginShape()</b>. The first and last points + * in a series of <b>curveVertex()</b> lines will be used to guide the beginning and end of a the curve. A minimum of four + * points is required to draw a tiny curve between the second and third points. Adding a fifth point with + * <b>curveVertex()</b> will draw the curve between the second, third, and fourth points. The <b>curveVertex()</b> function + * is an implementation of Catmull-Rom splines. Using the 3D version of requires rendering with P3D or OPENGL (see the + * Environment reference for more information). <br /> <br /><b>NOTE: </b> Fill does not work properly yet. + * + * @param {float | int} x The x-coordinate of the vertex + * @param {float | int} y The y-coordinate of the vertex + * @param {float | int} z The z-coordinate of the vertex + * + * @see curve + * @see beginShape + * @see endShape + * @see vertex + * @see bezierVertex + */ + Drawing2D.prototype.curveVertex = function(x, y) { + isCurve = true; + + p.vertex(x, y); + }; + + Drawing3D.prototype.curveVertex = function(x, y, z) { + isCurve = true; + + if (!curveInited) { + curveInit(); + } + var vert = []; + vert[0] = x; + vert[1] = y; + vert[2] = z; + curveVertArray.push(vert); + curveVertCount++; + + if (curveVertCount > 3) { + curveVertexSegment( curveVertArray[curveVertCount-4][0], + curveVertArray[curveVertCount-4][1], + curveVertArray[curveVertCount-4][2], + curveVertArray[curveVertCount-3][0], + curveVertArray[curveVertCount-3][1], + curveVertArray[curveVertCount-3][2], + curveVertArray[curveVertCount-2][0], + curveVertArray[curveVertCount-2][1], + curveVertArray[curveVertCount-2][2], + curveVertArray[curveVertCount-1][0], + curveVertArray[curveVertCount-1][1], + curveVertArray[curveVertCount-1][2] ); + } + }; + + /** + * The curve() function draws a curved line on the screen. The first and second parameters + * specify the beginning control point and the last two parameters specify + * the ending control point. The middle parameters specify the start and + * stop of the curve. Longer curves can be created by putting a series of + * <b>curve()</b> functions together or using <b>curveVertex()</b>. + * An additional function called <b>curveTightness()</b> provides control + * for the visual quality of the curve. The <b>curve()</b> function is an + * implementation of Catmull-Rom splines. Using the 3D version of requires + * rendering with P3D or OPENGL (see the Environment reference for more + * information). + * + * @param {int|float} x1 coordinates for the beginning control point + * @param {int|float} y1 coordinates for the beginning control point + * @param {int|float} z1 coordinates for the beginning control point + * @param {int|float} x2 coordinates for the first point + * @param {int|float} y2 coordinates for the first point + * @param {int|float} z2 coordinates for the first point + * @param {int|float} x3 coordinates for the second point + * @param {int|float} y3 coordinates for the second point + * @param {int|float} z3 coordinates for the second point + * @param {int|float} x4 coordinates for the ending control point + * @param {int|float} y4 coordinates for the ending control point + * @param {int|float} z4 coordinates for the ending control point + * + * @see #curveVertex() + * @see #curveTightness() + * @see #bezier() + */ + Drawing2D.prototype.curve = function(x1, y1, x2, y2, x3, y3, x4, y4) { + p.beginShape(); + p.curveVertex(x1, y1); + p.curveVertex(x2, y2); + p.curveVertex(x3, y3); + p.curveVertex(x4, y4); + p.endShape(); + }; + + Drawing3D.prototype.curve = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) { + if (z4 !== undef) { + p.beginShape(); + p.curveVertex(x1, y1, z1); + p.curveVertex(x2, y2, z2); + p.curveVertex(x3, y3, z3); + p.curveVertex(x4, y4, z4); + p.endShape(); + return; + } + p.beginShape(); + p.curveVertex(x1, y1); + p.curveVertex(z1, x2); + p.curveVertex(y2, z2); + p.curveVertex(x3, y3); + p.endShape(); + }; + + /** + * The curveTightness() function modifies the quality of forms created with <b>curve()</b> and + * <b>curveVertex()</b>. The parameter <b>squishy</b> determines how the + * curve fits to the vertex points. The value 0.0 is the default value for + * <b>squishy</b> (this value defines the curves to be Catmull-Rom splines) + * and the value 1.0 connects all the points with straight lines. + * Values within the range -5.0 and 5.0 will deform the curves but + * will leave them recognizable and as values increase in magnitude, + * they will continue to deform. + * + * @param {float} tightness amount of deformation from the original vertices + * + * @see #curve() + * @see #curveVertex() + * + */ + p.curveTightness = function(tightness) { + curTightness = tightness; + }; + + /** + * The curveDetail() function sets the resolution at which curves display. The default value is 20. + * This function is only useful when using the P3D or OPENGL renderer. + * + * @param {int} detail resolution of the curves + * + * @see curve() + * @see curveVertex() + * @see curveTightness() + */ + p.curveDetail = function(detail) { + curveDet = detail; + curveInit(); + }; + + /** + * Modifies the location from which rectangles draw. The default mode is rectMode(CORNER), which + * specifies the location to be the upper left corner of the shape and uses the third and fourth + * parameters of rect() to specify the width and height. The syntax rectMode(CORNERS) uses the + * first and second parameters of rect() to set the location of one corner and uses the third and + * fourth parameters to set the opposite corner. The syntax rectMode(CENTER) draws the image from + * its center point and uses the third and forth parameters of rect() to specify the image's width + * and height. The syntax rectMode(RADIUS) draws the image from its center point and uses the third + * and forth parameters of rect() to specify half of the image's width and height. The parameter must + * be written in ALL CAPS because Processing is a case sensitive language. Note: In version 125, the + * mode named CENTER_RADIUS was shortened to RADIUS. + * + * @param {MODE} MODE Either CORNER, CORNERS, CENTER, or RADIUS + * + * @see rect + */ + p.rectMode = function(aRectMode) { + curRectMode = aRectMode; + }; + + /** + * Modifies the location from which images draw. The default mode is imageMode(CORNER), which specifies + * the location to be the upper left corner and uses the fourth and fifth parameters of image() to set + * the image's width and height. The syntax imageMode(CORNERS) uses the second and third parameters of + * image() to set the location of one corner of the image and uses the fourth and fifth parameters to + * set the opposite corner. Use imageMode(CENTER) to draw images centered at the given x and y position. + * The parameter to imageMode() must be written in ALL CAPS because Processing is a case sensitive language. + * + * @param {MODE} MODE Either CORNER, CORNERS, or CENTER + * + * @see loadImage + * @see PImage + * @see image + * @see background + */ + p.imageMode = function(mode) { + switch (mode) { + case PConstants.CORNER: + imageModeConvert = imageModeCorner; + break; + case PConstants.CORNERS: + imageModeConvert = imageModeCorners; + break; + case PConstants.CENTER: + imageModeConvert = imageModeCenter; + break; + default: + throw "Invalid imageMode"; + } + }; + + /** + * The origin of the ellipse is modified by the ellipseMode() function. The default configuration is + * ellipseMode(CENTER), which specifies the location of the ellipse as the center of the shape. The RADIUS + * mode is the same, but the width and height parameters to ellipse() specify the radius of the ellipse, + * rather than the diameter. The CORNER mode draws the shape from the upper-left corner of its bounding box. + * The CORNERS mode uses the four parameters to ellipse() to set two opposing corners of the ellipse's bounding + * box. The parameter must be written in "ALL CAPS" because Processing is a case sensitive language. + * + * @param {MODE} MODE Either CENTER, RADIUS, CORNER, or CORNERS. + * + * @see ellipse + */ + p.ellipseMode = function(aEllipseMode) { + curEllipseMode = aEllipseMode; + }; + + /** + * The arc() function draws an arc in the display window. + * Arcs are drawn along the outer edge of an ellipse defined by the + * <b>x</b>, <b>y</b>, <b>width</b> and <b>height</b> parameters. + * The origin or the arc's ellipse may be changed with the + * <b>ellipseMode()</b> function. + * The <b>start</b> and <b>stop</b> parameters specify the angles + * at which to draw the arc. + * + * @param {float} a x-coordinate of the arc's ellipse + * @param {float} b y-coordinate of the arc's ellipse + * @param {float} c width of the arc's ellipse + * @param {float} d height of the arc's ellipse + * @param {float} start angle to start the arc, specified in radians + * @param {float} stop angle to stop the arc, specified in radians + * + * @see #ellipseMode() + * @see #ellipse() + */ + p.arc = function(x, y, width, height, start, stop) { + if (width <= 0 || stop < start) { return; } + + if (curEllipseMode === PConstants.CORNERS) { + width = width - x; + height = height - y; + } else if (curEllipseMode === PConstants.RADIUS) { + x = x - width; + y = y - height; + width = width * 2; + height = height * 2; + } else if (curEllipseMode === PConstants.CENTER) { + x = x - width/2; + y = y - height/2; + } + // make sure that we're starting at a useful point + while (start < 0) { + start += PConstants.TWO_PI; + stop += PConstants.TWO_PI; + } + if (stop - start > PConstants.TWO_PI) { + start = 0; + stop = PConstants.TWO_PI; + } + var hr = width / 2, + vr = height / 2, + centerX = x + hr, + centerY = y + vr, + step = 1/(hr+vr); + + var drawSlice = (function(x, y, start, step, stop) { + return function(p, closed, i, a, e) { + i = 0; + a = start; + e = stop + step; + p.beginShape(); + if(closed) { p.vertex(x-0.5, y-0.5); } + for (; a < e; i++, a = i*step + start) { + p.vertex( + (x + Math.cos(a) * hr)|0, + (y + Math.sin(a) * vr)|0 + ); + } + p.endShape(closed ? PConstants.CLOSE : undefined); + }; + }(centerX+0.5, centerY+0.5, start, step, stop)); + + if (doFill) { + var savedStroke = doStroke; + doStroke = false; + drawSlice(p, true); + doStroke = savedStroke; + } + + if (doStroke) { + var savedFill = doFill; + doFill = false; + drawSlice(p); + doFill = savedFill; + } + }; + + /** + * Draws a line (a direct path between two points) to the screen. The version of line() with four parameters + * draws the line in 2D. To color a line, use the stroke() function. A line cannot be filled, therefore the + * fill() method will not affect the color of a line. 2D lines are drawn with a width of one pixel by default, + * but this can be changed with the strokeWeight() function. The version with six parameters allows the line + * to be placed anywhere within XYZ space. Drawing this shape in 3D using the z parameter requires the P3D or + * OPENGL parameter in combination with size. + * + * @param {int|float} x1 x-coordinate of the first point + * @param {int|float} y1 y-coordinate of the first point + * @param {int|float} z1 z-coordinate of the first point + * @param {int|float} x2 x-coordinate of the second point + * @param {int|float} y2 y-coordinate of the second point + * @param {int|float} z2 z-coordinate of the second point + * + * @see strokeWeight + * @see strokeJoin + * @see strokeCap + * @see beginShape + */ + Drawing2D.prototype.line = function(x1, y1, x2, y2) { + if (!doStroke) { + return; + } + if (!renderSmooth) { + x1 = Math.round(x1); + x2 = Math.round(x2); + y1 = Math.round(y1); + y2 = Math.round(y2); + } + + // A line is only defined if it has different start and end coordinates. + // If they are the same, we call point instead. + if (x1 === x2 && y1 === y2) { + p.point(x1, y1); + return; + } + + var swap = undef, + lineCap = undef, + drawCrisp = true, + currentModelView = modelView.array(), + identityMatrix = [1, 0, 0, 0, 1, 0]; + // Test if any transformations have been applied to the sketch + for (var i = 0; i < 6 && drawCrisp; i++) { + drawCrisp = currentModelView[i] === identityMatrix[i]; + } + /* Draw crisp lines if the line is vertical or horizontal with the following method + * If any transformations have been applied to the sketch, don't make the line crisp + * If the line is directed up or to the left, reverse it by swapping x1/x2 or y1/y2 + * Make the line 1 pixel longer to work around cross-platform canvas implementations + * If the lineWidth is odd, translate the line by 0.5 in the perpendicular direction + * Even lineWidths do not need to be translated because the canvas will draw them on pixel boundaries + * Change the cap to butt-end to work around cross-platform canvas implementations + * Reverse the translate and lineCap canvas state changes after drawing the line + */ + if (drawCrisp) { + if (x1 === x2) { + if (y1 > y2) { + swap = y1; + y1 = y2; + y2 = swap; + } + y2++; + if (lineWidth % 2 === 1) { + curContext.translate(0.5, 0.0); + } + } else if (y1 === y2) { + if (x1 > x2) { + swap = x1; + x1 = x2; + x2 = swap; + } + x2++; + if (lineWidth % 2 === 1) { + curContext.translate(0.0, 0.5); + } + } + if (lineWidth === 1) { + lineCap = curContext.lineCap; + curContext.lineCap = 'butt'; + } + } + curContext.beginPath(); + curContext.moveTo(x1 || 0, y1 || 0); + curContext.lineTo(x2 || 0, y2 || 0); + executeContextStroke(); + if (drawCrisp) { + if (x1 === x2 && lineWidth % 2 === 1) { + curContext.translate(-0.5, 0.0); + } else if (y1 === y2 && lineWidth % 2 === 1) { + curContext.translate(0.0, -0.5); + } + if (lineWidth === 1) { + curContext.lineCap = lineCap; + } + } + }; + + Drawing3D.prototype.line = function(x1, y1, z1, x2, y2, z2) { + if (y2 === undef || z2 === undef) { // 2D line called in 3D context + z2 = 0; + y2 = x2; + x2 = z1; + z1 = 0; + } + + // a line is only defined if it has different start and end coordinates. + // If they are the same, we call point instead. + if (x1===x2 && y1===y2 && z1===z2) { + p.point(x1,y1,z1); + return; + } + + var lineVerts = [x1, y1, z1, x2, y2, z2]; + + var view = new PMatrix3D(); + view.scale(1, -1, 1); + view.apply(modelView.array()); + view.transpose(); + + if (lineWidth > 0 && doStroke) { + curContext.useProgram(programObject2D); + + uniformMatrix("uModel2d", programObject2D, "uModel", false, [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]); + uniformMatrix("uView2d", programObject2D, "uView", false, view.array()); + + uniformf("uColor2d", programObject2D, "uColor", strokeStyle); + uniformi("uIsDrawingText", programObject2D, "uIsDrawingText", false); + + vertexAttribPointer("aVertex2d", programObject2D, "aVertex", 3, lineBuffer); + disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); + + curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(lineVerts), curContext.STREAM_DRAW); + curContext.drawArrays(curContext.LINES, 0, 2); + } + }; + + /** + * Draws a Bezier curve on the screen. These curves are defined by a series of anchor and control points. The first + * two parameters specify the first anchor point and the last two parameters specify the other anchor point. The + * middle parameters specify the control points which define the shape of the curve. Bezier curves were developed + * by French engineer Pierre Bezier. Using the 3D version of requires rendering with P3D or OPENGL (see the + * Environment reference for more information). + * + * @param {int | float} x1,y1,z1 coordinates for the first anchor point + * @param {int | float} cx1,cy1,cz1 coordinates for the first control point + * @param {int | float} cx2,cy2,cz2 coordinates for the second control point + * @param {int | float} x2,y2,z2 coordinates for the second anchor point + * + * @see bezierVertex + * @see curve + */ + Drawing2D.prototype.bezier = function() { + if (arguments.length !== 8) { + throw("You must use 8 parameters for bezier() in 2D mode"); + } + + p.beginShape(); + p.vertex( arguments[0], arguments[1] ); + p.bezierVertex( arguments[2], arguments[3], + arguments[4], arguments[5], + arguments[6], arguments[7] ); + p.endShape(); + }; + + Drawing3D.prototype.bezier = function() { + if (arguments.length !== 12) { + throw("You must use 12 parameters for bezier() in 3D mode"); + } + + p.beginShape(); + p.vertex( arguments[0], arguments[1], arguments[2] ); + p.bezierVertex( arguments[3], arguments[4], arguments[5], + arguments[6], arguments[7], arguments[8], + arguments[9], arguments[10], arguments[11] ); + p.endShape(); + }; + + /** + * Sets the resolution at which Beziers display. The default value is 20. This function is only useful when using the P3D + * or OPENGL renderer as the default (JAVA2D) renderer does not use this information. + * + * @param {int} detail resolution of the curves + * + * @see curve + * @see curveVertex + * @see curveTightness + */ + p.bezierDetail = function( detail ){ + bezDetail = detail; + }; + + /** + * The bezierPoint() function evalutes quadratic bezier at point t for points a, b, c, d. + * The parameter t varies between 0 and 1. The a and d parameters are the + * on-curve points, b and c are the control points. To make a two-dimensional + * curve, call this function once with the x coordinates and a second time + * with the y coordinates to get the location of a bezier curve at t. + * + * @param {float} a coordinate of first point on the curve + * @param {float} b coordinate of first control point + * @param {float} c coordinate of second control point + * @param {float} d coordinate of second point on the curve + * @param {float} t value between 0 and 1 + * + * @see #bezier() + * @see #bezierVertex() + * @see #curvePoint() + */ + p.bezierPoint = function(a, b, c, d, t) { + return (1 - t) * (1 - t) * (1 - t) * a + 3 * (1 - t) * (1 - t) * t * b + 3 * (1 - t) * t * t * c + t * t * t * d; + }; + + /** + * The bezierTangent() function calculates the tangent of a point on a Bezier curve. There is a good + * definition of "tangent" at Wikipedia: <a href="http://en.wikipedia.org/wiki/Tangent" target="new">http://en.wikipedia.org/wiki/Tangent</a> + * + * @param {float} a coordinate of first point on the curve + * @param {float} b coordinate of first control point + * @param {float} c coordinate of second control point + * @param {float} d coordinate of second point on the curve + * @param {float} t value between 0 and 1 + * + * @see #bezier() + * @see #bezierVertex() + * @see #curvePoint() + */ + p.bezierTangent = function(a, b, c, d, t) { + return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b)); + }; + + /** + * The curvePoint() function evalutes the Catmull-Rom curve at point t for points a, b, c, d. The + * parameter t varies between 0 and 1, a and d are points on the curve, + * and b and c are the control points. This can be done once with the x + * coordinates and a second time with the y coordinates to get the + * location of a curve at t. + * + * @param {int|float} a coordinate of first point on the curve + * @param {int|float} b coordinate of second point on the curve + * @param {int|float} c coordinate of third point on the curve + * @param {int|float} d coordinate of fourth point on the curve + * @param {float} t value between 0 and 1 + * + * @see #curve() + * @see #curveVertex() + * @see #bezierPoint() + */ + p.curvePoint = function(a, b, c, d, t) { + return 0.5 * ((2 * b) + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t * t + (-a + 3 * b - 3 * c + d) * t * t * t); + }; + + /** + * The curveTangent() function calculates the tangent of a point on a Catmull-Rom curve. + * There is a good definition of "tangent" at Wikipedia: <a href="http://en.wikipedia.org/wiki/Tangent" target="new">http://en.wikipedia.org/wiki/Tangent</a>. + * + * @param {int|float} a coordinate of first point on the curve + * @param {int|float} b coordinate of first control point + * @param {int|float} c coordinate of second control point + * @param {int|float} d coordinate of second point on the curve + * @param {float} t value between 0 and 1 + * + * @see #curve() + * @see #curveVertex() + * @see #curvePoint() + * @see #bezierTangent() + */ + p.curveTangent = function(a, b, c, d, t) { + return 0.5 * ((-a + c) + 2 * (2 * a - 5 * b + 4 * c - d) * t + 3 * (-a + 3 * b - 3 * c + d) * t * t); + }; + + /** + * A triangle is a plane created by connecting three points. The first two arguments specify the first point, + * the middle two arguments specify the second point, and the last two arguments specify the third point. + * + * @param {int | float} x1 x-coordinate of the first point + * @param {int | float} y1 y-coordinate of the first point + * @param {int | float} x2 x-coordinate of the second point + * @param {int | float} y2 y-coordinate of the second point + * @param {int | float} x3 x-coordinate of the third point + * @param {int | float} y3 y-coordinate of the third point + */ + p.triangle = function(x1, y1, x2, y2, x3, y3) { + p.beginShape(PConstants.TRIANGLES); + p.vertex(x1, y1, 0); + p.vertex(x2, y2, 0); + p.vertex(x3, y3, 0); + p.endShape(); + }; + + /** + * A quad is a quadrilateral, a four sided polygon. It is similar to a rectangle, but the angles between its + * edges are not constrained to ninety degrees. The first pair of parameters (x1,y1) sets the first vertex + * and the subsequent pairs should proceed clockwise or counter-clockwise around the defined shape. + * + * @param {float | int} x1 x-coordinate of the first corner + * @param {float | int} y1 y-coordinate of the first corner + * @param {float | int} x2 x-coordinate of the second corner + * @param {float | int} y2 y-coordinate of the second corner + * @param {float | int} x3 x-coordinate of the third corner + * @param {float | int} y3 y-coordinate of the third corner + * @param {float | int} x4 x-coordinate of the fourth corner + * @param {float | int} y4 y-coordinate of the fourth corner + */ + p.quad = function(x1, y1, x2, y2, x3, y3, x4, y4) { + p.beginShape(PConstants.QUADS); + p.vertex(x1, y1, 0); + p.vertex(x2, y2, 0); + p.vertex(x3, y3, 0); + p.vertex(x4, y4, 0); + p.endShape(); + }; + + var roundedRect$2d = function(x, y, width, height, tl, tr, br, bl) { + if (bl === undef) { + tr = tl; + br = tl; + bl = tl; + } + var halfWidth = width / 2, + halfHeight = height / 2; + if (tl > halfWidth || tl > halfHeight) { + tl = Math.min(halfWidth, halfHeight); + } + if (tr > halfWidth || tr > halfHeight) { + tr = Math.min(halfWidth, halfHeight); + } + if (br > halfWidth || br > halfHeight) { + br = Math.min(halfWidth, halfHeight); + } + if (bl > halfWidth || bl > halfHeight) { + bl = Math.min(halfWidth, halfHeight); + } + // Translate the stroke by (0.5, 0.5) to draw a crisp border + if (!doFill || doStroke) { + curContext.translate(0.5, 0.5); + } + curContext.beginPath(); + curContext.moveTo(x + tl, y); + curContext.lineTo(x + width - tr, y); + curContext.quadraticCurveTo(x + width, y, x + width, y + tr); + curContext.lineTo(x + width, y + height - br); + curContext.quadraticCurveTo(x + width, y + height, x + width - br, y + height); + curContext.lineTo(x + bl, y + height); + curContext.quadraticCurveTo(x, y + height, x, y + height - bl); + curContext.lineTo(x, y + tl); + curContext.quadraticCurveTo(x, y, x + tl, y); + if (!doFill || doStroke) { + curContext.translate(-0.5, -0.5); + } + executeContextFill(); + executeContextStroke(); + }; + + /** + * Draws a rectangle to the screen. A rectangle is a four-sided shape with every angle at ninety + * degrees. The first two parameters set the location, the third sets the width, and the fourth + * sets the height. The origin is changed with the rectMode() function. + * + * @param {int|float} x x-coordinate of the rectangle + * @param {int|float} y y-coordinate of the rectangle + * @param {int|float} width width of the rectangle + * @param {int|float} height height of the rectangle + * + * @see rectMode + * @see quad + */ + Drawing2D.prototype.rect = function(x, y, width, height, tl, tr, br, bl) { + if (!width && !height) { + return; + } + + if (curRectMode === PConstants.CORNERS) { + width -= x; + height -= y; + } else if (curRectMode === PConstants.RADIUS) { + width *= 2; + height *= 2; + x -= width / 2; + y -= height / 2; + } else if (curRectMode === PConstants.CENTER) { + x -= width / 2; + y -= height / 2; + } + + if (!renderSmooth) { + x = Math.round(x); + y = Math.round(y); + width = Math.round(width); + height = Math.round(height); + } + if (tl !== undef) { + roundedRect$2d(x, y, width, height, tl, tr, br, bl); + return; + } + + // Translate the line by (0.5, 0.5) to draw a crisp rectangle border + if (doStroke && lineWidth % 2 === 1) { + curContext.translate(0.5, 0.5); + } + curContext.beginPath(); + curContext.rect(x, y, width, height); + executeContextFill(); + executeContextStroke(); + if (doStroke && lineWidth % 2 === 1) { + curContext.translate(-0.5, -0.5); + } + }; + + Drawing3D.prototype.rect = function(x, y, width, height, tl, tr, br, bl) { + if (tl !== undef) { + throw "rect() with rounded corners is not supported in 3D mode"; + } + + if (curRectMode === PConstants.CORNERS) { + width -= x; + height -= y; + } else if (curRectMode === PConstants.RADIUS) { + width *= 2; + height *= 2; + x -= width / 2; + y -= height / 2; + } else if (curRectMode === PConstants.CENTER) { + x -= width / 2; + y -= height / 2; + } + + // Modeling transformation + var model = new PMatrix3D(); + model.translate(x, y, 0); + model.scale(width, height, 1); + model.transpose(); + + // viewing transformation needs to have Y flipped + // becuase that's what Processing does. + var view = new PMatrix3D(); + view.scale(1, -1, 1); + view.apply(modelView.array()); + view.transpose(); + + if (lineWidth > 0 && doStroke) { + curContext.useProgram(programObject2D); + uniformMatrix("uModel2d", programObject2D, "uModel", false, model.array()); + uniformMatrix("uView2d", programObject2D, "uView", false, view.array()); + uniformf("uColor2d", programObject2D, "uColor", strokeStyle); + uniformi("uIsDrawingText2d", programObject2D, "uIsDrawingText", false); + vertexAttribPointer("aVertex2d", programObject2D, "aVertex", 3, rectBuffer); + disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); + curContext.drawArrays(curContext.LINE_LOOP, 0, rectVerts.length / 3); + } + + if (doFill) { + curContext.useProgram(programObject3D); + uniformMatrix("uModel3d", programObject3D, "uModel", false, model.array()); + uniformMatrix("uView3d", programObject3D, "uView", false, view.array()); + + // fix stitching problems. (lines get occluded by triangles + // since they share the same depth values). This is not entirely + // working, but it's a start for drawing the outline. So + // developers can start playing around with styles. + curContext.enable(curContext.POLYGON_OFFSET_FILL); + curContext.polygonOffset(1, 1); + + uniformf("color3d", programObject3D, "uColor", fillStyle); + + if(lightCount > 0){ + var v = new PMatrix3D(); + v.set(view); + + var m = new PMatrix3D(); + m.set(model); + + v.mult(m); + + var normalMatrix = new PMatrix3D(); + normalMatrix.set(v); + normalMatrix.invert(); + normalMatrix.transpose(); + + uniformMatrix("uNormalTransform3d", programObject3D, "uNormalTransform", false, normalMatrix.array()); + vertexAttribPointer("aNormal3d", programObject3D, "aNormal", 3, rectNormBuffer); + } + else{ + disableVertexAttribPointer("normal3d", programObject3D, "aNormal"); + } + + vertexAttribPointer("vertex3d", programObject3D, "aVertex", 3, rectBuffer); + + curContext.drawArrays(curContext.TRIANGLE_FAN, 0, rectVerts.length / 3); + curContext.disable(curContext.POLYGON_OFFSET_FILL); + } + }; + + /** + * Draws an ellipse (oval) in the display window. An ellipse with an equal <b>width</b> and <b>height</b> is a circle. + * The first two parameters set the location, the third sets the width, and the fourth sets the height. The origin may be + * changed with the <b>ellipseMode()</b> function. + * + * @param {float|int} x x-coordinate of the ellipse + * @param {float|int} y y-coordinate of the ellipse + * @param {float|int} width width of the ellipse + * @param {float|int} height height of the ellipse + * + * @see ellipseMode + */ + Drawing2D.prototype.ellipse = function(x, y, width, height) { + x = x || 0; + y = y || 0; + + if (width <= 0 && height <= 0) { + return; + } + + if (curEllipseMode === PConstants.RADIUS) { + width *= 2; + height *= 2; + } else if (curEllipseMode === PConstants.CORNERS) { + width = width - x; + height = height - y; + x += width / 2; + y += height / 2; + } else if (curEllipseMode === PConstants.CORNER) { + x += width / 2; + y += height / 2; + } + + // Shortcut for drawing a 2D circle + if (width === height) { + curContext.beginPath(); + curContext.arc(x, y, width / 2, 0, PConstants.TWO_PI, false); + executeContextFill(); + executeContextStroke(); + } else { + var w = width / 2, + h = height / 2, + C = 0.5522847498307933, + c_x = C * w, + c_y = C * h; + + p.beginShape(); + p.vertex(x + w, y); + p.bezierVertex(x + w, y - c_y, x + c_x, y - h, x, y - h); + p.bezierVertex(x - c_x, y - h, x - w, y - c_y, x - w, y); + p.bezierVertex(x - w, y + c_y, x - c_x, y + h, x, y + h); + p.bezierVertex(x + c_x, y + h, x + w, y + c_y, x + w, y); + p.endShape(); + } + }; + + Drawing3D.prototype.ellipse = function(x, y, width, height) { + x = x || 0; + y = y || 0; + + if (width <= 0 && height <= 0) { + return; + } + + if (curEllipseMode === PConstants.RADIUS) { + width *= 2; + height *= 2; + } else if (curEllipseMode === PConstants.CORNERS) { + width = width - x; + height = height - y; + x += width / 2; + y += height / 2; + } else if (curEllipseMode === PConstants.CORNER) { + x += width / 2; + y += height / 2; + } + + var w = width / 2, + h = height / 2, + C = 0.5522847498307933, + c_x = C * w, + c_y = C * h; + + p.beginShape(); + p.vertex(x + w, y); + p.bezierVertex(x + w, y - c_y, 0, x + c_x, y - h, 0, x, y - h, 0); + p.bezierVertex(x - c_x, y - h, 0, x - w, y - c_y, 0, x - w, y, 0); + p.bezierVertex(x - w, y + c_y, 0, x - c_x, y + h, 0, x, y + h, 0); + p.bezierVertex(x + c_x, y + h, 0, x + w, y + c_y, 0, x + w, y, 0); + p.endShape(); + + if (doFill) { + //temporary workaround to not working fills for bezier -- will fix later + var xAv = 0, yAv = 0, i, j; + for (i = 0; i < vertArray.length; i++) { + xAv += vertArray[i][0]; + yAv += vertArray[i][1]; + } + xAv /= vertArray.length; + yAv /= vertArray.length; + var vert = [], + fillVertArray = [], + colorVertArray = []; + vert[0] = xAv; + vert[1] = yAv; + vert[2] = 0; + vert[3] = 0; + vert[4] = 0; + vert[5] = fillStyle[0]; + vert[6] = fillStyle[1]; + vert[7] = fillStyle[2]; + vert[8] = fillStyle[3]; + vert[9] = strokeStyle[0]; + vert[10] = strokeStyle[1]; + vert[11] = strokeStyle[2]; + vert[12] = strokeStyle[3]; + vert[13] = normalX; + vert[14] = normalY; + vert[15] = normalZ; + vertArray.unshift(vert); + for (i = 0; i < vertArray.length; i++) { + for (j = 0; j < 3; j++) { + fillVertArray.push(vertArray[i][j]); + } + for (j = 5; j < 9; j++) { + colorVertArray.push(vertArray[i][j]); + } + } + fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray); + } + }; + + /** + * Sets the current normal vector. This is for drawing three dimensional shapes and surfaces and + * specifies a vector perpendicular to the surface of the shape which determines how lighting affects + * it. Processing attempts to automatically assign normals to shapes, but since that's imperfect, + * this is a better option when you want more control. This function is identical to glNormal3f() in OpenGL. + * + * @param {float} nx x direction + * @param {float} ny y direction + * @param {float} nz z direction + * + * @see beginShape + * @see endShape + * @see lights + */ + p.normal = function(nx, ny, nz) { + if (arguments.length !== 3 || !(typeof nx === "number" && typeof ny === "number" && typeof nz === "number")) { + throw "normal() requires three numeric arguments."; + } + + normalX = nx; + normalY = ny; + normalZ = nz; + + if (curShape !== 0) { + if (normalMode === PConstants.NORMAL_MODE_AUTO) { + normalMode = PConstants.NORMAL_MODE_SHAPE; + } else if (normalMode === PConstants.NORMAL_MODE_SHAPE) { + normalMode = PConstants.NORMAL_MODE_VERTEX; + } + } + }; + + //////////////////////////////////////////////////////////////////////////// + // Raster drawing functions + //////////////////////////////////////////////////////////////////////////// + + /** + * Saves an image from the display window. Images are saved in TIFF, TARGA, JPEG, and PNG format + * depending on the extension within the filename parameter. For example, "image.tif" will have + * a TIFF image and "image.png" will save a PNG image. If no extension is included in the filename, + * the image will save in TIFF format and .tif will be added to the name. These files are saved to + * the sketch's folder, which may be opened by selecting "Show sketch folder" from the "Sketch" menu. + * It is not possible to use save() while running the program in a web browser. All images saved + * from the main drawing window will be opaque. To save images without a background, use createGraphics(). + * + * @param {String} filename any sequence of letters and numbers + * + * @see saveFrame + * @see createGraphics + */ + p.save = function(file, img) { + // file is unused at the moment + // may implement this differently in later release + if (img !== undef) { + return window.open(img.toDataURL(),"_blank"); + } + return window.open(p.externals.canvas.toDataURL(),"_blank"); + }; + + var saveNumber = 0; + + p.saveFrame = function(file) { + if(file === undef) { + // use default name template if parameter is not specified + file = "screen-####.png"; + } + // Increment changeable part: screen-0000.png, screen-0001.png, ... + var frameFilename = file.replace(/#+/, function(all) { + var s = "" + (saveNumber++); + while(s.length < all.length) { + s = "0" + s; + } + return s; + }); + p.save(frameFilename); + }; + + var utilityContext2d = document.createElement("canvas").getContext("2d"); + + var canvasDataCache = [undef, undef, undef]; // we need three for now + + function getCanvasData(obj, w, h) { + var canvasData = canvasDataCache.shift(); + + if (canvasData === undef) { + canvasData = {}; + canvasData.canvas = document.createElement("canvas"); + canvasData.context = canvasData.canvas.getContext('2d'); + } + + canvasDataCache.push(canvasData); + + var canvas = canvasData.canvas, context = canvasData.context, + width = w || obj.width, height = h || obj.height; + + canvas.width = width; + canvas.height = height; + + if (!obj) { + context.clearRect(0, 0, width, height); + } else if ("data" in obj) { // ImageData + context.putImageData(obj, 0, 0); + } else { + context.clearRect(0, 0, width, height); + context.drawImage(obj, 0, 0, width, height); + } + return canvasData; + } + + /** + * Handle the sketch code for pixels[] and pixels.length + * parser code converts pixels[] to getPixels() + * or setPixels(), .length becomes getLength() + */ + function buildPixelsObject(pImage) { + return { + + getLength: (function(aImg) { + return function() { + if (aImg.isRemote) { + throw "Image is loaded remotely. Cannot get length."; + } else { + return aImg.imageData.data.length ? aImg.imageData.data.length/4 : 0; + } + }; + }(pImage)), + + getPixel: (function(aImg) { + return function(i) { + var offset = i*4, + data = aImg.imageData.data; + + if (aImg.isRemote) { + throw "Image is loaded remotely. Cannot get pixels."; + } + + return (data[offset+3] << 24) & PConstants.ALPHA_MASK | + (data[offset] << 16) & PConstants.RED_MASK | + (data[offset+1] << 8) & PConstants.GREEN_MASK | + data[offset+2] & PConstants.BLUE_MASK; + }; + }(pImage)), + + setPixel: (function(aImg) { + return function(i, c) { + var offset = i*4, + data = aImg.imageData.data; + + if (aImg.isRemote) { + throw "Image is loaded remotely. Cannot set pixel."; + } + + data[offset+0] = (c & PConstants.RED_MASK) >>> 16; + data[offset+1] = (c & PConstants.GREEN_MASK) >>> 8; + data[offset+2] = (c & PConstants.BLUE_MASK); + data[offset+3] = (c & PConstants.ALPHA_MASK) >>> 24; + aImg.__isDirty = true; + }; + }(pImage)), + + toArray: (function(aImg) { + return function() { + var arr = [], + data = aImg.imageData.data, + length = aImg.width * aImg.height; + + if (aImg.isRemote) { + throw "Image is loaded remotely. Cannot get pixels."; + } + + for (var i = 0, offset = 0; i < length; i++, offset += 4) { + arr.push( (data[offset+3] << 24) & PConstants.ALPHA_MASK | + (data[offset] << 16) & PConstants.RED_MASK | + (data[offset+1] << 8) & PConstants.GREEN_MASK | + data[offset+2] & PConstants.BLUE_MASK ); + } + return arr; + }; + }(pImage)), + + set: (function(aImg) { + return function(arr) { + var offset, + data, + c; + if (this.isRemote) { + throw "Image is loaded remotely. Cannot set pixels."; + } + + data = aImg.imageData.data; + for (var i = 0, aL = arr.length; i < aL; i++) { + c = arr[i]; + offset = i*4; + + data[offset+0] = (c & PConstants.RED_MASK) >>> 16; + data[offset+1] = (c & PConstants.GREEN_MASK) >>> 8; + data[offset+2] = (c & PConstants.BLUE_MASK); + data[offset+3] = (c & PConstants.ALPHA_MASK) >>> 24; + } + aImg.__isDirty = true; + }; + }(pImage)) + + }; + } + + /** + * Datatype for storing images. Processing can display .gif, .jpg, .tga, and .png images. Images may be + * displayed in 2D and 3D space. Before an image is used, it must be loaded with the loadImage() function. + * The PImage object contains fields for the width and height of the image, as well as an array called + * pixels[] which contains the values for every pixel in the image. A group of methods, described below, + * allow easy access to the image's pixels and alpha channel and simplify the process of compositing. + * Before using the pixels[] array, be sure to use the loadPixels() method on the image to make sure that the + * pixel data is properly loaded. To create a new image, use the createImage() function (do not use new PImage()). + * + * @param {int} width image width + * @param {int} height image height + * @param {MODE} format Either RGB, ARGB, ALPHA (grayscale alpha channel) + * + * @returns {PImage} + * + * @see loadImage + * @see imageMode + * @see createImage + */ + var PImage = function(aWidth, aHeight, aFormat) { + + // Keep track of whether or not the cached imageData has been touched. + this.__isDirty = false; + + if (aWidth instanceof HTMLImageElement) { + // convert an <img> to a PImage + this.fromHTMLImageData(aWidth); + } else if (aHeight || aFormat) { + this.width = aWidth || 1; + this.height = aHeight || 1; + + // Stuff a canvas into sourceImg so image() calls can use drawImage like an <img> + var canvas = this.sourceImg = document.createElement("canvas"); + canvas.width = this.width; + canvas.height = this.height; + + var imageData = this.imageData = canvas.getContext('2d').createImageData(this.width, this.height); + this.format = (aFormat === PConstants.ARGB || aFormat === PConstants.ALPHA) ? aFormat : PConstants.RGB; + if (this.format === PConstants.RGB) { + // Set the alpha channel of an RGB image to opaque. + for (var i = 3, data = this.imageData.data, len = data.length; i < len; i += 4) { + data[i] = 255; + } + } + + this.__isDirty = true; + this.updatePixels(); + } else { + this.width = 0; + this.height = 0; + this.imageData = utilityContext2d.createImageData(1, 1); + this.format = PConstants.ARGB; + } + + this.pixels = buildPixelsObject(this); + }; + PImage.prototype = { + + /** + * Temporary hack to deal with cross-Processing-instance created PImage. See + * tickets #1623 and #1644. + */ + __isPImage: true, + + /** + * @member PImage + * Updates the image with the data in its pixels[] array. Use in conjunction with loadPixels(). If + * you're only reading pixels from the array, there's no need to call updatePixels(). + * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the rule + * is that any time you want to manipulate the pixels[] array, you must first call loadPixels(), and + * after changes have been made, call updatePixels(). Even if the renderer may not seem to use this + * function in the current Processing release, this will always be subject to change. + * Currently, none of the renderers use the additional parameters to updatePixels(). + */ + updatePixels: function() { + var canvas = this.sourceImg; + if (canvas && canvas instanceof HTMLCanvasElement && this.__isDirty) { + canvas.getContext('2d').putImageData(this.imageData, 0, 0); + } + this.__isDirty = false; + }, + + fromHTMLImageData: function(htmlImg) { + // convert an <img> to a PImage + var canvasData = getCanvasData(htmlImg); + try { + var imageData = canvasData.context.getImageData(0, 0, htmlImg.width, htmlImg.height); + this.fromImageData(imageData); + } catch(e) { + if (htmlImg.width && htmlImg.height) { + this.isRemote = true; + this.width = htmlImg.width; + this.height = htmlImg.height; + } + } + this.sourceImg = htmlImg; + }, + + 'get': function(x, y, w, h) { + if (!arguments.length) { + return p.get(this); + } + if (arguments.length === 2) { + return p.get(x, y, this); + } + if (arguments.length === 4) { + return p.get(x, y, w, h, this); + } + }, + + /** + * @member PImage + * Changes the color of any pixel or writes an image directly into the image. The x and y parameter + * specify the pixel or the upper-left corner of the image. The color parameter specifies the color value. + * Setting the color of a single pixel with set(x, y) is easy, but not as fast as putting the data + * directly into pixels[]. The equivalent statement to "set(x, y, #000000)" using pixels[] is + * "pixels[y*width+x] = #000000". Processing requires calling loadPixels() to load the display window + * data into the pixels[] array before getting the values and calling updatePixels() to update the window. + * + * @param {int} x x-coordinate of the pixel or upper-left corner of the image + * @param {int} y y-coordinate of the pixel or upper-left corner of the image + * @param {color} color any value of the color datatype + * + * @see get + * @see pixels[] + * @see copy + */ + 'set': function(x, y, c) { + p.set(x, y, c, this); + this.__isDirty = true; + }, + + /** + * @member PImage + * Blends a region of pixels into the image specified by the img parameter. These copies utilize full + * alpha channel support and a choice of the following modes to blend the colors of source pixels (A) + * with the ones of pixels in the destination image (B): + * BLEND - linear interpolation of colours: C = A*factor + B + * ADD - additive blending with white clip: C = min(A*factor + B, 255) + * SUBTRACT - subtractive blending with black clip: C = max(B - A*factor, 0) + * DARKEST - only the darkest colour succeeds: C = min(A*factor, B) + * LIGHTEST - only the lightest colour succeeds: C = max(A*factor, B) + * DIFFERENCE - subtract colors from underlying image. + * EXCLUSION - similar to DIFFERENCE, but less extreme. + * MULTIPLY - Multiply the colors, result will always be darker. + * SCREEN - Opposite multiply, uses inverse values of the colors. + * OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values, and screens light values. + * HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower. + * SOFT_LIGHT - Mix of DARKEST and LIGHTEST. Works like OVERLAY, but not as harsh. + * DODGE - Lightens light tones and increases contrast, ignores darks. Called "Color Dodge" in Illustrator and Photoshop. + * BURN - Darker areas are applied, increasing contrast, ignores lights. Called "Color Burn" in Illustrator and Photoshop. + * All modes use the alpha information (highest byte) of source image pixels as the blending factor. + * If the source and destination regions are different sizes, the image will be automatically resized to + * match the destination size. If the srcImg parameter is not used, the display window is used as the source image. + * This function ignores imageMode(). + * + * @param {int} x X coordinate of the source's upper left corner + * @param {int} y Y coordinate of the source's upper left corner + * @param {int} width source image width + * @param {int} height source image height + * @param {int} dx X coordinate of the destinations's upper left corner + * @param {int} dy Y coordinate of the destinations's upper left corner + * @param {int} dwidth destination image width + * @param {int} dheight destination image height + * @param {PImage} srcImg an image variable referring to the source image + * @param {MODE} MODE Either BLEND, ADD, SUBTRACT, LIGHTEST, DARKEST, DIFFERENCE, EXCLUSION, + * MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN + * + * @see alpha + * @see copy + */ + blend: function(srcImg, x, y, width, height, dx, dy, dwidth, dheight, MODE) { + if (arguments.length === 9) { + p.blend(this, srcImg, x, y, width, height, dx, dy, dwidth, dheight, this); + } else if (arguments.length === 10) { + p.blend(srcImg, x, y, width, height, dx, dy, dwidth, dheight, MODE, this); + } + delete this.sourceImg; + }, + + /** + * @member PImage + * Copies a region of pixels from one image into another. If the source and destination regions + * aren't the same size, it will automatically resize source pixels to fit the specified target region. + * No alpha information is used in the process, however if the source image has an alpha channel set, + * it will be copied as well. This function ignores imageMode(). + * + * @param {int} sx X coordinate of the source's upper left corner + * @param {int} sy Y coordinate of the source's upper left corner + * @param {int} swidth source image width + * @param {int} sheight source image height + * @param {int} dx X coordinate of the destinations's upper left corner + * @param {int} dy Y coordinate of the destinations's upper left corner + * @param {int} dwidth destination image width + * @param {int} dheight destination image height + * @param {PImage} srcImg an image variable referring to the source image + * + * @see alpha + * @see blend + */ + copy: function(srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, dheight) { + if (arguments.length === 8) { + p.blend(this, srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, PConstants.REPLACE, this); + } else if (arguments.length === 9) { + p.blend(srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, dheight, PConstants.REPLACE, this); + } + delete this.sourceImg; + }, + + /** + * @member PImage + * Filters an image as defined by one of the following modes: + * THRESHOLD - converts the image to black and white pixels depending if they are above or below + * the threshold defined by the level parameter. The level must be between 0.0 (black) and 1.0(white). + * If no level is specified, 0.5 is used. + * GRAY - converts any colors in the image to grayscale equivalents + * INVERT - sets each pixel to its inverse value + * POSTERIZE - limits each channel of the image to the number of colors specified as the level parameter + * BLUR - executes a Guassian blur with the level parameter specifying the extent of the blurring. + * If no level parameter is used, the blur is equivalent to Guassian blur of radius 1. + * OPAQUE - sets the alpha channel to entirely opaque. + * ERODE - reduces the light areas with the amount defined by the level parameter. + * DILATE - increases the light areas with the amount defined by the level parameter + * + * @param {MODE} MODE Either THRESHOLD, GRAY, INVERT, POSTERIZE, BLUR, OPAQUE, ERODE, or DILATE + * @param {int|float} param in the range from 0 to 1 + */ + filter: function(mode, param) { + if (arguments.length === 2) { + p.filter(mode, param, this); + } else if (arguments.length === 1) { + // no param specified, send null to show its invalid + p.filter(mode, null, this); + } + delete this.sourceImg; + }, + + /** + * @member PImage + * Saves the image into a file. Images are saved in TIFF, TARGA, JPEG, and PNG format depending on + * the extension within the filename parameter. For example, "image.tif" will have a TIFF image and + * "image.png" will save a PNG image. If no extension is included in the filename, the image will save + * in TIFF format and .tif will be added to the name. These files are saved to the sketch's folder, + * which may be opened by selecting "Show sketch folder" from the "Sketch" menu. It is not possible to + * use save() while running the program in a web browser. + * To save an image created within the code, rather than through loading, it's necessary to make the + * image with the createImage() function so it is aware of the location of the program and can therefore + * save the file to the right place. See the createImage() reference for more information. + * + * @param {String} filename a sequence of letters and numbers + */ + save: function(file){ + p.save(file,this); + }, + + /** + * @member PImage + * Resize the image to a new width and height. To make the image scale proportionally, use 0 as the + * value for the wide or high parameter. + * + * @param {int} wide the resized image width + * @param {int} high the resized image height + * + * @see get + */ + resize: function(w, h) { + if (this.isRemote) { // Remote images cannot access imageData + throw "Image is loaded remotely. Cannot resize."; + } + if (this.width !== 0 || this.height !== 0) { + // make aspect ratio if w or h is 0 + if (w === 0 && h !== 0) { + w = Math.floor(this.width / this.height * h); + } else if (h === 0 && w !== 0) { + h = Math.floor(this.height / this.width * w); + } + // put 'this.imageData' into a new canvas + var canvas = getCanvasData(this.imageData).canvas; + // pull imageData object out of canvas into ImageData object + var imageData = getCanvasData(canvas, w, h).context.getImageData(0, 0, w, h); + // set this as new pimage + this.fromImageData(imageData); + } + }, + + /** + * @member PImage + * Masks part of an image from displaying by loading another image and using it as an alpha channel. + * This mask image should only contain grayscale data, but only the blue color channel is used. The + * mask image needs to be the same size as the image to which it is applied. + * In addition to using a mask image, an integer array containing the alpha channel data can be + * specified directly. This method is useful for creating dynamically generated alpha masks. This + * array must be of the same length as the target image's pixels array and should contain only grayscale + * data of values between 0-255. + * + * @param {PImage} maskImg any PImage object used as the alpha channel for "img", needs to be same + * size as "img" + * @param {int[]} maskArray any array of Integer numbers used as the alpha channel, needs to be same + * length as the image's pixel array + */ + mask: function(mask) { + var obj = this.toImageData(), + i, + size; + + if (mask instanceof PImage || mask.__isPImage) { + if (mask.width === this.width && mask.height === this.height) { + mask = mask.toImageData(); + + for (i = 2, size = this.width * this.height * 4; i < size; i += 4) { + // using it as an alpha channel + obj.data[i + 1] = mask.data[i]; + // but only the blue color channel + } + } else { + throw "mask must have the same dimensions as PImage."; + } + } else if (mask instanceof Array) { + if (this.width * this.height === mask.length) { + for (i = 0, size = mask.length; i < size; ++i) { + obj.data[i * 4 + 3] = mask[i]; + } + } else { + throw "mask array must be the same length as PImage pixels array."; + } + } + + this.fromImageData(obj); + }, + + // These are intentionally left blank for PImages, we work live with pixels and draw as necessary + /** + * @member PImage + * Loads the pixel data for the image into its pixels[] array. This function must always be called + * before reading from or writing to pixels[]. + * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the + * rule is that any time you want to manipulate the pixels[] array, you must first call loadPixels(), + * and after changes have been made, call updatePixels(). Even if the renderer may not seem to use + * this function in the current Processing release, this will always be subject to change. + */ + loadPixels: noop, + + toImageData: function() { + if (this.isRemote) { + return this.sourceImg; + } + + if (!this.__isDirty) { + return this.imageData; + } + + var canvasData = getCanvasData(this.sourceImg); + return canvasData.context.getImageData(0, 0, this.width, this.height); + }, + + toDataURL: function() { + if (this.isRemote) { // Remote images cannot access imageData + throw "Image is loaded remotely. Cannot create dataURI."; + } + var canvasData = getCanvasData(this.imageData); + return canvasData.canvas.toDataURL(); + }, + + fromImageData: function(canvasImg) { + var w = canvasImg.width, + h = canvasImg.height, + canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'); + + this.width = canvas.width = w; + this.height = canvas.height = h; + + ctx.putImageData(canvasImg, 0, 0); + + // changed for 0.9 + this.format = PConstants.ARGB; + + this.imageData = canvasImg; + this.sourceImg = canvas; + } + }; + + p.PImage = PImage; + + /** + * Creates a new PImage (the datatype for storing images). This provides a fresh buffer of pixels to play + * with. Set the size of the buffer with the width and height parameters. The format parameter defines how + * the pixels are stored. See the PImage reference for more information. + * Be sure to include all three parameters, specifying only the width and height (but no format) will + * produce a strange error. + * Advanced users please note that createImage() should be used instead of the syntax new PImage(). + * + * @param {int} width image width + * @param {int} height image height + * @param {MODE} format Either RGB, ARGB, ALPHA (grayscale alpha channel) + * + * @returns {PImage} + * + * @see PImage + * @see PGraphics + */ + p.createImage = function(w, h, mode) { + return new PImage(w,h,mode); + }; + + // Loads an image for display. Type is an extension. Callback is fired on load. + /** + * Loads an image into a variable of type PImage. Four types of images ( .gif, .jpg, .tga, .png) images may + * be loaded. To load correctly, images must be located in the data directory of the current sketch. In most + * cases, load all images in setup() to preload them at the start of the program. Loading images inside draw() + * will reduce the speed of a program. + * The filename parameter can also be a URL to a file found online. For security reasons, a Processing sketch + * found online can only download files from the same server from which it came. Getting around this restriction + * requires a signed applet. + * The extension parameter is used to determine the image type in cases where the image filename does not end + * with a proper extension. Specify the extension as the second parameter to loadImage(), as shown in the + * third example on this page. + * If an image is not loaded successfully, the null value is returned and an error message will be printed to + * the console. The error message does not halt the program, however the null value may cause a NullPointerException + * if your code does not check whether the value returned from loadImage() is null. + * Depending on the type of error, a PImage object may still be returned, but the width and height of the image + * will be set to -1. This happens if bad image data is returned or cannot be decoded properly. Sometimes this happens + * with image URLs that produce a 403 error or that redirect to a password prompt, because loadImage() will attempt + * to interpret the HTML as image data. + * + * @param {String} filename name of file to load, can be .gif, .jpg, .tga, or a handful of other image + * types depending on your platform. + * @param {String} extension the type of image to load, for example "png", "gif", "jpg" + * + * @returns {PImage} + * + * @see PImage + * @see image + * @see imageMode + * @see background + */ + p.loadImage = function(file, type, callback) { + // if type is specified, we just ignore it + + var pimg; + // if image is in the preloader cache return a new PImage + if (curSketch.imageCache.images[file]) { + pimg = new PImage(curSketch.imageCache.images[file]); + pimg.loaded = true; + return pimg; + } + // else async load it + pimg = new PImage(); + var img = document.createElement('img'); + + pimg.sourceImg = img; + + img.onload = (function(aImage, aPImage, aCallback) { + var image = aImage; + var pimg = aPImage; + var callback = aCallback; + return function() { + // change the <img> object into a PImage now that its loaded + pimg.fromHTMLImageData(image); + pimg.loaded = true; + if (callback) { + callback(); + } + }; + }(img, pimg, callback)); + + img.src = file; // needs to be called after the img.onload function is declared or it wont work in opera + return pimg; + }; + + // async loading of large images, same functionality as loadImage above + /** + * This function load images on a separate thread so that your sketch does not freeze while images load during + * setup(). While the image is loading, its width and height will be 0. If an error occurs while loading the image, + * its width and height will be set to -1. You'll know when the image has loaded properly because its width and + * height will be greater than 0. Asynchronous image loading (particularly when downloading from a server) can + * dramatically improve performance. + * The extension parameter is used to determine the image type in cases where the image filename does not end + * with a proper extension. Specify the extension as the second parameter to requestImage(). + * + * @param {String} filename name of file to load, can be .gif, .jpg, .tga, or a handful of other image + * types depending on your platform. + * @param {String} extension the type of image to load, for example "png", "gif", "jpg" + * + * @returns {PImage} + * + * @see PImage + * @see loadImage + */ + p.requestImage = p.loadImage; + + function get$2(x,y) { + var data; + // return the color at x,y (int) of curContext + if (x >= p.width || x < 0 || y < 0 || y >= p.height) { + // x,y is outside image return transparent black + return 0; + } + + // loadPixels() has been called + if (isContextReplaced) { + var offset = ((0|x) + p.width * (0|y)) * 4; + data = p.imageData.data; + return (data[offset + 3] << 24) & PConstants.ALPHA_MASK | + (data[offset] << 16) & PConstants.RED_MASK | + (data[offset + 1] << 8) & PConstants.GREEN_MASK | + data[offset + 2] & PConstants.BLUE_MASK; + } + + // x,y is inside canvas space + data = p.toImageData(0|x, 0|y, 1, 1).data; + return (data[3] << 24) & PConstants.ALPHA_MASK | + (data[0] << 16) & PConstants.RED_MASK | + (data[1] << 8) & PConstants.GREEN_MASK | + data[2] & PConstants.BLUE_MASK; + } + function get$3(x,y,img) { + if (img.isRemote) { // Remote images cannot access imageData + throw "Image is loaded remotely. Cannot get x,y."; + } + // PImage.get(x,y) was called, return the color (int) at x,y of img + var offset = y * img.width * 4 + (x * 4), + data = img.imageData.data; + return (data[offset + 3] << 24) & PConstants.ALPHA_MASK | + (data[offset] << 16) & PConstants.RED_MASK | + (data[offset + 1] << 8) & PConstants.GREEN_MASK | + data[offset + 2] & PConstants.BLUE_MASK; + } + function get$4(x, y, w, h) { + // return a PImage of w and h from cood x,y of curContext + var c = new PImage(w, h, PConstants.ARGB); + c.fromImageData(p.toImageData(x, y, w, h)); + return c; + } + function get$5(x, y, w, h, img) { + if (img.isRemote) { // Remote images cannot access imageData + throw "Image is loaded remotely. Cannot get x,y,w,h."; + } + // PImage.get(x,y,w,h) was called, return x,y,w,h PImage of img + // offset start point needs to be *4 + var c = new PImage(w, h, PConstants.ARGB), cData = c.imageData.data, + imgWidth = img.width, imgHeight = img.height, imgData = img.imageData.data; + // Don't need to copy pixels from the image outside ranges. + var startRow = Math.max(0, -y), startColumn = Math.max(0, -x), + stopRow = Math.min(h, imgHeight - y), stopColumn = Math.min(w, imgWidth - x); + for (var i = startRow; i < stopRow; ++i) { + var sourceOffset = ((y + i) * imgWidth + (x + startColumn)) * 4; + var targetOffset = (i * w + startColumn) * 4; + for (var j = startColumn; j < stopColumn; ++j) { + cData[targetOffset++] = imgData[sourceOffset++]; + cData[targetOffset++] = imgData[sourceOffset++]; + cData[targetOffset++] = imgData[sourceOffset++]; + cData[targetOffset++] = imgData[sourceOffset++]; + } + } + c.__isDirty = true; + return c; + } + + // Gets a single pixel or block of pixels from the current Canvas Context or a PImage + /** + * Reads the color of any pixel or grabs a section of an image. If no parameters are specified, the entire + * image is returned. Get the value of one pixel by specifying an x,y coordinate. Get a section of the display + * window by specifying an additional width and height parameter. If the pixel requested is outside of the image + * window, black is returned. The numbers returned are scaled according to the current color ranges, but only RGB + * values are returned by this function. For example, even though you may have drawn a shape with colorMode(HSB), + * the numbers returned will be in RGB. + * Getting the color of a single pixel with get(x, y) is easy, but not as fast as grabbing the data directly + * from pixels[]. The equivalent statement to "get(x, y)" using pixels[] is "pixels[y*width+x]". Processing + * requires calling loadPixels() to load the display window data into the pixels[] array before getting the values. + * This function ignores imageMode(). + * + * @param {int} x x-coordinate of the pixel + * @param {int} y y-coordinate of the pixel + * @param {int} width width of pixel rectangle to get + * @param {int} height height of pixel rectangle to get + * + * @returns {Color|PImage} + * + * @see set + * @see pixels[] + * @see imageMode + */ + p.get = function(x, y, w, h, img) { + // for 0 2 and 4 arguments use curContext, otherwise PImage.get was called + if (img !== undefined) { + return get$5(x, y, w, h, img); + } + if (h !== undefined) { + return get$4(x, y, w, h); + } + if (w !== undefined) { + return get$3(x, y, w); + } + if (y !== undefined) { + return get$2(x, y); + } + if (x !== undefined) { + // PImage.get() was called, return a new PImage + return get$5(0, 0, x.width, x.height, x); + } + + return get$4(0, 0, p.width, p.height); + }; + + /** + * Creates and returns a new <b>PGraphics</b> object of the types P2D, P3D, and JAVA2D. Use this class if you need to draw + * into an off-screen graphics buffer. It's not possible to use <b>createGraphics()</b> with OPENGL, because it doesn't + * allow offscreen use. The DXF and PDF renderers require the filename parameter. <br /><br /> It's important to call + * any drawing commands between beginDraw() and endDraw() statements. This is also true for any commands that affect + * drawing, such as smooth() or colorMode().<br /><br /> Unlike the main drawing surface which is completely opaque, + * surfaces created with createGraphics() can have transparency. This makes it possible to draw into a graphics and + * maintain the alpha channel. + * + * @param {int} width width in pixels + * @param {int} height height in pixels + * @param {int} renderer Either P2D, P3D, JAVA2D, PDF, DXF + * @param {String} filename the name of the file (not supported yet) + */ + p.createGraphics = function(w, h, render) { + var pg = new Processing(); + pg.size(w, h, render); + pg.background(0,0); + return pg; + }; + + // pixels caching + function resetContext() { + if(isContextReplaced) { + curContext = originalContext; + isContextReplaced = false; + + p.updatePixels(); + } + } + function SetPixelContextWrapper() { + function wrapFunction(newContext, name) { + function wrapper() { + resetContext(); + curContext[name].apply(curContext, arguments); + } + newContext[name] = wrapper; + } + function wrapProperty(newContext, name) { + function getter() { + resetContext(); + return curContext[name]; + } + function setter(value) { + resetContext(); + curContext[name] = value; + } + p.defineProperty(newContext, name, { get: getter, set: setter }); + } + for(var n in curContext) { + if(typeof curContext[n] === 'function') { + wrapFunction(this, n); + } else { + wrapProperty(this, n); + } + } + } + function replaceContext() { + if(isContextReplaced) { + return; + } + p.loadPixels(); + if(proxyContext === null) { + originalContext = curContext; + proxyContext = new SetPixelContextWrapper(); + } + isContextReplaced = true; + curContext = proxyContext; + setPixelsCached = 0; + } + + function set$3(x, y, c) { + if (x < p.width && x >= 0 && y >= 0 && y < p.height) { + replaceContext(); + p.pixels.setPixel((0|x)+p.width*(0|y), c); + if(++setPixelsCached > maxPixelsCached) { + resetContext(); + } + } + } + function set$4(x, y, obj, img) { + if (img.isRemote) { // Remote images cannot access imageData + throw "Image is loaded remotely. Cannot set x,y."; + } + var c = p.color.toArray(obj); + var offset = y * img.width * 4 + (x*4); + var data = img.imageData.data; + data[offset] = c[0]; + data[offset+1] = c[1]; + data[offset+2] = c[2]; + data[offset+3] = c[3]; + } + + // Paints a pixel array into the canvas + /** + * Changes the color of any pixel or writes an image directly into the display window. The x and y parameters + * specify the pixel to change and the color parameter specifies the color value. The color parameter is affected + * by the current color mode (the default is RGB values from 0 to 255). When setting an image, the x and y + * parameters define the coordinates for the upper-left corner of the image. + * Setting the color of a single pixel with set(x, y) is easy, but not as fast as putting the data directly + * into pixels[]. The equivalent statement to "set(x, y, #000000)" using pixels[] is "pixels[y*width+x] = #000000". + * You must call loadPixels() to load the display window data into the pixels[] array before setting the values + * and calling updatePixels() to update the window with any changes. This function ignores imageMode(). + * + * @param {int} x x-coordinate of the pixel + * @param {int} y y-coordinate of the pixel + * @param {Color} obj any value of the color datatype + * @param {PImage} img any valid variable of type PImage + * + * @see get + * @see pixels[] + * @see imageMode + */ + p.set = function(x, y, obj, img) { + var color, oldFill; + if (arguments.length === 3) { + // called p.set(), was it with a color or a img ? + if (typeof obj === "number") { + set$3(x, y, obj); + } else if (obj instanceof PImage || obj.__isPImage) { + p.image(obj, x, y); + } + } else if (arguments.length === 4) { + // PImage.set(x,y,c) was called, set coordinate x,y color to c of img + set$4(x, y, obj, img); + } + }; + p.imageData = {}; + + // handle the sketch code for pixels[] + // parser code converts pixels[] to getPixels() or setPixels(), + // .length becomes getLength() + /** + * Array containing the values for all the pixels in the display window. These values are of the color datatype. + * This array is the size of the display window. For example, if the image is 100x100 pixels, there will be 10000 + * values and if the window is 200x300 pixels, there will be 60000 values. The index value defines the position + * of a value within the array. For example, the statment color b = pixels[230] will set the variable b to be + * equal to the value at that location in the array. + * Before accessing this array, the data must loaded with the loadPixels() function. After the array data has + * been modified, the updatePixels() function must be run to update the changes. + * + * @param {int} index must not exceed the size of the array + * + * @see loadPixels + * @see updatePixels + * @see get + * @see set + * @see PImage + */ + p.pixels = { + getLength: function() { return p.imageData.data.length ? p.imageData.data.length/4 : 0; }, + getPixel: function(i) { + var offset = i*4, data = p.imageData.data; + return (data[offset+3] << 24) & 0xff000000 | + (data[offset+0] << 16) & 0x00ff0000 | + (data[offset+1] << 8) & 0x0000ff00 | + data[offset+2] & 0x000000ff; + }, + setPixel: function(i,c) { + var offset = i*4, data = p.imageData.data; + data[offset+0] = (c & 0x00ff0000) >>> 16; // RED_MASK + data[offset+1] = (c & 0x0000ff00) >>> 8; // GREEN_MASK + data[offset+2] = (c & 0x000000ff); // BLUE_MASK + data[offset+3] = (c & 0xff000000) >>> 24; // ALPHA_MASK + }, + toArray: function() { + var arr = [], length = p.imageData.width * p.imageData.height, data = p.imageData.data; + for (var i = 0, offset = 0; i < length; i++, offset += 4) { + arr.push((data[offset+3] << 24) & 0xff000000 | + (data[offset+0] << 16) & 0x00ff0000 | + (data[offset+1] << 8) & 0x0000ff00 | + data[offset+2] & 0x000000ff); + } + return arr; + }, + set: function(arr) { + for (var i = 0, aL = arr.length; i < aL; i++) { + this.setPixel(i, arr[i]); + } + } + }; + + // Gets a 1-Dimensional pixel array from Canvas + /** + * Loads the pixel data for the display window into the pixels[] array. This function must always be called + * before reading from or writing to pixels[]. + * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the rule is that + * any time you want to manipulate the pixels[] array, you must first call loadPixels(), and after changes + * have been made, call updatePixels(). Even if the renderer may not seem to use this function in the current + * Processing release, this will always be subject to change. + * + * @see pixels[] + * @see updatePixels + */ + p.loadPixels = function() { + p.imageData = drawing.$ensureContext().getImageData(0, 0, p.width, p.height); + }; + + // Draws a 1-Dimensional pixel array to Canvas + /** + * Updates the display window with the data in the pixels[] array. Use in conjunction with loadPixels(). If + * you're only reading pixels from the array, there's no need to call updatePixels() unless there are changes. + * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the rule is that + * any time you want to manipulate the pixels[] array, you must first call loadPixels(), and after changes + * have been made, call updatePixels(). Even if the renderer may not seem to use this function in the current + * Processing release, this will always be subject to change. + * Currently, none of the renderers use the additional parameters to updatePixels(), however this may be + * implemented in the future. + * + * @see loadPixels + * @see pixels[] + */ + p.updatePixels = function() { + if (p.imageData) { + drawing.$ensureContext().putImageData(p.imageData, 0, 0); + } + }; + + /** + * Set various hints and hacks for the renderer. This is used to handle obscure rendering features that cannot be + * implemented in a consistent manner across renderers. Many options will often graduate to standard features + * instead of hints over time. + * hint(ENABLE_OPENGL_4X_SMOOTH) - Enable 4x anti-aliasing for OpenGL. This can help force anti-aliasing if + * it has not been enabled by the user. On some graphics cards, this can also be set by the graphics driver's + * control panel, however not all cards make this available. This hint must be called immediately after the + * size() command because it resets the renderer, obliterating any settings and anything drawn (and like size(), + * re-running the code that came before it again). + * hint(DISABLE_OPENGL_2X_SMOOTH) - In Processing 1.0, Processing always enables 2x smoothing when the OpenGL + * renderer is used. This hint disables the default 2x smoothing and returns the smoothing behavior found in + * earlier releases, where smooth() and noSmooth() could be used to enable and disable smoothing, though the + * quality was inferior. + * hint(ENABLE_NATIVE_FONTS) - Use the native version fonts when they are installed, rather than the bitmapped + * version from a .vlw file. This is useful with the JAVA2D renderer setting, as it will improve font rendering + * speed. This is not enabled by default, because it can be misleading while testing because the type will look + * great on your machine (because you have the font installed) but lousy on others' machines if the identical + * font is unavailable. This option can only be set per-sketch, and must be called before any use of textFont(). + * hint(DISABLE_DEPTH_TEST) - Disable the zbuffer, allowing you to draw on top of everything at will. When depth + * testing is disabled, items will be drawn to the screen sequentially, like a painting. This hint is most often + * used to draw in 3D, then draw in 2D on top of it (for instance, to draw GUI controls in 2D on top of a 3D + * interface). Starting in release 0149, this will also clear the depth buffer. Restore the default with + * hint(ENABLE_DEPTH_TEST), but note that with the depth buffer cleared, any 3D drawing that happens later in + * draw() will ignore existing shapes on the screen. + * hint(ENABLE_DEPTH_SORT) - Enable primitive z-sorting of triangles and lines in P3D and OPENGL. This can slow + * performance considerably, and the algorithm is not yet perfect. Restore the default with hint(DISABLE_DEPTH_SORT). + * hint(DISABLE_OPENGL_ERROR_REPORT) - Speeds up the OPENGL renderer setting by not checking for errors while + * running. Undo with hint(ENABLE_OPENGL_ERROR_REPORT). + * As of release 0149, unhint() has been removed in favor of adding additional ENABLE/DISABLE constants to reset + * the default behavior. This prevents the double negatives, and also reinforces which hints can be enabled or disabled. + * + * @param {MODE} item constant: name of the hint to be enabled or disabled + * + * @see PGraphics + * @see createGraphics + * @see size + */ + p.hint = function(which) { + var curContext = drawing.$ensureContext(); + if (which === PConstants.DISABLE_DEPTH_TEST) { + curContext.disable(curContext.DEPTH_TEST); + curContext.depthMask(false); + curContext.clear(curContext.DEPTH_BUFFER_BIT); + } + else if (which === PConstants.ENABLE_DEPTH_TEST) { + curContext.enable(curContext.DEPTH_TEST); + curContext.depthMask(true); + } + else if (which === PConstants.ENABLE_OPENGL_2X_SMOOTH || + which === PConstants.ENABLE_OPENGL_4X_SMOOTH){ + renderSmooth = true; + } + else if (which === PConstants.DISABLE_OPENGL_2X_SMOOTH){ + renderSmooth = false; + } + }; + + /** + * The background() function sets the color used for the background of the Processing window. + * The default background is light gray. In the <b>draw()</b> function, the background color is used to clear the display window at the beginning of each frame. + * An image can also be used as the background for a sketch, however its width and height must be the same size as the sketch window. + * To resize an image 'b' to the size of the sketch window, use b.resize(width, height). + * Images used as background will ignore the current <b>tint()</b> setting. + * For the main drawing surface, the alpha value will be ignored. However, + * alpha can be used on PGraphics objects from <b>createGraphics()</b>. This is + * the only way to set all the pixels partially transparent, for instance. + * If the 'gray' parameter is passed in the function sets the background to a grayscale value, based on the + * current colorMode. + * <p> + * Note that background() should be called before any transformations occur, + * because some implementations may require the current transformation matrix + * to be identity before drawing. + * + * @param {int|float} gray specifies a value between white and black + * @param {int|float} value1 red or hue value (depending on the current color mode) + * @param {int|float} value2 green or saturation value (depending on the current color mode) + * @param {int|float} value3 blue or brightness value (depending on the current color mode) + * @param {int|float} alpha opacity of the background + * @param {Color} color any value of the color datatype + * @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) + * @param {PImage} image an instance of a PImage to use as a background + * + * @see #stroke() + * @see #fill() + * @see #tint() + * @see #colorMode() + */ + var backgroundHelper = function(arg1, arg2, arg3, arg4) { + var obj; + + if (arg1 instanceof PImage || arg1.__isPImage) { + obj = arg1; + + if (!obj.loaded) { + throw "Error using image in background(): PImage not loaded."; + } + if(obj.width !== p.width || obj.height !== p.height){ + throw "Background image must be the same dimensions as the canvas."; + } + } else { + obj = p.color(arg1, arg2, arg3, arg4); + } + + backgroundObj = obj; + }; + + Drawing2D.prototype.background = function(arg1, arg2, arg3, arg4) { + if (arg1 !== undef) { + backgroundHelper(arg1, arg2, arg3, arg4); + } + + if (backgroundObj instanceof PImage || backgroundObj.__isPImage) { + saveContext(); + curContext.setTransform(1, 0, 0, 1, 0, 0); + p.image(backgroundObj, 0, 0); + restoreContext(); + } else { + saveContext(); + curContext.setTransform(1, 0, 0, 1, 0, 0); + + // If the background is transparent + if (p.alpha(backgroundObj) !== colorModeA) { + curContext.clearRect(0,0, p.width, p.height); + } + curContext.fillStyle = p.color.toString(backgroundObj); + curContext.fillRect(0, 0, p.width, p.height); + isFillDirty = true; + restoreContext(); + } + }; + + Drawing3D.prototype.background = function(arg1, arg2, arg3, arg4) { + if (arguments.length > 0) { + backgroundHelper(arg1, arg2, arg3, arg4); + } + + var c = p.color.toGLArray(backgroundObj); + curContext.clearColor(c[0], c[1], c[2], c[3]); + curContext.clear(curContext.COLOR_BUFFER_BIT | curContext.DEPTH_BUFFER_BIT); + + // An image as a background in 3D is not implemented yet + }; + + // Draws an image to the Canvas + /** + * Displays images to the screen. The images must be in the sketch's "data" directory to load correctly. Select "Add + * file..." from the "Sketch" menu to add the image. Processing currently works with GIF, JPEG, and Targa images. The + * color of an image may be modified with the tint() function and if a GIF has transparency, it will maintain its + * transparency. The img parameter specifies the image to display and the x and y parameters define the location of + * the image from its upper-left corner. The image is displayed at its original size unless the width and height + * parameters specify a different size. The imageMode() function changes the way the parameters work. A call to + * imageMode(CORNERS) will change the width and height parameters to define the x and y values of the opposite + * corner of the image. + * + * @param {PImage} img the image to display + * @param {int|float} x x-coordinate of the image + * @param {int|float} y y-coordinate of the image + * @param {int|float} width width to display the image + * @param {int|float} height height to display the image + * + * @see loadImage + * @see PImage + * @see imageMode + * @see tint + * @see background + * @see alpha + */ + Drawing2D.prototype.image = function(img, x, y, w, h) { + // Fix fractional positions + x = Math.round(x); + y = Math.round(y); + + if (img.width > 0) { + var wid = w || img.width; + var hgt = h || img.height; + + var bounds = imageModeConvert(x || 0, y || 0, w || img.width, h || img.height, arguments.length < 4); + var fastImage = !!img.sourceImg && curTint === null; + if (fastImage) { + var htmlElement = img.sourceImg; + if (img.__isDirty) { + img.updatePixels(); + } + // Using HTML element's width and height in case if the image was resized. + curContext.drawImage(htmlElement, 0, 0, + htmlElement.width, htmlElement.height, bounds.x, bounds.y, bounds.w, bounds.h); + } else { + var obj = img.toImageData(); + + // Tint the image + if (curTint !== null) { + curTint(obj); + img.__isDirty = true; + } + + curContext.drawImage(getCanvasData(obj).canvas, 0, 0, + img.width, img.height, bounds.x, bounds.y, bounds.w, bounds.h); + } + } + }; + + Drawing3D.prototype.image = function(img, x, y, w, h) { + if (img.width > 0) { + // Fix fractional positions + x = Math.round(x); + y = Math.round(y); + w = w || img.width; + h = h || img.height; + + p.beginShape(p.QUADS); + p.texture(img); + p.vertex(x, y, 0, 0, 0); + p.vertex(x, y+h, 0, 0, h); + p.vertex(x+w, y+h, 0, w, h); + p.vertex(x+w, y, 0, w, 0); + p.endShape(); + } + }; + + /** + * The tint() function sets the fill value for displaying images. Images can be tinted to + * specified colors or made transparent by setting the alpha. + * <br><br>To make an image transparent, but not change it's color, + * use white as the tint color and specify an alpha value. For instance, + * tint(255, 128) will make an image 50% transparent (unless + * <b>colorMode()</b> has been used). + * + * <br><br>When using hexadecimal notation to specify a color, use "#" or + * "0x" before the values (e.g. #CCFFAA, 0xFFCCFFAA). The # syntax uses six + * digits to specify a color (the way colors are specified in HTML and CSS). + * When using the hexadecimal notation starting with "0x", the hexadecimal + * value must be specified with eight characters; the first two characters + * define the alpha component and the remainder the red, green, and blue + * components. + * <br><br>The value for the parameter "gray" must be less than or equal + * to the current maximum value as specified by <b>colorMode()</b>. + * The default maximum value is 255. + * <br><br>The tint() method is also used to control the coloring of + * textures in 3D. + * + * @param {int|float} gray any valid number + * @param {int|float} alpha opacity of the image + * @param {int|float} value1 red or hue value + * @param {int|float} value2 green or saturation value + * @param {int|float} value3 blue or brightness value + * @param {int|float} color any value of the color datatype + * @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) + * + * @see #noTint() + * @see #image() + */ + p.tint = function(a1, a2, a3, a4) { + var tintColor = p.color(a1, a2, a3, a4); + var r = p.red(tintColor) / colorModeX; + var g = p.green(tintColor) / colorModeY; + var b = p.blue(tintColor) / colorModeZ; + var a = p.alpha(tintColor) / colorModeA; + curTint = function(obj) { + var data = obj.data, + length = 4 * obj.width * obj.height; + for (var i = 0; i < length;) { + data[i++] *= r; + data[i++] *= g; + data[i++] *= b; + data[i++] *= a; + } + }; + // for overriding the color buffer when 3d rendering + curTint3d = function(data){ + for (var i = 0; i < data.length;) { + data[i++] = r; + data[i++] = g; + data[i++] = b; + data[i++] = a; + } + }; + }; + + /** + * The noTint() function removes the current fill value for displaying images and reverts to displaying images with their original hues. + * + * @see #tint() + * @see #image() + */ + p.noTint = function() { + curTint = null; + curTint3d = null; + }; + + /** + * Copies a region of pixels from the display window to another area of the display window and copies a region of pixels from an + * image used as the srcImg parameter into the display window. If the source and destination regions aren't the same size, it will + * automatically resize the source pixels to fit the specified target region. No alpha information is used in the process, however + * if the source image has an alpha channel set, it will be copied as well. This function ignores imageMode(). + * + * @param {int} x X coordinate of the source's upper left corner + * @param {int} y Y coordinate of the source's upper left corner + * @param {int} width source image width + * @param {int} height source image height + * @param {int} dx X coordinate of the destination's upper left corner + * @param {int} dy Y coordinate of the destination's upper left corner + * @param {int} dwidth destination image width + * @param {int} dheight destination image height + * @param {PImage} srcImg image variable referring to the source image + * + * @see blend + * @see get + */ + p.copy = function(src, sx, sy, sw, sh, dx, dy, dw, dh) { + if (dh === undef) { + // shift everything, and introduce p + dh = dw; + dw = dy; + dy = dx; + dx = sh; + sh = sw; + sw = sy; + sy = sx; + sx = src; + src = p; + } + p.blend(src, sx, sy, sw, sh, dx, dy, dw, dh, PConstants.REPLACE); + }; + + /** + * Blends a region of pixels from one image into another (or in itself again) with full alpha channel support. There + * is a choice of the following modes to blend the source pixels (A) with the ones of pixels in the destination image (B): + * BLEND - linear interpolation of colours: C = A*factor + B + * ADD - additive blending with white clip: C = min(A*factor + B, 255) + * SUBTRACT - subtractive blending with black clip: C = max(B - A*factor, 0) + * DARKEST - only the darkest colour succeeds: C = min(A*factor, B) + * LIGHTEST - only the lightest colour succeeds: C = max(A*factor, B) + * DIFFERENCE - subtract colors from underlying image. + * EXCLUSION - similar to DIFFERENCE, but less extreme. + * MULTIPLY - Multiply the colors, result will always be darker. + * SCREEN - Opposite multiply, uses inverse values of the colors. + * OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values, and screens light values. + * HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower. + * SOFT_LIGHT - Mix of DARKEST and LIGHTEST. Works like OVERLAY, but not as harsh. + * DODGE - Lightens light tones and increases contrast, ignores darks. Called "Color Dodge" in Illustrator and Photoshop. + * BURN - Darker areas are applied, increasing contrast, ignores lights. Called "Color Burn" in Illustrator and Photoshop. + * All modes use the alpha information (highest byte) of source image pixels as the blending factor. If the source and + * destination regions are different sizes, the image will be automatically resized to match the destination size. If the + * srcImg parameter is not used, the display window is used as the source image. This function ignores imageMode(). + * + * @param {int} x X coordinate of the source's upper left corner + * @param {int} y Y coordinate of the source's upper left corner + * @param {int} width source image width + * @param {int} height source image height + * @param {int} dx X coordinate of the destination's upper left corner + * @param {int} dy Y coordinate of the destination's upper left corner + * @param {int} dwidth destination image width + * @param {int} dheight destination image height + * @param {PImage} srcImg image variable referring to the source image + * @param {PImage} MODE Either BLEND, ADD, SUBTRACT, LIGHTEST, DARKEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, + * OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN + * @see filter + */ + p.blend = function(src, sx, sy, sw, sh, dx, dy, dw, dh, mode, pimgdest) { + if (src.isRemote) { + throw "Image is loaded remotely. Cannot blend image."; + } + + if (mode === undef) { + // shift everything, and introduce p + mode = dh; + dh = dw; + dw = dy; + dy = dx; + dx = sh; + sh = sw; + sw = sy; + sy = sx; + sx = src; + src = p; + } + + var sx2 = sx + sw, + sy2 = sy + sh, + dx2 = dx + dw, + dy2 = dy + dh, + dest = pimgdest || p; + + // check if pimgdest is there and pixels, if so this was a call from pimg.blend + if (pimgdest === undef || mode === undef) { + p.loadPixels(); + } + + src.loadPixels(); + + if (src === p && p.intersect(sx, sy, sx2, sy2, dx, dy, dx2, dy2)) { + p.blit_resize(p.get(sx, sy, sx2 - sx, sy2 - sy), 0, 0, sx2 - sx - 1, sy2 - sy - 1, + dest.imageData.data, dest.width, dest.height, dx, dy, dx2, dy2, mode); + } else { + p.blit_resize(src, sx, sy, sx2, sy2, dest.imageData.data, dest.width, dest.height, dx, dy, dx2, dy2, mode); + } + + if (pimgdest === undef) { + p.updatePixels(); + } + }; + + // helper function for filter() + var buildBlurKernel = function(r) { + var radius = p.floor(r * 3.5), i; + radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248); + if (p.shared.blurRadius !== radius) { + p.shared.blurRadius = radius; + p.shared.blurKernelSize = 1 + (p.shared.blurRadius<<1); + p.shared.blurKernel = new Float32Array(p.shared.blurKernelSize); + var sharedBlurKernal = p.shared.blurKernel; + var sharedBlurKernelSize = p.shared.blurKernelSize; + var sharedBlurRadius = p.shared.blurRadius; + // init blurKernel + for (i = 0; i < sharedBlurKernelSize; i++) { + sharedBlurKernal[i] = 0; + } + var radiusiSquared = (radius - 1) * (radius - 1); + for (i = 1; i < radius; i++) { + sharedBlurKernal[radius + i] = sharedBlurKernal[radius-i] = radiusiSquared; + } + sharedBlurKernal[radius] = radius * radius; + } + }; + + var blurARGB = function(r, aImg) { + var sum, cr, cg, cb, ca, c, m; + var read, ri, ym, ymi, bk0; + var wh = aImg.pixels.getLength(); + var r2 = new Float32Array(wh); + var g2 = new Float32Array(wh); + var b2 = new Float32Array(wh); + var a2 = new Float32Array(wh); + var yi = 0; + var x, y, i, offset; + + buildBlurKernel(r); + + var aImgHeight = aImg.height; + var aImgWidth = aImg.width; + var sharedBlurKernelSize = p.shared.blurKernelSize; + var sharedBlurRadius = p.shared.blurRadius; + var sharedBlurKernal = p.shared.blurKernel; + var pix = aImg.imageData.data; + + for (y = 0; y < aImgHeight; y++) { + for (x = 0; x < aImgWidth; x++) { + cb = cg = cr = ca = sum = 0; + read = x - sharedBlurRadius; + if (read<0) { + bk0 = -read; + read = 0; + } else { + if (read >= aImgWidth) { + break; + } + bk0=0; + } + for (i = bk0; i < sharedBlurKernelSize; i++) { + if (read >= aImgWidth) { + break; + } + offset = (read + yi) *4; + m = sharedBlurKernal[i]; + ca += m * pix[offset + 3]; + cr += m * pix[offset]; + cg += m * pix[offset + 1]; + cb += m * pix[offset + 2]; + sum += m; + read++; + } + ri = yi + x; + a2[ri] = ca / sum; + r2[ri] = cr / sum; + g2[ri] = cg / sum; + b2[ri] = cb / sum; + } + yi += aImgWidth; + } + + yi = 0; + ym = -sharedBlurRadius; + ymi = ym*aImgWidth; + + for (y = 0; y < aImgHeight; y++) { + for (x = 0; x < aImgWidth; x++) { + cb = cg = cr = ca = sum = 0; + if (ym<0) { + bk0 = ri = -ym; + read = x; + } else { + if (ym >= aImgHeight) { + break; + } + bk0 = 0; + ri = ym; + read = x + ymi; + } + for (i = bk0; i < sharedBlurKernelSize; i++) { + if (ri >= aImgHeight) { + break; + } + m = sharedBlurKernal[i]; + ca += m * a2[read]; + cr += m * r2[read]; + cg += m * g2[read]; + cb += m * b2[read]; + sum += m; + ri++; + read += aImgWidth; + } + offset = (x + yi) *4; + pix[offset] = cr / sum; + pix[offset + 1] = cg / sum; + pix[offset + 2] = cb / sum; + pix[offset + 3] = ca / sum; + } + yi += aImgWidth; + ymi += aImgWidth; + ym++; + } + }; + + // helper funtion for ERODE and DILATE modes of filter() + var dilate = function(isInverted, aImg) { + var currIdx = 0; + var maxIdx = aImg.pixels.getLength(); + var out = new Int32Array(maxIdx); + var currRowIdx, maxRowIdx, colOrig, colOut, currLum; + var idxRight, idxLeft, idxUp, idxDown, + colRight, colLeft, colUp, colDown, + lumRight, lumLeft, lumUp, lumDown; + + if (!isInverted) { + // erosion (grow light areas) + while (currIdx<maxIdx) { + currRowIdx = currIdx; + maxRowIdx = currIdx + aImg.width; + while (currIdx < maxRowIdx) { + colOrig = colOut = aImg.pixels.getPixel(currIdx); + idxLeft = currIdx - 1; + idxRight = currIdx + 1; + idxUp = currIdx - aImg.width; + idxDown = currIdx + aImg.width; + if (idxLeft < currRowIdx) { + idxLeft = currIdx; + } + if (idxRight >= maxRowIdx) { + idxRight = currIdx; + } + if (idxUp < 0) { + idxUp = 0; + } + if (idxDown >= maxIdx) { + idxDown = currIdx; + } + colUp = aImg.pixels.getPixel(idxUp); + colLeft = aImg.pixels.getPixel(idxLeft); + colDown = aImg.pixels.getPixel(idxDown); + colRight = aImg.pixels.getPixel(idxRight); + + // compute luminance + currLum = 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff); + lumLeft = 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff); + lumRight = 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff); + lumUp = 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff); + lumDown = 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff); + + if (lumLeft > currLum) { + colOut = colLeft; + currLum = lumLeft; + } + if (lumRight > currLum) { + colOut = colRight; + currLum = lumRight; + } + if (lumUp > currLum) { + colOut = colUp; + currLum = lumUp; + } + if (lumDown > currLum) { + colOut = colDown; + currLum = lumDown; + } + out[currIdx++] = colOut; + } + } + } else { + // dilate (grow dark areas) + while (currIdx < maxIdx) { + currRowIdx = currIdx; + maxRowIdx = currIdx + aImg.width; + while (currIdx < maxRowIdx) { + colOrig = colOut = aImg.pixels.getPixel(currIdx); + idxLeft = currIdx - 1; + idxRight = currIdx + 1; + idxUp = currIdx - aImg.width; + idxDown = currIdx + aImg.width; + if (idxLeft < currRowIdx) { + idxLeft = currIdx; + } + if (idxRight >= maxRowIdx) { + idxRight = currIdx; + } + if (idxUp < 0) { + idxUp = 0; + } + if (idxDown >= maxIdx) { + idxDown = currIdx; + } + colUp = aImg.pixels.getPixel(idxUp); + colLeft = aImg.pixels.getPixel(idxLeft); + colDown = aImg.pixels.getPixel(idxDown); + colRight = aImg.pixels.getPixel(idxRight); + + // compute luminance + currLum = 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff); + lumLeft = 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff); + lumRight = 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff); + lumUp = 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff); + lumDown = 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff); + + if (lumLeft < currLum) { + colOut = colLeft; + currLum = lumLeft; + } + if (lumRight < currLum) { + colOut = colRight; + currLum = lumRight; + } + if (lumUp < currLum) { + colOut = colUp; + currLum = lumUp; + } + if (lumDown < currLum) { + colOut = colDown; + currLum = lumDown; + } + out[currIdx++]=colOut; + } + } + } + aImg.pixels.set(out); + //p.arraycopy(out,0,pixels,0,maxIdx); + }; + + /** + * Filters the display window as defined by one of the following modes: + * THRESHOLD - converts the image to black and white pixels depending if they are above or below the threshold + * defined by the level parameter. The level must be between 0.0 (black) and 1.0(white). If no level is specified, 0.5 is used. + * GRAY - converts any colors in the image to grayscale equivalents + * INVERT - sets each pixel to its inverse value + * POSTERIZE - limits each channel of the image to the number of colors specified as the level parameter + * BLUR - executes a Guassian blur with the level parameter specifying the extent of the blurring. If no level parameter is + * used, the blur is equivalent to Guassian blur of radius 1. + * OPAQUE - sets the alpha channel to entirely opaque. + * ERODE - reduces the light areas with the amount defined by the level parameter. + * DILATE - increases the light areas with the amount defined by the level parameter. + * + * @param {MODE} MODE Either THRESHOLD, GRAY, INVERT, POSTERIZE, BLUR, OPAQUE, ERODE, or DILATE + * @param {int|float} level defines the quality of the filter + * + * @see blend + */ + p.filter = function(kind, param, aImg){ + var img, col, lum, i; + + if (arguments.length === 3) { + aImg.loadPixels(); + img = aImg; + } else { + p.loadPixels(); + img = p; + } + + if (param === undef) { + param = null; + } + if (img.isRemote) { // Remote images cannot access imageData + throw "Image is loaded remotely. Cannot filter image."; + } + // begin filter process + var imglen = img.pixels.getLength(); + switch (kind) { + case PConstants.BLUR: + var radius = param || 1; // if no param specified, use 1 (default for p5) + blurARGB(radius, img); + break; + + case PConstants.GRAY: + if (img.format === PConstants.ALPHA) { //trouble + // for an alpha image, convert it to an opaque grayscale + for (i = 0; i < imglen; i++) { + col = 255 - img.pixels.getPixel(i); + img.pixels.setPixel(i,(0xff000000 | (col << 16) | (col << 8) | col)); + } + img.format = PConstants.RGB; //trouble + } else { + for (i = 0; i < imglen; i++) { + col = img.pixels.getPixel(i); + lum = (77*(col>>16&0xff) + 151*(col>>8&0xff) + 28*(col&0xff))>>8; + img.pixels.setPixel(i,((col & PConstants.ALPHA_MASK) | lum<<16 | lum<<8 | lum)); + } + } + break; + + case PConstants.INVERT: + for (i = 0; i < imglen; i++) { + img.pixels.setPixel(i, (img.pixels.getPixel(i) ^ 0xffffff)); + } + break; + + case PConstants.POSTERIZE: + if (param === null) { + throw "Use filter(POSTERIZE, int levels) instead of filter(POSTERIZE)"; + } + var levels = p.floor(param); + if ((levels < 2) || (levels > 255)) { + throw "Levels must be between 2 and 255 for filter(POSTERIZE, levels)"; + } + var levels1 = levels - 1; + for (i = 0; i < imglen; i++) { + var rlevel = (img.pixels.getPixel(i) >> 16) & 0xff; + var glevel = (img.pixels.getPixel(i) >> 8) & 0xff; + var blevel = img.pixels.getPixel(i) & 0xff; + rlevel = (((rlevel * levels) >> 8) * 255) / levels1; + glevel = (((glevel * levels) >> 8) * 255) / levels1; + blevel = (((blevel * levels) >> 8) * 255) / levels1; + img.pixels.setPixel(i, ((0xff000000 & img.pixels.getPixel(i)) | (rlevel << 16) | (glevel << 8) | blevel)); + } + break; + + case PConstants.OPAQUE: + for (i = 0; i < imglen; i++) { + img.pixels.setPixel(i, (img.pixels.getPixel(i) | 0xff000000)); + } + img.format = PConstants.RGB; //trouble + break; + + case PConstants.THRESHOLD: + if (param === null) { + param = 0.5; + } + if ((param < 0) || (param > 1)) { + throw "Level must be between 0 and 1 for filter(THRESHOLD, level)"; + } + var thresh = p.floor(param * 255); + for (i = 0; i < imglen; i++) { + var max = p.max((img.pixels.getPixel(i) & PConstants.RED_MASK) >> 16, p.max((img.pixels.getPixel(i) & PConstants.GREEN_MASK) >> 8, (img.pixels.getPixel(i) & PConstants.BLUE_MASK))); + img.pixels.setPixel(i, ((img.pixels.getPixel(i) & PConstants.ALPHA_MASK) | ((max < thresh) ? 0x000000 : 0xffffff))); + } + break; + + case PConstants.ERODE: + dilate(true, img); + break; + + case PConstants.DILATE: + dilate(false, img); + break; + } + img.updatePixels(); + }; + + + // shared variables for blit_resize(), filter_new_scanline(), filter_bilinear(), filter() + // change this in the future to not be exposed to p + p.shared = { + fracU: 0, + ifU: 0, + fracV: 0, + ifV: 0, + u1: 0, + u2: 0, + v1: 0, + v2: 0, + sX: 0, + sY: 0, + iw: 0, + iw1: 0, + ih1: 0, + ul: 0, + ll: 0, + ur: 0, + lr: 0, + cUL: 0, + cLL: 0, + cUR: 0, + cLR: 0, + srcXOffset: 0, + srcYOffset: 0, + r: 0, + g: 0, + b: 0, + a: 0, + srcBuffer: null, + blurRadius: 0, + blurKernelSize: 0, + blurKernel: null + }; + + p.intersect = function(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2) { + var sw = sx2 - sx1 + 1; + var sh = sy2 - sy1 + 1; + var dw = dx2 - dx1 + 1; + var dh = dy2 - dy1 + 1; + if (dx1 < sx1) { + dw += dx1 - sx1; + if (dw > sw) { + dw = sw; + } + } else { + var w = sw + sx1 - dx1; + if (dw > w) { + dw = w; + } + } + if (dy1 < sy1) { + dh += dy1 - sy1; + if (dh > sh) { + dh = sh; + } + } else { + var h = sh + sy1 - dy1; + if (dh > h) { + dh = h; + } + } + return ! (dw <= 0 || dh <= 0); + }; + + var blendFuncs = {}; + blendFuncs[PConstants.BLEND] = p.modes.blend; + blendFuncs[PConstants.ADD] = p.modes.add; + blendFuncs[PConstants.SUBTRACT] = p.modes.subtract; + blendFuncs[PConstants.LIGHTEST] = p.modes.lightest; + blendFuncs[PConstants.DARKEST] = p.modes.darkest; + blendFuncs[PConstants.REPLACE] = p.modes.replace; + blendFuncs[PConstants.DIFFERENCE] = p.modes.difference; + blendFuncs[PConstants.EXCLUSION] = p.modes.exclusion; + blendFuncs[PConstants.MULTIPLY] = p.modes.multiply; + blendFuncs[PConstants.SCREEN] = p.modes.screen; + blendFuncs[PConstants.OVERLAY] = p.modes.overlay; + blendFuncs[PConstants.HARD_LIGHT] = p.modes.hard_light; + blendFuncs[PConstants.SOFT_LIGHT] = p.modes.soft_light; + blendFuncs[PConstants.DODGE] = p.modes.dodge; + blendFuncs[PConstants.BURN] = p.modes.burn; + + p.blit_resize = function(img, srcX1, srcY1, srcX2, srcY2, destPixels, + screenW, screenH, destX1, destY1, destX2, destY2, mode) { + var x, y; + if (srcX1 < 0) { + srcX1 = 0; + } + if (srcY1 < 0) { + srcY1 = 0; + } + if (srcX2 >= img.width) { + srcX2 = img.width - 1; + } + if (srcY2 >= img.height) { + srcY2 = img.height - 1; + } + var srcW = srcX2 - srcX1; + var srcH = srcY2 - srcY1; + var destW = destX2 - destX1; + var destH = destY2 - destY1; + + if (destW <= 0 || destH <= 0 || srcW <= 0 || srcH <= 0 || destX1 >= screenW || + destY1 >= screenH || srcX1 >= img.width || srcY1 >= img.height) { + return; + } + + var dx = Math.floor(srcW / destW * PConstants.PRECISIONF); + var dy = Math.floor(srcH / destH * PConstants.PRECISIONF); + + var pshared = p.shared; + + pshared.srcXOffset = Math.floor(destX1 < 0 ? -destX1 * dx : srcX1 * PConstants.PRECISIONF); + pshared.srcYOffset = Math.floor(destY1 < 0 ? -destY1 * dy : srcY1 * PConstants.PRECISIONF); + if (destX1 < 0) { + destW += destX1; + destX1 = 0; + } + if (destY1 < 0) { + destH += destY1; + destY1 = 0; + } + destW = Math.min(destW, screenW - destX1); + destH = Math.min(destH, screenH - destY1); + + var destOffset = destY1 * screenW + destX1; + var destColor; + + pshared.srcBuffer = img.imageData.data; + pshared.iw = img.width; + pshared.iw1 = img.width - 1; + pshared.ih1 = img.height - 1; + + // cache for speed + var filterBilinear = p.filter_bilinear, + filterNewScanline = p.filter_new_scanline, + blendFunc = blendFuncs[mode], + blendedColor, + idx, + cULoffset, + cURoffset, + cLLoffset, + cLRoffset, + ALPHA_MASK = PConstants.ALPHA_MASK, + RED_MASK = PConstants.RED_MASK, + GREEN_MASK = PConstants.GREEN_MASK, + BLUE_MASK = PConstants.BLUE_MASK, + PREC_MAXVAL = PConstants.PREC_MAXVAL, + PRECISIONB = PConstants.PRECISIONB, + PREC_RED_SHIFT = PConstants.PREC_RED_SHIFT, + PREC_ALPHA_SHIFT = PConstants.PREC_ALPHA_SHIFT, + srcBuffer = pshared.srcBuffer, + min = Math.min; + + for (y = 0; y < destH; y++) { + + pshared.sX = pshared.srcXOffset; + pshared.fracV = pshared.srcYOffset & PREC_MAXVAL; + pshared.ifV = PREC_MAXVAL - pshared.fracV; + pshared.v1 = (pshared.srcYOffset >> PRECISIONB) * pshared.iw; + pshared.v2 = min((pshared.srcYOffset >> PRECISIONB) + 1, pshared.ih1) * pshared.iw; + + for (x = 0; x < destW; x++) { + idx = (destOffset + x) * 4; + + destColor = (destPixels[idx + 3] << 24) & + ALPHA_MASK | (destPixels[idx] << 16) & + RED_MASK | (destPixels[idx + 1] << 8) & + GREEN_MASK | destPixels[idx + 2] & BLUE_MASK; + + pshared.fracU = pshared.sX & PREC_MAXVAL; + pshared.ifU = PREC_MAXVAL - pshared.fracU; + pshared.ul = (pshared.ifU * pshared.ifV) >> PRECISIONB; + pshared.ll = (pshared.ifU * pshared.fracV) >> PRECISIONB; + pshared.ur = (pshared.fracU * pshared.ifV) >> PRECISIONB; + pshared.lr = (pshared.fracU * pshared.fracV) >> PRECISIONB; + pshared.u1 = (pshared.sX >> PRECISIONB); + pshared.u2 = min(pshared.u1 + 1, pshared.iw1); + + cULoffset = (pshared.v1 + pshared.u1) * 4; + cURoffset = (pshared.v1 + pshared.u2) * 4; + cLLoffset = (pshared.v2 + pshared.u1) * 4; + cLRoffset = (pshared.v2 + pshared.u2) * 4; + + pshared.cUL = (srcBuffer[cULoffset + 3] << 24) & + ALPHA_MASK | (srcBuffer[cULoffset] << 16) & + RED_MASK | (srcBuffer[cULoffset + 1] << 8) & + GREEN_MASK | srcBuffer[cULoffset + 2] & BLUE_MASK; + + pshared.cUR = (srcBuffer[cURoffset + 3] << 24) & + ALPHA_MASK | (srcBuffer[cURoffset] << 16) & + RED_MASK | (srcBuffer[cURoffset + 1] << 8) & + GREEN_MASK | srcBuffer[cURoffset + 2] & BLUE_MASK; + + pshared.cLL = (srcBuffer[cLLoffset + 3] << 24) & + ALPHA_MASK | (srcBuffer[cLLoffset] << 16) & + RED_MASK | (srcBuffer[cLLoffset + 1] << 8) & + GREEN_MASK | srcBuffer[cLLoffset + 2] & BLUE_MASK; + + pshared.cLR = (srcBuffer[cLRoffset + 3] << 24) & + ALPHA_MASK | (srcBuffer[cLRoffset] << 16) & + RED_MASK | (srcBuffer[cLRoffset + 1] << 8) & + GREEN_MASK | srcBuffer[cLRoffset + 2] & BLUE_MASK; + + pshared.r = ((pshared.ul * ((pshared.cUL & RED_MASK) >> 16) + + pshared.ll * ((pshared.cLL & RED_MASK) >> 16) + + pshared.ur * ((pshared.cUR & RED_MASK) >> 16) + + pshared.lr * ((pshared.cLR & RED_MASK) >> 16)) << PREC_RED_SHIFT) & RED_MASK; + pshared.g = ((pshared.ul * (pshared.cUL & GREEN_MASK) + + pshared.ll * (pshared.cLL & GREEN_MASK) + + pshared.ur * (pshared.cUR & GREEN_MASK) + + pshared.lr * (pshared.cLR & GREEN_MASK)) >>> PRECISIONB) & GREEN_MASK; + pshared.b = (pshared.ul * (pshared.cUL & BLUE_MASK) + + pshared.ll * (pshared.cLL & BLUE_MASK) + + pshared.ur * (pshared.cUR & BLUE_MASK) + + pshared.lr * (pshared.cLR & BLUE_MASK)) >>> PRECISIONB; + pshared.a = ((pshared.ul * ((pshared.cUL & ALPHA_MASK) >>> 24) + + pshared.ll * ((pshared.cLL & ALPHA_MASK) >>> 24) + + pshared.ur * ((pshared.cUR & ALPHA_MASK) >>> 24) + + pshared.lr * ((pshared.cLR & ALPHA_MASK) >>> 24)) << PREC_ALPHA_SHIFT) & ALPHA_MASK; + + blendedColor = blendFunc(destColor, (pshared.a | pshared.r | pshared.g | pshared.b)); + + destPixels[idx] = (blendedColor & RED_MASK) >>> 16; + destPixels[idx + 1] = (blendedColor & GREEN_MASK) >>> 8; + destPixels[idx + 2] = (blendedColor & BLUE_MASK); + destPixels[idx + 3] = (blendedColor & ALPHA_MASK) >>> 24; + + pshared.sX += dx; + } + destOffset += screenW; + pshared.srcYOffset += dy; + } + }; + + //////////////////////////////////////////////////////////////////////////// + // Font handling + //////////////////////////////////////////////////////////////////////////// + + /** + * loadFont() Loads a font into a variable of type PFont. + * + * @param {String} name filename of the font to load + * @param {int|float} size option font size (used internally) + * + * @returns {PFont} new PFont object + * + * @see #PFont + * @see #textFont + * @see #text + * @see #createFont + */ + p.loadFont = function(name, size) { + if (name === undef) { + throw("font name required in loadFont."); + } + if (name.indexOf(".svg") === -1) { + if (size === undef) { + size = curTextFont.size; + } + return PFont.get(name, size); + } + // If the font is a glyph, calculate by SVG table + var font = p.loadGlyphs(name); + + return { + name: name, + css: '12px sans-serif', + glyph: true, + units_per_em: font.units_per_em, + horiz_adv_x: 1 / font.units_per_em * font.horiz_adv_x, + ascent: font.ascent, + descent: font.descent, + width: function(str) { + var width = 0; + var len = str.length; + for (var i = 0; i < len; i++) { + try { + width += parseFloat(p.glyphLook(p.glyphTable[name], str[i]).horiz_adv_x); + } + catch(e) { + Processing.debug(e); + } + } + return width / p.glyphTable[name].units_per_em; + } + }; + }; + + /** + * createFont() Loads a font into a variable of type PFont. + * Smooth and charset are ignored in Processing.js. + * + * @param {String} name filename of the font to load + * @param {int|float} size font size in pixels + * @param {boolean} smooth not used in Processing.js + * @param {char[]} charset not used in Processing.js + * + * @returns {PFont} new PFont object + * + * @see #PFont + * @see #textFont + * @see #text + * @see #loadFont + */ + p.createFont = function(name, size) { + // because Processing.js only deals with real fonts, + // createFont is simply a wrapper for loadFont/2 + return p.loadFont(name, size); + }; + + /** + * textFont() Sets the current font. + * + * @param {PFont} pfont the PFont to load as current text font + * @param {int|float} size optional font size in pixels + * + * @see #createFont + * @see #loadFont + * @see #PFont + * @see #text + */ + p.textFont = function(pfont, size) { + if (size !== undef) { + // If we're using an SVG glyph font, don't load from cache + if (!pfont.glyph) { + pfont = PFont.get(pfont.name, size); + } + curTextSize = size; + } + curTextFont = pfont; + curFontName = curTextFont.name; + curTextAscent = curTextFont.ascent; + curTextDescent = curTextFont.descent; + curTextLeading = curTextFont.leading; + var curContext = drawing.$ensureContext(); + curContext.font = curTextFont.css; + }; + + /** + * textSize() Sets the current font size in pixels. + * + * @param {int|float} size font size in pixels + * + * @see #textFont + * @see #loadFont + * @see #PFont + * @see #text + */ + p.textSize = function(size) { + curTextFont = PFont.get(curFontName, size); + curTextSize = size; + // recache metrics + curTextAscent = curTextFont.ascent; + curTextDescent = curTextFont.descent; + curTextLeading = curTextFont.leading; + var curContext = drawing.$ensureContext(); + curContext.font = curTextFont.css; + }; + + /** + * textAscent() returns the maximum height a character extends above the baseline of the + * current font at its current size, in pixels. + * + * @returns {float} height of the current font above the baseline, at its current size, in pixels + * + * @see #textDescent + */ + p.textAscent = function() { + return curTextAscent; + }; + + /** + * textDescent() returns the maximum depth a character will protrude below the baseline of + * the current font at its current size, in pixels. + * + * @returns {float} depth of the current font below the baseline, at its current size, in pixels + * + * @see #textAscent + */ + p.textDescent = function() { + return curTextDescent; + }; + + /** + * textLeading() Sets the current font's leading, which is the distance + * from baseline to baseline over consecutive lines, with additional vertical + * spacing taking into account. Usually this value is 1.2 or 1.25 times the + * textsize, but this value can be changed to effect vertically compressed + * or stretched text. + * + * @param {int|float} the desired baseline-to-baseline size in pixels + */ + p.textLeading = function(leading) { + curTextLeading = leading; + }; + + /** + * textAlign() Sets the current alignment for drawing text. + * + * @param {int} ALIGN Horizontal alignment, either LEFT, CENTER, or RIGHT + * @param {int} YALIGN optional vertical alignment, either TOP, BOTTOM, CENTER, or BASELINE + * + * @see #loadFont + * @see #PFont + * @see #text + */ + p.textAlign = function(xalign, yalign) { + horizontalTextAlignment = xalign; + verticalTextAlignment = yalign || PConstants.BASELINE; + }; + + /** + * toP5String converts things with arbitrary data type into + * string values, for text rendering. + * + * @param {any} any object that can be converted into a string + * + * @return {String} the string representation of the input + */ + function toP5String(obj) { + if(obj instanceof String) { + return obj; + } + if(typeof obj === 'number') { + // check if an int + if(obj === (0 | obj)) { + return obj.toString(); + } + return p.nf(obj, 0, 3); + } + if(obj === null || obj === undef) { + return ""; + } + return obj.toString(); + } + + /** + * textWidth() Calculates and returns the width of any character or text string in pixels. + * + * @param {char|String} str char or String to be measured + * + * @return {float} width of char or String in pixels + * + * @see #loadFont + * @see #PFont + * @see #text + * @see #textFont + */ + Drawing2D.prototype.textWidth = function(str) { + var lines = toP5String(str).split(/\r?\n/g), width = 0; + var i, linesCount = lines.length; + + curContext.font = curTextFont.css; + for (i = 0; i < linesCount; ++i) { + width = Math.max(width, curTextFont.measureTextWidth(lines[i])); + } + return width | 0; + }; + + Drawing3D.prototype.textWidth = function(str) { + var lines = toP5String(str).split(/\r?\n/g), width = 0; + var i, linesCount = lines.length; + if (textcanvas === undef) { + textcanvas = document.createElement("canvas"); + } + + var textContext = textcanvas.getContext("2d"); + textContext.font = curTextFont.css; + + for (i = 0; i < linesCount; ++i) { + width = Math.max(width, textContext.measureText(lines[i]).width); + } + return width | 0; + }; + + // A lookup table for characters that can not be referenced by Object + p.glyphLook = function(font, chr) { + try { + switch (chr) { + case "1": + return font.one; + case "2": + return font.two; + case "3": + return font.three; + case "4": + return font.four; + case "5": + return font.five; + case "6": + return font.six; + case "7": + return font.seven; + case "8": + return font.eight; + case "9": + return font.nine; + case "0": + return font.zero; + case " ": + return font.space; + case "$": + return font.dollar; + case "!": + return font.exclam; + case '"': + return font.quotedbl; + case "#": + return font.numbersign; + case "%": + return font.percent; + case "&": + return font.ampersand; + case "'": + return font.quotesingle; + case "(": + return font.parenleft; + case ")": + return font.parenright; + case "*": + return font.asterisk; + case "+": + return font.plus; + case ",": + return font.comma; + case "-": + return font.hyphen; + case ".": + return font.period; + case "/": + return font.slash; + case "_": + return font.underscore; + case ":": + return font.colon; + case ";": + return font.semicolon; + case "<": + return font.less; + case "=": + return font.equal; + case ">": + return font.greater; + case "?": + return font.question; + case "@": + return font.at; + case "[": + return font.bracketleft; + case "\\": + return font.backslash; + case "]": + return font.bracketright; + case "^": + return font.asciicircum; + case "`": + return font.grave; + case "{": + return font.braceleft; + case "|": + return font.bar; + case "}": + return font.braceright; + case "~": + return font.asciitilde; + // If the character is not 'special', access it by object reference + default: + return font[chr]; + } + } catch(e) { + Processing.debug(e); + } + }; + + // Print some text to the Canvas + Drawing2D.prototype.text$line = function(str, x, y, z, align) { + var textWidth = 0, xOffset = 0; + // If the font is a standard Canvas font... + if (!curTextFont.glyph) { + if (str && ("fillText" in curContext)) { + if (isFillDirty) { + curContext.fillStyle = p.color.toString(currentFillColor); + isFillDirty = false; + } + + // horizontal offset/alignment + if(align === PConstants.RIGHT || align === PConstants.CENTER) { + textWidth = curTextFont.measureTextWidth(str); + + if(align === PConstants.RIGHT) { + xOffset = -textWidth; + } else { // if(align === PConstants.CENTER) + xOffset = -textWidth/2; + } + } + + curContext.fillText(str, x+xOffset, y); + } + } else { + // If the font is a Batik SVG font... + var font = p.glyphTable[curFontName]; + saveContext(); + curContext.translate(x, y + curTextSize); + + // horizontal offset/alignment + if(align === PConstants.RIGHT || align === PConstants.CENTER) { + textWidth = font.width(str); + + if(align === PConstants.RIGHT) { + xOffset = -textWidth; + } else { // if(align === PConstants.CENTER) + xOffset = -textWidth/2; + } + } + + var upem = font.units_per_em, + newScale = 1 / upem * curTextSize; + + curContext.scale(newScale, newScale); + + for (var i=0, len=str.length; i < len; i++) { + // Test character against glyph table + try { + p.glyphLook(font, str[i]).draw(); + } catch(e) { + Processing.debug(e); + } + } + restoreContext(); + } + }; + + Drawing3D.prototype.text$line = function(str, x, y, z, align) { + // handle case for 3d text + if (textcanvas === undef) { + textcanvas = document.createElement("canvas"); + } + var oldContext = curContext; + curContext = textcanvas.getContext("2d"); + curContext.font = curTextFont.css; + var textWidth = curTextFont.measureTextWidth(str); + textcanvas.width = textWidth; + textcanvas.height = curTextSize; + curContext = textcanvas.getContext("2d"); // refreshes curContext + curContext.font = curTextFont.css; + curContext.textBaseline="top"; + + // paint on 2D canvas + Drawing2D.prototype.text$line(str,0,0,0,PConstants.LEFT); + + // use it as a texture + var aspect = textcanvas.width/textcanvas.height; + curContext = oldContext; + + curContext.bindTexture(curContext.TEXTURE_2D, textTex); + curContext.texImage2D(curContext.TEXTURE_2D, 0, curContext.RGBA, curContext.RGBA, curContext.UNSIGNED_BYTE, textcanvas); + curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR); + curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR); + curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_T, curContext.CLAMP_TO_EDGE); + curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_S, curContext.CLAMP_TO_EDGE); + // If we don't have a power of two texture, we can't mipmap it. + // curContext.generateMipmap(curContext.TEXTURE_2D); + + // horizontal offset/alignment + var xOffset = 0; + if (align === PConstants.RIGHT) { + xOffset = -textWidth; + } else if(align === PConstants.CENTER) { + xOffset = -textWidth/2; + } + var model = new PMatrix3D(); + var scalefactor = curTextSize * 0.5; + model.translate(x+xOffset-scalefactor/2, y-scalefactor, z); + model.scale(-aspect*scalefactor, -scalefactor, scalefactor); + model.translate(-1, -1, -1); + model.transpose(); + + var view = new PMatrix3D(); + view.scale(1, -1, 1); + view.apply(modelView.array()); + view.transpose(); + + curContext.useProgram(programObject2D); + vertexAttribPointer("aVertex2d", programObject2D, "aVertex", 3, textBuffer); + vertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord", 2, textureBuffer); + uniformi("uSampler2d", programObject2D, "uSampler", [0]); + + uniformi("uIsDrawingText2d", programObject2D, "uIsDrawingText", true); + + uniformMatrix("uModel2d", programObject2D, "uModel", false, model.array()); + uniformMatrix("uView2d", programObject2D, "uView", false, view.array()); + uniformf("uColor2d", programObject2D, "uColor", fillStyle); + curContext.bindBuffer(curContext.ELEMENT_ARRAY_BUFFER, indexBuffer); + curContext.drawElements(curContext.TRIANGLES, 6, curContext.UNSIGNED_SHORT, 0); + }; + + + /** + * unbounded text function (z is an optional argument) + */ + function text$4(str, x, y, z) { + var lines, linesCount; + if(str.indexOf('\n') < 0) { + lines = [str]; + linesCount = 1; + } else { + lines = str.split(/\r?\n/g); + linesCount = lines.length; + } + // handle text line-by-line + + var yOffset = 0; + if(verticalTextAlignment === PConstants.TOP) { + yOffset = curTextAscent + curTextDescent; + } else if(verticalTextAlignment === PConstants.CENTER) { + yOffset = curTextAscent/2 - (linesCount-1)*curTextLeading/2; + } else if(verticalTextAlignment === PConstants.BOTTOM) { + yOffset = -(curTextDescent + (linesCount-1)*curTextLeading); + } + + for(var i=0;i<linesCount;++i) { + var line = lines[i]; + drawing.text$line(line, x, y + yOffset, z, horizontalTextAlignment); + yOffset += curTextLeading; + } + } + + + /** + * box-bounded text function (z is an optional argument) + */ + function text$6(str, x, y, width, height, z) { + // 'fail' on 0-valued dimensions + if (str.length === 0 || width === 0 || height === 0) { + return; + } + // also 'fail' if the text height is larger than the bounding height + if(curTextSize > height) { + return; + } + + var spaceMark = -1; + var start = 0; + var lineWidth = 0; + var drawCommands = []; + + // run through text, character-by-character + for (var charPos=0, len=str.length; charPos < len; charPos++) + { + var currentChar = str[charPos]; + var spaceChar = (currentChar === " "); + var letterWidth = curTextFont.measureTextWidth(currentChar); + + // if we aren't looking at a newline, and the text still fits, keep processing + if (currentChar !== "\n" && (lineWidth + letterWidth <= width)) { + if (spaceChar) { spaceMark = charPos; } + lineWidth += letterWidth; + } + + // if we're looking at a newline, or the text no longer fits, push the section that fit into the drawcommand list + else + { + if (spaceMark + 1 === start) { + if(charPos>0) { + // Whole line without spaces so far. + spaceMark = charPos; + } else { + // 'fail', because the line can't even fit the first character + return; + } + } + + if (currentChar === "\n") { + drawCommands.push({text:str.substring(start, charPos), width: lineWidth}); + start = charPos + 1; + } else { + // current is not a newline, which means the line doesn't fit in box. push text. + // In Processing 1.5.1, the space is also pushed, so we push up to spaceMark+1, + // rather than up to spaceMark, as was the case for Processing 1.5 and earlier. + drawCommands.push({text:str.substring(start, spaceMark+1), width: lineWidth}); + start = spaceMark + 1; + } + + // newline + return + lineWidth = 0; + charPos = start - 1; + } + } + + // push the remaining text + if (start < len) { + drawCommands.push({text:str.substring(start), width: lineWidth}); + } + + // resolve horizontal alignment + var xOffset = 1, + yOffset = curTextAscent; + if (horizontalTextAlignment === PConstants.CENTER) { + xOffset = width/2; + } else if (horizontalTextAlignment === PConstants.RIGHT) { + xOffset = width; + } + + // resolve vertical alignment + var linesCount = drawCommands.length, + visibleLines = Math.min(linesCount, Math.floor(height/curTextLeading)); + if(verticalTextAlignment === PConstants.TOP) { + yOffset = curTextAscent + curTextDescent; + } else if(verticalTextAlignment === PConstants.CENTER) { + yOffset = (height/2) - curTextLeading * (visibleLines/2 - 1); + } else if(verticalTextAlignment === PConstants.BOTTOM) { + yOffset = curTextDescent + curTextLeading; + } + + var command, + drawCommand, + leading; + for (command = 0; command < linesCount; command++) { + leading = command * curTextLeading; + // stop if not enough space for one more line draw + if (yOffset + leading > height - curTextDescent) { + break; + } + drawCommand = drawCommands[command]; + drawing.text$line(drawCommand.text, x + xOffset, y + yOffset + leading, z, horizontalTextAlignment); + } + } + + /** + * text() Draws text to the screen. + * + * @param {String|char|int|float} data the alphanumeric symbols to be displayed + * @param {int|float} x x-coordinate of text + * @param {int|float} y y-coordinate of text + * @param {int|float} z optional z-coordinate of text + * @param {String} stringdata optional letters to be displayed + * @param {int|float} width optional width of text box + * @param {int|float} height optional height of text box + * + * @see #textAlign + * @see #textMode + * @see #loadFont + * @see #PFont + * @see #textFont + */ + p.text = function() { + if (textMode === PConstants.SHAPE) { + // TODO: requires beginRaw function + return; + } + if (arguments.length === 3) { // for text( str, x, y) + text$4(toP5String(arguments[0]), arguments[1], arguments[2], 0); + } else if (arguments.length === 4) { // for text( str, x, y, z) + text$4(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3]); + } else if (arguments.length === 5) { // for text( str, x, y , width, height) + text$6(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3], arguments[4], 0); + } else if (arguments.length === 6) { // for text( stringdata, x, y , width, height, z) + text$6(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); + } + }; + + /** + * Sets the way text draws to the screen. In the default configuration (the MODEL mode), it's possible to rotate, + * scale, and place letters in two and three dimensional space. <br /><br /> Changing to SCREEN mode draws letters + * directly to the front of the window and greatly increases rendering quality and speed when used with the P2D and + * P3D renderers. textMode(SCREEN) with OPENGL and JAVA2D (the default) renderers will generally be slower, though + * pixel accurate with P2D and P3D. With textMode(SCREEN), the letters draw at the actual size of the font (in pixels) + * and therefore calls to <b>textSize()</b> will not affect the size of the letters. To create a font at the size you + * desire, use the "Create font..." option in the Tools menu, or use the createFont() function. When using textMode(SCREEN), + * any z-coordinate passed to a text() command will be ignored, because your computer screen is...flat! + * + * @param {int} MODE Either MODEL, SCREEN or SHAPE (not yet supported) + * + * @see loadFont + * @see PFont + * @see text + * @see textFont + * @see createFont + */ + p.textMode = function(mode){ + textMode = mode; + }; + + // Load Batik SVG Fonts and parse to pre-def objects for quick rendering + p.loadGlyphs = function(url) { + var x, y, cx, cy, nx, ny, d, a, lastCom, lenC, horiz_adv_x, getXY = '[0-9\\-]+', path; + + // Return arrays of SVG commands and coords + // get this to use p.matchAll() - will need to work around the lack of null return + var regex = function(needle, hay) { + var i = 0, + results = [], + latest, regexp = new RegExp(needle, "g"); + latest = results[i] = regexp.exec(hay); + while (latest) { + i++; + latest = results[i] = regexp.exec(hay); + } + return results; + }; + + var buildPath = function(d) { + var c = regex("[A-Za-z][0-9\\- ]+|Z", d); + var beforePathDraw = function() { + saveContext(); + return drawing.$ensureContext(); + }; + var afterPathDraw = function() { + executeContextFill(); + executeContextStroke(); + restoreContext(); + }; + + // Begin storing path object + path = "return {draw:function(){var curContext=beforePathDraw();curContext.beginPath();"; + + x = 0; + y = 0; + cx = 0; + cy = 0; + nx = 0; + ny = 0; + d = 0; + a = 0; + lastCom = ""; + lenC = c.length - 1; + + // Loop through SVG commands translating to canvas eqivs functions in path object + for (var j = 0; j < lenC; j++) { + var com = c[j][0], xy = regex(getXY, com); + + switch (com[0]) { + case "M": + //curContext.moveTo(x,-y); + x = parseFloat(xy[0][0]); + y = parseFloat(xy[1][0]); + path += "curContext.moveTo(" + x + "," + (-y) + ");"; + break; + + case "L": + //curContext.lineTo(x,-y); + x = parseFloat(xy[0][0]); + y = parseFloat(xy[1][0]); + path += "curContext.lineTo(" + x + "," + (-y) + ");"; + break; + + case "H": + //curContext.lineTo(x,-y) + x = parseFloat(xy[0][0]); + path += "curContext.lineTo(" + x + "," + (-y) + ");"; + break; + + case "V": + //curContext.lineTo(x,-y); + y = parseFloat(xy[0][0]); + path += "curContext.lineTo(" + x + "," + (-y) + ");"; + break; + + case "T": + //curContext.quadraticCurveTo(cx,-cy,nx,-ny); + nx = parseFloat(xy[0][0]); + ny = parseFloat(xy[1][0]); + + if (lastCom === "Q" || lastCom === "T") { + d = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(cy - y, 2)); + a = Math.PI + Math.atan2(cx - x, cy - y); + cx = x + (Math.sin(a) * (d)); + cy = y + (Math.cos(a) * (d)); + } else { + cx = x; + cy = y; + } + + path += "curContext.quadraticCurveTo(" + cx + "," + (-cy) + "," + nx + "," + (-ny) + ");"; + x = nx; + y = ny; + break; + + case "Q": + //curContext.quadraticCurveTo(cx,-cy,nx,-ny); + cx = parseFloat(xy[0][0]); + cy = parseFloat(xy[1][0]); + nx = parseFloat(xy[2][0]); + ny = parseFloat(xy[3][0]); + path += "curContext.quadraticCurveTo(" + cx + "," + (-cy) + "," + nx + "," + (-ny) + ");"; + x = nx; + y = ny; + break; + + case "Z": + //curContext.closePath(); + path += "curContext.closePath();"; + break; + } + lastCom = com[0]; + } + + path += "afterPathDraw();"; + path += "curContext.translate(" + horiz_adv_x + ",0);"; + path += "}}"; + + return ((new Function("beforePathDraw", "afterPathDraw", path))(beforePathDraw, afterPathDraw)); + }; + + // Parse SVG font-file into block of Canvas commands + var parseSVGFont = function(svg) { + // Store font attributes + var font = svg.getElementsByTagName("font"); + p.glyphTable[url].horiz_adv_x = font[0].getAttribute("horiz-adv-x"); + + var font_face = svg.getElementsByTagName("font-face")[0]; + p.glyphTable[url].units_per_em = parseFloat(font_face.getAttribute("units-per-em")); + p.glyphTable[url].ascent = parseFloat(font_face.getAttribute("ascent")); + p.glyphTable[url].descent = parseFloat(font_face.getAttribute("descent")); + + var glyph = svg.getElementsByTagName("glyph"), + len = glyph.length; + + // Loop through each glyph in the SVG + for (var i = 0; i < len; i++) { + // Store attributes for this glyph + var unicode = glyph[i].getAttribute("unicode"); + var name = glyph[i].getAttribute("glyph-name"); + horiz_adv_x = glyph[i].getAttribute("horiz-adv-x"); + if (horiz_adv_x === null) { + horiz_adv_x = p.glyphTable[url].horiz_adv_x; + } + d = glyph[i].getAttribute("d"); + // Split path commands in glpyh + if (d !== undef) { + path = buildPath(d); + // Store glyph data to table object + p.glyphTable[url][name] = { + name: name, + unicode: unicode, + horiz_adv_x: horiz_adv_x, + draw: path.draw + }; + } + } // finished adding glyphs to table + }; + + // Load and parse Batik SVG font as XML into a Processing Glyph object + var loadXML = function() { + var xmlDoc; + + try { + xmlDoc = document.implementation.createDocument("", "", null); + } + catch(e_fx_op) { + Processing.debug(e_fx_op.message); + return; + } + + try { + xmlDoc.async = false; + xmlDoc.load(url); + parseSVGFont(xmlDoc.getElementsByTagName("svg")[0]); + } + catch(e_sf_ch) { + // Google Chrome, Safari etc. + Processing.debug(e_sf_ch); + try { + var xmlhttp = new window.XMLHttpRequest(); + xmlhttp.open("GET", url, false); + xmlhttp.send(null); + parseSVGFont(xmlhttp.responseXML.documentElement); + } + catch(e) { + Processing.debug(e_sf_ch); + } + } + }; + + // Create a new object in glyphTable to store this font + p.glyphTable[url] = {}; + + // Begin loading the Batik SVG font... + loadXML(url); + + // Return the loaded font for attribute grabbing + return p.glyphTable[url]; + }; + + /** + * Gets the sketch parameter value. The parameter can be defined as the canvas attribute with + * the "data-processing-" prefix or provided in the pjs directive (e.g. param-test="52"). + * The function tries the canvas attributes, then the pjs directive content. + * + * @param {String} name The name of the param to read. + * + * @returns {String} The parameter value, or null if parameter is not defined. + */ + p.param = function(name) { + // trying attribute that was specified in CANVAS + var attributeName = "data-processing-" + name; + if (curElement.hasAttribute(attributeName)) { + return curElement.getAttribute(attributeName); + } + // trying child PARAM elements of the CANVAS + for (var i = 0, len = curElement.childNodes.length; i < len; ++i) { + var item = curElement.childNodes.item(i); + if (item.nodeType !== 1 || item.tagName.toLowerCase() !== "param") { + continue; + } + if (item.getAttribute("name") === name) { + return item.getAttribute("value"); + } + } + // fallback to default params + if (curSketch.params.hasOwnProperty(name)) { + return curSketch.params[name]; + } + return null; + }; + + //////////////////////////////////////////////////////////////////////////// + // 2D/3D methods wiring utils + //////////////////////////////////////////////////////////////////////////// + function wireDimensionalFunctions(mode) { + // Drawing2D/Drawing3D + if (mode === '3D') { + drawing = new Drawing3D(); + } else if (mode === '2D') { + drawing = new Drawing2D(); + } else { + drawing = new DrawingPre(); + } + + // Wire up functions (Use DrawingPre properties names) + for (var i in DrawingPre.prototype) { + if (DrawingPre.prototype.hasOwnProperty(i) && i.indexOf("$") < 0) { + p[i] = drawing[i]; + } + } + + // Run initialization + drawing.$init(); + } + + function createDrawingPreFunction(name) { + return function() { + wireDimensionalFunctions("2D"); + return drawing[name].apply(this, arguments); + }; + } + DrawingPre.prototype.translate = createDrawingPreFunction("translate"); + DrawingPre.prototype.transform = createDrawingPreFunction("transform"); + DrawingPre.prototype.scale = createDrawingPreFunction("scale"); + DrawingPre.prototype.pushMatrix = createDrawingPreFunction("pushMatrix"); + DrawingPre.prototype.popMatrix = createDrawingPreFunction("popMatrix"); + DrawingPre.prototype.resetMatrix = createDrawingPreFunction("resetMatrix"); + DrawingPre.prototype.applyMatrix = createDrawingPreFunction("applyMatrix"); + DrawingPre.prototype.rotate = createDrawingPreFunction("rotate"); + DrawingPre.prototype.rotateZ = createDrawingPreFunction("rotateZ"); + DrawingPre.prototype.shearX = createDrawingPreFunction("shearX"); + DrawingPre.prototype.shearY = createDrawingPreFunction("shearY"); + DrawingPre.prototype.redraw = createDrawingPreFunction("redraw"); + DrawingPre.prototype.toImageData = createDrawingPreFunction("toImageData"); + DrawingPre.prototype.ambientLight = createDrawingPreFunction("ambientLight"); + DrawingPre.prototype.directionalLight = createDrawingPreFunction("directionalLight"); + DrawingPre.prototype.lightFalloff = createDrawingPreFunction("lightFalloff"); + DrawingPre.prototype.lightSpecular = createDrawingPreFunction("lightSpecular"); + DrawingPre.prototype.pointLight = createDrawingPreFunction("pointLight"); + DrawingPre.prototype.noLights = createDrawingPreFunction("noLights"); + DrawingPre.prototype.spotLight = createDrawingPreFunction("spotLight"); + DrawingPre.prototype.beginCamera = createDrawingPreFunction("beginCamera"); + DrawingPre.prototype.endCamera = createDrawingPreFunction("endCamera"); + DrawingPre.prototype.frustum = createDrawingPreFunction("frustum"); + DrawingPre.prototype.box = createDrawingPreFunction("box"); + DrawingPre.prototype.sphere = createDrawingPreFunction("sphere"); + DrawingPre.prototype.ambient = createDrawingPreFunction("ambient"); + DrawingPre.prototype.emissive = createDrawingPreFunction("emissive"); + DrawingPre.prototype.shininess = createDrawingPreFunction("shininess"); + DrawingPre.prototype.specular = createDrawingPreFunction("specular"); + DrawingPre.prototype.fill = createDrawingPreFunction("fill"); + DrawingPre.prototype.stroke = createDrawingPreFunction("stroke"); + DrawingPre.prototype.strokeWeight = createDrawingPreFunction("strokeWeight"); + DrawingPre.prototype.smooth = createDrawingPreFunction("smooth"); + DrawingPre.prototype.noSmooth = createDrawingPreFunction("noSmooth"); + DrawingPre.prototype.point = createDrawingPreFunction("point"); + DrawingPre.prototype.vertex = createDrawingPreFunction("vertex"); + DrawingPre.prototype.endShape = createDrawingPreFunction("endShape"); + DrawingPre.prototype.bezierVertex = createDrawingPreFunction("bezierVertex"); + DrawingPre.prototype.curveVertex = createDrawingPreFunction("curveVertex"); + DrawingPre.prototype.curve = createDrawingPreFunction("curve"); + DrawingPre.prototype.line = createDrawingPreFunction("line"); + DrawingPre.prototype.bezier = createDrawingPreFunction("bezier"); + DrawingPre.prototype.rect = createDrawingPreFunction("rect"); + DrawingPre.prototype.ellipse = createDrawingPreFunction("ellipse"); + DrawingPre.prototype.background = createDrawingPreFunction("background"); + DrawingPre.prototype.image = createDrawingPreFunction("image"); + DrawingPre.prototype.textWidth = createDrawingPreFunction("textWidth"); + DrawingPre.prototype.text$line = createDrawingPreFunction("text$line"); + DrawingPre.prototype.$ensureContext = createDrawingPreFunction("$ensureContext"); + DrawingPre.prototype.$newPMatrix = createDrawingPreFunction("$newPMatrix"); + + DrawingPre.prototype.size = function(aWidth, aHeight, aMode) { + wireDimensionalFunctions(aMode === PConstants.WEBGL ? "3D" : "2D"); + p.size(aWidth, aHeight, aMode); + }; + + DrawingPre.prototype.$init = noop; + + Drawing2D.prototype.$init = function() { + // Setup default 2d canvas context. + // Moving this here removes the number of times we need to check the 3D variable + p.size(p.width, p.height); + + curContext.lineCap = 'round'; + + // Set default stroke and fill color + p.noSmooth(); + p.disableContextMenu(); + }; + Drawing3D.prototype.$init = function() { + // For ref/perf test compatibility until those are fixed + p.use3DContext = true; + p.disableContextMenu(); + }; + + DrawingShared.prototype.$ensureContext = function() { + return curContext; + }; + + ////////////////////////////////////////////////////////////////////////// + // Keyboard Events + ////////////////////////////////////////////////////////////////////////// + + // In order to catch key events in a canvas, it needs to be "specially focusable", + // by assigning it a tabindex. If no tabindex is specified on-page, set this to 0. + if (!curElement.getAttribute("tabindex")) { + curElement.setAttribute("tabindex", 0); + } + + function getKeyCode(e) { + var code = e.which || e.keyCode; + switch (code) { + case 13: // ENTER + return 10; + case 91: // META L (Saf/Mac) + case 93: // META R (Saf/Mac) + case 224: // META (FF/Mac) + return 157; + case 57392: // CONTROL (Op/Mac) + return 17; + case 46: // DELETE + return 127; + case 45: // INSERT + return 155; + } + return code; + } + + function getKeyChar(e) { + var c = e.which || e.keyCode; + var anyShiftPressed = e.shiftKey || e.ctrlKey || e.altKey || e.metaKey; + switch (c) { + case 13: + c = anyShiftPressed ? 13 : 10; // RETURN vs ENTER (Mac) + break; + case 8: + c = anyShiftPressed ? 127 : 8; // DELETE vs BACKSPACE (Mac) + break; + } + return new Char(c); + } + + function suppressKeyEvent(e) { + if (typeof e.preventDefault === "function") { + e.preventDefault(); + } else if (typeof e.stopPropagation === "function") { + e.stopPropagation(); + } + return false; + } + + function updateKeyPressed() { + var ch; + for (ch in pressedKeysMap) { + if (pressedKeysMap.hasOwnProperty(ch)) { + p.__keyPressed = true; + return; + } + } + p.__keyPressed = false; + } + + function resetKeyPressed() { + p.__keyPressed = false; + pressedKeysMap = []; + lastPressedKeyCode = null; + } + + function simulateKeyTyped(code, c) { + pressedKeysMap[code] = c; + lastPressedKeyCode = null; + p.key = c; + p.keyCode = code; + p.keyPressed(); + p.keyCode = 0; + p.keyTyped(); + updateKeyPressed(); + } + + function handleKeydown(e) { + var code = getKeyCode(e); + if (code === PConstants.DELETE) { + simulateKeyTyped(code, new Char(127)); + return; + } + if (codedKeys.indexOf(code) < 0) { + lastPressedKeyCode = code; + return; + } + var c = new Char(PConstants.CODED); + p.key = c; + p.keyCode = code; + pressedKeysMap[code] = c; + p.keyPressed(); + lastPressedKeyCode = null; + updateKeyPressed(); + return suppressKeyEvent(e); + } + + function handleKeypress(e) { + if (lastPressedKeyCode === null) { + return; // processed in handleKeydown + } + var code = lastPressedKeyCode, c = getKeyChar(e); + simulateKeyTyped(code, c); + return suppressKeyEvent(e); + } + + function handleKeyup(e) { + var code = getKeyCode(e), c = pressedKeysMap[code]; + if (c === undef) { + return; // no keyPressed event was generated. + } + p.key = c; + p.keyCode = code; + p.keyReleased(); + delete pressedKeysMap[code]; + updateKeyPressed(); + } + + // Send aCode Processing syntax to be converted to JavaScript + if (!pgraphicsMode) { + if (aCode instanceof Processing.Sketch) { + // Use sketch as is + curSketch = aCode; + } else if (typeof aCode === "function") { + // Wrap function with default sketch parameters + curSketch = new Processing.Sketch(aCode); + } else if (!aCode) { + // Empty sketch + curSketch = new Processing.Sketch(function (){}); + } else { + //#if PARSER + // Compile the code + curSketch = Processing.compile(aCode); + //#else + // throw "PJS compile is not supported"; + //#endif + } + + // Expose internal field for diagnostics and testing + p.externals.sketch = curSketch; + + wireDimensionalFunctions(); + + // the onfocus and onblur events are handled in two parts. + // 1) the p.focused property is handled per sketch + curElement.onfocus = function() { + p.focused = true; + }; + + curElement.onblur = function() { + p.focused = false; + if (!curSketch.options.globalKeyEvents) { + resetKeyPressed(); + } + }; + + // 2) looping status is handled per page, based on the pauseOnBlur @pjs directive + if (curSketch.options.pauseOnBlur) { + attachEventHandler(window, 'focus', function() { + if (doLoop) { + p.loop(); + } + }); + + attachEventHandler(window, 'blur', function() { + if (doLoop && loopStarted) { + p.noLoop(); + doLoop = true; // make sure to keep this true after the noLoop call + } + resetKeyPressed(); + }); + } + + // if keyboard events should be handled globally, the listeners should + // be bound to the document window, rather than to the current canvas + var keyTrigger = curSketch.options.globalKeyEvents ? window : curElement; + attachEventHandler(keyTrigger, "keydown", handleKeydown); + attachEventHandler(keyTrigger, "keypress", handleKeypress); + attachEventHandler(keyTrigger, "keyup", handleKeyup); + + // Step through the libraries that were attached at doc load... + for (var i in Processing.lib) { + if (Processing.lib.hasOwnProperty(i)) { + if(Processing.lib[i].hasOwnProperty("attach")) { + // use attach function if present + Processing.lib[i].attach(p); + } else if(Processing.lib[i] instanceof Function) { + // Init the libraries in the context of this p_instance (legacy) + Processing.lib[i].call(this); + } + } + } + + // sketch execute test interval, used to reschedule + // an execute when preloads have not yet finished. + var retryInterval = 100; + + var executeSketch = function(processing) { + // Don't start until all specified images and fonts in the cache are preloaded + if (!(curSketch.imageCache.pending || PFont.preloading.pending(retryInterval))) { + // the opera preload cache can only be cleared once we start + if (window.opera) { + var link, + element, + operaCache=curSketch.imageCache.operaCache; + for (link in operaCache) { + if(operaCache.hasOwnProperty(link)) { + element = operaCache[link]; + if (element !== null) { + document.body.removeChild(element); + } + delete(operaCache[link]); + } + } + } + + curSketch.attach(processing, defaultScope); + + // pass a reference to the p instance for this sketch. + curSketch.onLoad(processing); + + // Run void setup() + if (processing.setup) { + processing.setup(); + // if any transforms were performed in setup reset to identity matrix + // so draw loop is unpolluted + processing.resetMatrix(); + curSketch.onSetup(); + } + + // some pixels can be cached, flushing + resetContext(); + + if (processing.draw) { + if (!doLoop) { + processing.redraw(); + } else { + processing.loop(); + } + } + } else { + window.setTimeout(function() { executeSketch(processing); }, retryInterval); + } + }; + + // Only store an instance of non-createGraphics instances. + addInstance(this); + + // The parser adds custom methods to the processing context + // this renames p to processing so these methods will run + executeSketch(p); + } else { + // No executable sketch was specified + // or called via createGraphics + curSketch = new Processing.Sketch(); + + wireDimensionalFunctions(); + + // Hack to make PGraphics work again after splitting size() + p.size = function(w, h, render) { + if (render && render === PConstants.WEBGL) { + wireDimensionalFunctions('3D'); + } else { + wireDimensionalFunctions('2D'); + } + + p.size(w, h, render); + }; + } + }; + + // Place-holder for overridable debugging function + Processing.debug = (function() { + if ("console" in window) { + return function(msg) { + window.console.log('Processing.js: ' + msg); + }; + } + return noop; + }()); + + // bind prototype + Processing.prototype = defaultScope; + + /** + * instance store and lookup + */ + Processing.instances = processingInstances; + Processing.getInstanceById = function(name) { + return processingInstances[processingInstanceIds[name]]; + }; + + // Unsupported Processing File and I/O operations. + (function(Processing) { + var unsupportedP5 = ("open() createOutput() createInput() BufferedReader selectFolder() " + + "dataPath() createWriter() selectOutput() beginRecord() " + + "saveStream() endRecord() selectInput() saveBytes() createReader() " + + "beginRaw() endRaw() PrintWriter delay()").split(" "), + count = unsupportedP5.length, + prettyName, + p5Name; + + function createUnsupportedFunc(n) { + return function() { + throw "Processing.js does not support " + n + "."; + }; + } + + while (count--) { + prettyName = unsupportedP5[count]; + p5Name = prettyName.replace("()", ""); + Processing[p5Name] = createUnsupportedFunc(prettyName); + } + }(defaultScope)); + + // we're done. Return our object. + return Processing; +}; + +},{}],28:[function(require,module,exports){ +// Base source files +var source = { + virtEquals: require("./Helpers/virtEquals"), + virtHashCode: require("./Helpers/virtHashCode"), + ObjectIterator: require("./Helpers/ObjectIterator"), + PConstants: require("./Helpers/PConstants"), + ArrayList: require("./Objects/ArrayList"), + HashMap: require("./Objects/HashMap"), + PVector: require("./Objects/PVector"), + PFont: require("./Objects/PFont"), + Char: require("./Objects/Char"), + XMLAttribute: require("./Objects/XMLAttribute"), + XMLElement: require("./Objects/XMLElement"), + PMatrix2D: require("./Objects/PMatrix2D"), + PMatrix3D: require("./Objects/PMatrix3D"), + PShape: require("./Objects/PShape"), + colors: require("./Objects/webcolors"), + PShapeSVG: require("./Objects/PShapeSVG"), + CommonFunctions: require("./P5Functions/commonFunctions"), + defaultScope: require("./Helpers/defaultScope"), + Processing: require("./Processing"), + setupParser: require("./Parser/Parser"), + finalize: require("./Helpers/finalizeProcessing") +}; + +// Additional code that gets tacked onto "p" during +// instantiation of a Processing sketch. +source.extend = { + withMath: require("./P5Functions/Math.js"), + withProxyFunctions: require("./P5Functions/JavaProxyFunctions")(source.virtHashCode, source.virtEquals), + withTouch: require("./P5Functions/touchmouse"), + withCommonFunctions: source.CommonFunctions.withCommonFunctions +}; + +/** + * Processing.js building function + */ +module.exports = function buildProcessingJS(Browser, testHarness) { + var noop = function(){}, + virtEquals = source.virtEquals, + virtHashCode = source.virtHashCode, + PConstants = source.PConstants, + CommonFunctions = source.CommonFunctions, + ObjectIterator = source.ObjectIterator, + Char = source.Char, + XMLAttribute = source.XMLAttribute(), + + ArrayList = source.ArrayList({ + virtHashCode: virtHashCode, + virtEquals: virtEquals + }), + + HashMap = source.HashMap({ + virtHashCode: virtHashCode, + virtEquals: virtEquals + }), + + PVector = source.PVector({ + PConstants: PConstants + }), + + PFont = source.PFont({ + Browser: Browser, + noop: noop + }), + + XMLElement = source.XMLElement({ + Browser: Browser, + XMLAttribute: XMLAttribute + }), + + PMatrix2D = source.PMatrix2D({ + p:CommonFunctions + }), + + PMatrix3D = source.PMatrix3D({ + p:CommonFunctions + }), + + PShape = source.PShape({ + PConstants: PConstants, + PMatrix2D: PMatrix2D, + PMatrix3D: PMatrix3D + }), + + PShapeSVG = source.PShapeSVG({ + CommonFunctions: CommonFunctions, + PConstants: PConstants, + PShape: PShape, + XMLElement: XMLElement, + colors: source.colors + }), + + defaultScope = source.defaultScope({ + ArrayList: ArrayList, + HashMap: HashMap, + PVector: PVector, + PFont: PFont, + PShapeSVG: PShapeSVG, + ObjectIterator: ObjectIterator, + PConstants: PConstants, + Char: Char, + XMLElement: XMLElement, + XML: XMLElement + }), + + Processing = source.Processing({ + defaultScope: defaultScope, + Browser: Browser, + extend: source.extend, + noop: noop + }); + + // set up the Processing syntax parser + Processing = source.setupParser(Processing, { + Browser: Browser, + aFunctions: testHarness, + defaultScope: defaultScope + }); + + // finalise the Processing object + Processing = source.finalize(Processing, { + version: require('../package.json').version, + isDomPresent: false || Browser.isDomPresent, + window: Browser.window, + document: Browser.document, + noop: noop + }); + + // done. + return Processing; +}; + +},{"../package.json":2,"./Helpers/ObjectIterator":3,"./Helpers/PConstants":4,"./Helpers/defaultScope":6,"./Helpers/finalizeProcessing":7,"./Helpers/virtEquals":8,"./Helpers/virtHashCode":9,"./Objects/ArrayList":10,"./Objects/Char":11,"./Objects/HashMap":12,"./Objects/PFont":13,"./Objects/PMatrix2D":14,"./Objects/PMatrix3D":15,"./Objects/PShape":16,"./Objects/PShapeSVG":17,"./Objects/PVector":18,"./Objects/XMLAttribute":19,"./Objects/XMLElement":20,"./Objects/webcolors":21,"./P5Functions/JavaProxyFunctions":22,"./P5Functions/Math.js":23,"./P5Functions/commonFunctions":24,"./P5Functions/touchmouse":25,"./Parser/Parser":26,"./Processing":27}]},{},[1]); diff --git a/js/riffwave.js b/js/riffwave.js new file mode 100644 index 0000000..3c0b5e4 --- /dev/null +++ b/js/riffwave.js @@ -0,0 +1,130 @@ +/* + * RIFFWAVE.js v0.03 - Audio encoder for HTML5 <audio> elements. + * Copyleft 2011 by Pedro Ladaria <pedro.ladaria at Gmail dot com> + * + * Public Domain + * + * Changelog: + * + * 0.01 - First release + * 0.02 - New faster base64 encoding + * 0.03 - Support for 16bit samples + * + * Notes: + * + * 8 bit data is unsigned: 0..255 + * 16 bit data is signed: −32,768..32,767 + * + */ + +var FastBase64 = { + + chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + encLookup: [], + + Init: function() { + for (var i=0; i<4096; i++) { + this.encLookup[i] = this.chars[i >> 6] + this.chars[i & 0x3F]; + } + }, + + Encode: function(src) { + var len = src.length; + var dst = ''; + var i = 0; + while (len > 2) { + n = (src[i] << 16) | (src[i+1]<<8) | src[i+2]; + dst+= this.encLookup[n >> 12] + this.encLookup[n & 0xFFF]; + len-= 3; + i+= 3; + } + if (len > 0) { + var n1= (src[i] & 0xFC) >> 2; + var n2= (src[i] & 0x03) << 4; + if (len > 1) n2 |= (src[++i] & 0xF0) >> 4; + dst+= this.chars[n1]; + dst+= this.chars[n2]; + if (len == 2) { + var n3= (src[i++] & 0x0F) << 2; + n3 |= (src[i] & 0xC0) >> 6; + dst+= this.chars[n3]; + } + if (len == 1) dst+= '='; + dst+= '='; + } + return dst; + } // end Encode + +} + +FastBase64.Init(); + +var RIFFWAVE = function(data) { + + this.data = []; // Array containing audio samples + this.wav = []; // Array containing the generated wave file + this.dataURI = ''; // http://en.wikipedia.org/wiki/Data_URI_scheme + + this.header = { // OFFS SIZE NOTES + chunkId : [0x52,0x49,0x46,0x46], // 0 4 "RIFF" = 0x52494646 + chunkSize : 0, // 4 4 36+SubChunk2Size = 4+(8+SubChunk1Size)+(8+SubChunk2Size) + format : [0x57,0x41,0x56,0x45], // 8 4 "WAVE" = 0x57415645 + subChunk1Id : [0x66,0x6d,0x74,0x20], // 12 4 "fmt " = 0x666d7420 + subChunk1Size: 16, // 16 4 16 for PCM + audioFormat : 1, // 20 2 PCM = 1 + numChannels : 1, // 22 2 Mono = 1, Stereo = 2... + sampleRate : 8000, // 24 4 8000, 44100... + byteRate : 0, // 28 4 SampleRate*NumChannels*BitsPerSample/8 + blockAlign : 0, // 32 2 NumChannels*BitsPerSample/8 + bitsPerSample: 8, // 34 2 8 bits = 8, 16 bits = 16 + subChunk2Id : [0x64,0x61,0x74,0x61], // 36 4 "data" = 0x64617461 + subChunk2Size: 0 // 40 4 data size = NumSamples*NumChannels*BitsPerSample/8 + }; + + function u32ToArray(i) { + return [i&0xFF, (i>>8)&0xFF, (i>>16)&0xFF, (i>>24)&0xFF]; + } + + function u16ToArray(i) { + return [i&0xFF, (i>>8)&0xFF]; + } + + function split16bitArray(data) { + var r = []; + var j = 0; + var len = data.length; + for (var i=0; i<len; i++) { + r[j++] = data[i] & 0xFF; + r[j++] = (data[i]>>8) & 0xFF; + } + return r; + } + + this.Make = function(data) { + if (data instanceof Array) this.data = data; + this.header.blockAlign = (this.header.numChannels * this.header.bitsPerSample) >> 3; + this.header.byteRate = this.header.blockAlign * this.sampleRate; + this.header.subChunk2Size = this.data.length * (this.header.bitsPerSample >> 3); + this.header.chunkSize = 36 + this.header.subChunk2Size; + + this.wav = this.header.chunkId.concat( + u32ToArray(this.header.chunkSize), + this.header.format, + this.header.subChunk1Id, + u32ToArray(this.header.subChunk1Size), + u16ToArray(this.header.audioFormat), + u16ToArray(this.header.numChannels), + u32ToArray(this.header.sampleRate), + u32ToArray(this.header.byteRate), + u16ToArray(this.header.blockAlign), + u16ToArray(this.header.bitsPerSample), + this.header.subChunk2Id, + u32ToArray(this.header.subChunk2Size), + (this.header.bitsPerSample == 16) ? split16bitArray(this.data) : this.data + ); + this.dataURI = 'data:audio/wav;base64,'+FastBase64.Encode(this.wav); + }; + + if (data instanceof Array) this.Make(data); + +}; // end RIFFWAVE diff --git a/js/shaperoller.js b/js/shaperoller.js new file mode 100644 index 0000000..f0360d6 --- /dev/null +++ b/js/shaperoller.js @@ -0,0 +1,172 @@ +var CIRCLE = 0; +var SQUARE_SHAPE = 1; +var points = []; +var currentAngle = 0; +var drawing = false; +var shapes = [CIRCLE, CIRCLE]; +var radii = [107, 17]; +var shapeNameList = ['circle', 'square']; +var speed = 0.5; + +function removeElementById(id) +{ + (function(x){x.parentNode.removeChild(x);})(document.getElementById(id)); +} + +function getNumShapes() +{ + var i = 0; + do + { + var x = document.getElementById("rt"+i); + i++; + } + while (x != null); + return i-1; +} + +function deleteShape(num) +{ + removeElementById("rt"+num); + removeElementById("st"+num); + removeElementById("shape"+num); + removeElementById("radius"+num); + removeElementById("del"+num); + removeElementById("br"+num); + +} + +function updateShapes() +{ + shapes = []; + radii = []; + var nShapes = getNumShapes(); + for (var i = 0; i < nShapes; i++) + { + var sel = document.getElementById("shape" + i); + var val = sel.options[sel.selectedIndex].value; + shapes.push(shapeNameList.indexOf(val)); + radii.push(document.getElementById("radius" + i).value); + } + speed = parseFloat(document.getElementById("speed").value); + +} + +function loadShapes() +{ + var nShapes = getNumShapes(); + for (var i = 0; i < nShapes; i++) + { + var sel = document.getElementById("shape" + i); + sel.selectedIndex = shapes[i]; + document.getElementById("radius" + i).value = radii[i]; + } + document.getElementById("speed").value = speed; +} + +function addShape() +{ + updateShapes(); + var nShapes = getNumShapes(); + var shapesDiv = document.getElementById("shapes"); + shapesDiv.innerHTML += '<span id="rt' + nShapes + '">Radius:</span> <input type="number" value="50" id="radius' + nShapes + '"> <span id="st' + nShapes + '">Shape:</span> ' + + '<select id="shape' + nShapes + '"><option value="circle">Circle</option><option value="square">Square</option></select> <button id="del' + nShapes + '" onclick="deleteShape(' + nShapes + ');">Delete</button><br id="br' + nShapes + '">'; + shapes.push(0); + radii.push(50); + loadShapes(); +} + +function startDrawing() +{ + drawing = true; + points = []; + updateShapes(); +} + +function squine(angle) +{ + angle %= TWO_PI; + if (angle >= QUARTER_PI && angle <= 3*QUARTER_PI) + return 1; + if (angle >= 5*QUARTER_PI && angle <= 7*QUARTER_PI) + return -1; + if (angle > 7*QUARTER_PI || angle < QUARTER_PI) + return map((angle+QUARTER_PI)%TWO_PI-QUARTER_PI, -QUARTER_PI, QUARTER_PI, -1, 1); + return -map(angle, 3*QUARTER_PI, 5*QUARTER_PI, -1, 1); +} + +function squos(angle) +{ + if (angle >= 7*QUARTER_PI || angle <= QUARTER_PI) + return 1; + if (angle >= 3*QUARTER_PI && angle <= 5*QUARTER_PI) + return -1; + if (angle > QUARTER_PI && angle < 3*QUARTER_PI) + return -map(angle, QUARTER_PI, 3*QUARTER_PI, -1, 1); + return map(angle, 5*QUARTER_PI, 7*QUARTER_PI, -1, 1); + +} + +function getPointOnShape(shape, radius, centerX, centerY, angle) +{ + if (shape == SQUARE_SHAPE) + return [radius*squos(angle)+centerX, radius*squine(angle)+centerY]; + else if (shape == CIRCLE) + return [radius*cos(angle)+centerX, radius*sin(angle)+centerY]; +} + + +function setup() +{ + createCanvas(700, 700).parent("canvasSpot"); + ellipseMode(CENTER); +} + +function draw() +{ + updateShapes(); + try{ + if (drawing) + { + background(255); + stroke(0); + noFill(); + + var centerX = width/2; + var centerY = height/2; + var nextPoint; + + for (var i = 0; i < shapes.length; i++) + { + + var angle = map(currentAngle%radii[i], 0, radii[i], 0, TWO_PI); + nextPoint = getPointOnShape(shapes[i], radii[i], centerX, centerY, angle); + centerX = nextPoint[0]; + centerY = nextPoint[1]; + + } + + + points.push([centerX, centerY]); + + + + if (points.length != 1 && dist(points[0][0], points[0][1], points[points.length-1][0], points[points.length-1][1]) < speed) + drawing = false; + + + for (var i = 1; i < points.length; i++) + { + line(points[i-1][0], points[i-1][1], points[i][0], points[i][1]); + } + + + currentAngle += speed; + if (drawing) + { + noStroke(); + fill(255, 0, 0); + ellipse(centerX, centerY, 7, 7); + } + }}catch(e){document.write(e + "<br>");} +}
\ No newline at end of file diff --git a/js/tree.js b/js/tree.js new file mode 100644 index 0000000..64cd2f3 --- /dev/null +++ b/js/tree.js @@ -0,0 +1,24 @@ + +function draw_branch(x, y, t) +{ + if (t > 8) + return; + var sz = (400 * pow(0.5, t)) * map(255-frameCount%256, 0, 255, 0, 1); + var angle = map(mouseX, 0, width, 0, HALF_PI) + t * map(mouseY, 0, height, -HALF_PI, HALF_PI); + line(x, y, x+cos(angle)*sz, y-sin(angle)*sz); + line(x, y, x-cos(angle)*sz, y-sin(angle)*sz); + draw_branch((x+cos(angle)*sz), (y-sin(angle)*sz), t+1); + draw_branch((x-cos(angle)*sz), (y-sin(angle)*sz), t+1); +} +function setup() +{ + + createCanvas(700, 700); + background(255); + draw_branch(width/2, height, 0); +} +function draw() +{ + background(255); + draw_branch(width/2, height, 0); +} diff --git a/js/treegenerator.js b/js/treegenerator.js new file mode 100644 index 0000000..225c639 --- /dev/null +++ b/js/treegenerator.js @@ -0,0 +1,37 @@ + +var size; +var start_angle; +var angle_decay; +function draw_branch(x, y, t) +{ + if (t > 8) + return; + var sz = size * pow(0.5, t); + var angle = start_angle + angle_decay * t; + line(x, y, x+cos(angle)*sz, y-sin(angle)*sz); + line(x, y, x-cos(angle)*sz, y-sin(angle)*sz); + draw_branch(x+cos(angle)*sz, y-sin(angle)*sz, t+1); + draw_branch(x-cos(angle)*sz, y-sin(angle)*sz, t+1); + +} +function setup() +{ + + createCanvas(700, 700); + stroke(0); +} + +function draw() +{ + size = parseFloat(document.getElementById("size").value); + start_angle = parseFloat(document.getElementById("angle").value); + angle_decay = parseFloat(document.getElementById("angledecay").value); + background(255); + draw_branch(width/2, height, 0); +} + +function saveTree() +{ + save("tree.png"); + +}
\ No newline at end of file |