You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

459 lines
15 KiB
JavaScript

/*
This file contains functions for drawing 2d primitives with a handy sketchy look in p5.js.
Author: Janneck Wullschleger in 07/2016
Web: http://itsjw.de
Mail: jw@itsjw.de
Updated: 24.02.2017 to use with a reference to the p5 instance.
Just put it in as param to the constructor.
Much of the source code is taken from the handy library for processing,
written by Jo Wood, giCentre, City University London based on an idea by Nikolaus Gradwohl.
The handy library is licensed under the GNU Lesser General Public License: http://www.gnu.org/licenses/.
*/
function Scribble(p) {
this.sketch = p || window;
this.bowing = 1;
this.roughness = 1;
this.maxOffset = 2;
this.numEllipseSteps = 9;
this.ellipseInc = (Math.PI*2)/this.numEllipseSteps;
this.getOffset = function( minVal, maxVal ) {
return this.roughness*(this.sketch.random()*(maxVal-minVal)+minVal);
}
this.buildEllipse = function( cx, cy, rx, ry, offset, overlap ) {
var radialOffset = this.getOffset( -0.5, 0.5 )-Math.PI/2;
this.sketch.beginShape();
this.sketch.curveVertex( this.getOffset( -offset, offset )+cx+0.9*rx*Math.cos( radialOffset-this.ellipseInc ),
this.getOffset( -offset, offset )+cy+0.9*ry*Math.sin( radialOffset-this.ellipseInc ) );
for ( var theta = radialOffset; theta < Math.PI*2+radialOffset-0.01; theta+=this.ellipseInc ) {
this.sketch.curveVertex( this.getOffset( -offset, offset )+cx+rx*Math.cos( theta ),
this.getOffset( -offset, offset )+cy+ry*Math.sin( theta ) );
}
this.sketch.curveVertex( this.getOffset( -offset, offset )+cx+rx*Math.cos( radialOffset+Math.PI*2+overlap*0.5 ),
this.getOffset( -offset, offset )+cy+ry*Math.sin( radialOffset+Math.PI*2+overlap*0.5 ) );
this.sketch.curveVertex( this.getOffset( -offset, offset )+cx+0.98*rx*Math.cos( radialOffset+overlap ),
this.getOffset( -offset, offset )+cy+0.98*ry*Math.sin( radialOffset+overlap ) );
this.sketch.curveVertex( this.getOffset( -offset, offset )+cx+0.9*rx*Math.cos( radialOffset+overlap*0.5 ),
this.getOffset( -offset, offset )+cy+0.9*ry*Math.sin( radialOffset+overlap*0.5 ) );
this.sketch.endShape();
}
this.getIntersectingLines = function( lineCoords, xCoords, yCoords ) {
var intersections = [];
var s1 = new Segment( lineCoords[0], lineCoords[1], lineCoords[2], lineCoords[3] );
for ( var i = 0; i < xCoords.length; i++ ) {
var s2 = new Segment( xCoords[i], yCoords[i], xCoords[(i+1)%xCoords.length], yCoords[(i+1)%xCoords.length] );
if ( s1.compare(s2) == Relation.INTERSECTS ) {
intersections.push( [s1.getIntersectionX(), s1.getIntersectionY()] );
}
}
return intersections;
}
this.scribbleLine = function( x1, y1, x2, y2 ) {
var lenSq = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2);
var offset = this.maxOffset;
if ( this.maxOffset*this.maxOffset*100 > lenSq ) {
offset = Math.sqrt( lenSq )/10;
}
var halfOffset = offset/2;
var divergePoint = 0.2 + this.sketch.random()*0.2;
var midDispX = this.bowing*this.maxOffset*(y2-y1)/200;
var midDispY = this.bowing*this.maxOffset*(x1-x2)/200;
midDispX = this.getOffset( -midDispX, midDispX );
midDispY = this.getOffset( -midDispY, midDispY );
this.sketch.noFill();
this.sketch.beginShape();
this.sketch.vertex( x1 + this.getOffset( -offset, offset ), y1 + this.getOffset( -offset, offset ) );
this.sketch.curveVertex(x1 + this.getOffset( -offset, offset ), y1 + this.getOffset( -offset, offset ) );
this.sketch.curveVertex(midDispX+x1+(x2 -x1)*divergePoint + this.getOffset( -offset, offset ), midDispY+y1 + (y2-y1)*divergePoint + this.getOffset( -offset, offset ) );
this.sketch.curveVertex(midDispX+x1+2*(x2-x1)*divergePoint + this.getOffset( -offset, offset ), midDispY+y1+ 2*(y2-y1)*divergePoint + this.getOffset( -offset,offset ) );
this.sketch.curveVertex(x2 + this.getOffset( -offset, offset ), y2 + this.getOffset( -offset, offset ) );
this.sketch.vertex( x2 + this.getOffset( -offset, offset ), y2 + this.getOffset( -offset, offset ) );
this.sketch.endShape();
this.sketch.beginShape();
this.sketch.vertex( x1 + this.getOffset( -halfOffset, halfOffset ), y1 + this.getOffset( -halfOffset, halfOffset ) );
this.sketch.curveVertex(x1 + this.getOffset( -halfOffset, halfOffset ), y1 + this.getOffset( -halfOffset, halfOffset ) );
this.sketch.curveVertex(midDispX+x1+(x2 -x1)*divergePoint + this.getOffset( -halfOffset, halfOffset ), midDispY+y1 + (y2-y1)*divergePoint + this.getOffset( -halfOffset, halfOffset ) );
this.sketch.curveVertex(midDispX+x1+2*(x2-x1)*divergePoint + this.getOffset( -halfOffset, halfOffset ), midDispY+y1+ 2*(y2-y1)*divergePoint + this.getOffset( -halfOffset, halfOffset ) );
this.sketch.curveVertex(x2 + this.getOffset( -halfOffset, halfOffset ), y2 + this.getOffset( -halfOffset, halfOffset ) );
this.sketch.vertex( x2 + this.getOffset( -halfOffset, halfOffset ), y2 + this.getOffset( -halfOffset, halfOffset ) );
this.sketch.endShape();
}
this.scribbleCurve = function( x1, y1, x2, y2, x3, y3, x4, y4 ) {
this.sketch.bezier( x1+this.getOffset( -2, 2 ), y1+this.getOffset( -2, 2 ),
x3+this.getOffset( -4, 4 ), y3+this.getOffset( -3, 3 ),
x4+this.getOffset( -3, 3 ), y4+this.getOffset( -3, 3 ),
x2+this.getOffset( -1, 1 ), y2+this.getOffset( -1, 1 ) );
this.sketch.bezier( x1+this.getOffset( -2, 2 ), y1+this.getOffset( -2, 2 ),
x3+this.getOffset( -3, 3 ), y3+this.getOffset( -3, 3 ),
x4+this.getOffset( -3, 3 ), y4+this.getOffset( -4, 4 ),
x2+this.getOffset( -2, 2 ), y2+this.getOffset( -2, 2 ) );
}
this.scribbleRect = function( x, y, w, h ) {
var halfWidth = w/2;
var halfHeight = h/2;
var left = Math.min( x-halfWidth, x+halfWidth );
var right = Math.max( x-halfWidth, x+halfWidth );
var top = Math.min( y-halfHeight, y+halfHeight );
var bottom = Math.max( y-halfHeight, y+halfHeight );
this.scribbleLine( left, top, right, top );
this.scribbleLine( right, top, right, bottom );
this.scribbleLine( right, bottom, left, bottom );
this.scribbleLine( left, bottom, left, top );
}
this.scribbleRoundedRect = function( x, y, w, h, radius ) {
var halfWidth = w/2;
var halfHeight = h/2;
if ( radius == 0 || radius > halfWidth || radius > halfHeight ) {
this.scribbleRect( x, y, w, h );
return;
}
var left = Math.min( x-halfWidth, x+halfWidth );
var right = Math.max( x-halfWidth, x+halfWidth );
var top = Math.min( y-halfHeight, y+halfHeight );
var bottom = Math.max( y-halfHeight, y+halfHeight );
this.scribbleLine( left+radius, top, right-radius, top, 1.5 );
this.scribbleLine( right, top+radius, right, bottom-radius, 1.5 );
this.scribbleLine( right-radius, bottom, left+radius, bottom, 1.5 );
this.scribbleLine( left, bottom-radius, left, top+radius, 1.5 );
this.scribbleCurve( left+radius, top, left, top+radius, left+radius*0.1, top+radius*0.1, left+radius*0.1, top+radius*0.1 );
this.scribbleCurve( right-radius, top, right, top+radius, right-radius*0.1, top+radius*0.1, right-radius*0.1, top+radius*0.1 );
this.scribbleCurve( left+radius, bottom, left, bottom-radius, left+radius*0.1, bottom-radius*0.1, left+radius*0.1, bottom-radius*0.1 );
this.scribbleCurve( right-radius, bottom, right, bottom-radius, right-radius*0.1, bottom-radius*0.1, right-radius*0.1, bottom-radius*0.1 );
}
this.scribbleEllipse = function( x, y, w, h ) {
var rx = Math.abs(w/2);
var ry = Math.abs(h/2);
rx += this.getOffset( -rx*0.05, rx*0.05 );
ry += this.getOffset( -ry*0.05, ry*0.05 );
this.buildEllipse( x, y, rx, ry, 1, this.ellipseInc*this.getOffset( 0.1, this.getOffset( 0.4, 1 ) ) );
this.buildEllipse( x, y, rx, ry, 1.5, 0 );
}
this.scribbleFilling = function( xCoords, yCoords, gap, angle ) {
if ((xCoords == null) || (yCoords == null) || (xCoords.length == 0) || (yCoords.length == 0)) {
return;
}
var hachureAngle = this.sketch.radians( angle%180 );
var cosAngle = Math.cos( hachureAngle );
var sinAngle = Math.sin( hachureAngle );
var tanAngle = Math.tan( hachureAngle );
var left = xCoords[0];
var right = xCoords[0];
var top = yCoords[0];
var bottom = yCoords[0];
for ( var i = 1; i < xCoords.length; i++ ) {
left = Math.min( left, xCoords[i] );
right = Math.max( right, xCoords[i] );
top = Math.min( top, yCoords[i] );
bottom = Math.max( bottom, yCoords[i] );
}
var it = new HachureIterator( top-1, bottom+1, left-1, right+1, gap, sinAngle, cosAngle, tanAngle );
var rectCoords = null;
while ( (rectCoords = it.getNextLine()) != null ) {
var lines = this.getIntersectingLines( rectCoords, xCoords, yCoords );
for ( var i = 0; i < lines.length; i+=2 ) {
if ( i < lines.length-1 ) {
var p1 = lines[i];
var p2 = lines[i+1];
this.scribbleLine( p1[0], p1[1], p2[0], p2[1], 2 );
}
}
}
}
}
function HachureIterator( _top, _bottom, _left, _right, _gap, _sinAngle, _cosAngle, _tanAngle ) {
var sinAngle = _sinAngle;
var tanAngle = _tanAngle;
var top = _top;
var bottom = _bottom;
var left = _left;
var right = _right;
var gap = _gap;
var pos;
var deltaX, hGap;
var sLeft, sRight;
if (Math.abs(sinAngle) < 0.0001) {
pos = left+gap;
} else if (Math.abs(sinAngle) > 0.9999) {
pos = top+gap;
} else {
deltaX = (bottom-top)*Math.abs(tanAngle);
pos = left-Math.abs(deltaX);
hGap = Math.abs(gap / _cosAngle);
sLeft = new Segment(left, bottom, left, top);
sRight = new Segment(right, bottom, right, top);
}
this.getNextLine = function() {
if (Math.abs(sinAngle) < 0.0001) {
if (pos < right) {
var line = [pos, top, pos, bottom];
pos += gap;
return line;
}
} else if (Math.abs(sinAngle) > 0.9999) {
if (pos<bottom) {
var line = [left, pos, right, pos];
pos += gap;
return line;
}
} else {
var xLower = pos-deltaX/2;
var xUpper = pos+deltaX/2;
var yLower = bottom;
var yUpper = top;
if (pos < right+deltaX) {
while (((xLower < left) && (xUpper < left)) || ((xLower > right) && (xUpper > right))) {
pos += hGap;
xLower = pos-deltaX/2;
xUpper = pos+deltaX/2;
if (pos > right+deltaX) {
return null;
}
}
var s = new Segment(xLower, yLower, xUpper, yUpper);
if (s.compare(sLeft) == Relation.INTERSECTS) {
xLower = s.getIntersectionX();
yLower = s.getIntersectionY();
}
if (s.compare(sRight) == Relation.INTERSECTS) {
xUpper = s.getIntersectionX();
yUpper = s.getIntersectionY();
}
if (tanAngle > 0) {
xLower = right-(xLower-left);
xUpper = right-(xUpper-left);
}
var line = [xLower, yLower, xUpper, yUpper];
pos += hGap;
return line;
}
}
return null;
}
}
function Segment( _x1, _y1, _x2, _y2 ) {
var x1 = _x1;
var y1 =_y1;
var x2 = _x2;
var y2 = _y2;
var a, b, c;
var undef;
var xi = Number.MAX_VALUE;
var yi = Number.MAX_VALUE;
a=y2-y1;
b=x1-x2;
c=x2*y1-x1*y2;
if ((a==0) && (b==0) && (c==0)) {
undef = true;
} else {
undef = false;
}
this.compare = function( otherSegment ) {
if ((this.isUndefined()) || (otherSegment.isUndefined())) {
return Relation.UNDEFINED;
}
var grad1 = Number.MAX_VALUE;
var grad2 = Number.MAX_VALUE;
var int1 = 0;
var int2 = 0;
if (Math.abs(b) > 0.00001) {
grad1 = -a/b;
int1 = -c/b;
}
if (Math.abs(otherSegment.getB()) > 0.00001) {
grad2 = -otherSegment.getA()/otherSegment.getB();
int2 = -otherSegment.getC()/otherSegment.getB();
}
if (grad1 == Number.MAX_VALUE) {
if (grad2 == Number.MAX_VALUE) {
if (-c/a != -otherSegment.getC()/otherSegment.getA()) {
return Relation.SEPARATE;
}
if ((y1 >= Math.min(otherSegment.getPy1(),otherSegment.getPy2())) &&
(y1 <= Math.max(otherSegment.getPy1(),otherSegment.getPy2()))) {
xi = x1;
yi = y1;
return Relation.INTERSECTS;
}
if ((y2 >= Math.min(otherSegment.getPy1(),otherSegment.getPy2())) &&
(y2 <= Math.max(otherSegment.getPy1(),otherSegment.getPy2()))) {
xi = x2;
yi = y2;
return Relation.INTERSECTS;
}
return Relation.SEPARATE;
}
xi = x1;
yi = grad2*xi+int2;
if (((y1-yi)*(yi-y2) < -0.00001) || ((otherSegment.getPy1()-yi)*(yi-otherSegment.getPy2()) < -0.00001)) {
return Relation.SEPARATE;
}
if (Math.abs(otherSegment.getA()) < 0.00001) {
if ((otherSegment.getPx1()-xi)*(xi-otherSegment.getPx2()) < -0.00001) {
return Relation.SEPARATE;
}
return Relation.INTERSECTS;
}
return Relation.INTERSECTS;
}
if (grad2 == Number.MAX_VALUE) {
xi = otherSegment.getPx1();
yi = grad1*xi+int1;
if (((otherSegment.getPy1()-yi)*(yi-otherSegment.getPy2()) < -0.00001) || ((y1-yi)*(yi-y2) < -0.00001)) {
return Relation.SEPARATE;
}
if (Math.abs(a) < 0.00001) {
if ((x1-xi)*(xi-x2) < -0.00001) {
return Relation.SEPARATE;
}
return Relation.INTERSECTS;
}
return Relation.INTERSECTS;
}
if (grad1 == grad2) {
if (int1 != int2) {
return Relation.SEPARATE;
}
if ((x1 >= Math.min(otherSegment.getPx1(),otherSegment.getPx2())) &&
(x1 <= Math.max(otherSegment.getPy1(),otherSegment.getPy2()))) {
xi = x1;
yi = y1;
return Relation.INTERSECTS;
}
if ((x2 >= Math.min(otherSegment.getPx1(),otherSegment.getPx2())) &&
(x2 <= Math.max(otherSegment.getPx1(),otherSegment.getPx2()))) {
xi = x2;
yi = y2;
return Relation.INTERSECTS;
}
return Relation.SEPARATE;
}
xi = (int2-int1)/(grad1-grad2);
yi = grad1*xi + int1;
if (((x1-xi)*(xi-x2) < -0.00001) || ((otherSegment.getPx1()-xi)*(xi-otherSegment.getPx2()) < -0.00001)) {
return Relation.SEPARATE;
}
return Relation.INTERSECTS;
}
this.getPx1 = function() {
return x1;
}
this.getPy1 = function() {
return y1;
}
this.getPx2 = function() {
return x2;
}
this.getPy2 = function() {
return y2;
}
this.isUndefined = function() {
return undef;
}
this.getA = function() {
return a;
}
this.getB = function() {
return b;
}
this.getC = function() {
return c;
}
this.getIntersectionX = function() {
return xi;
}
this.getIntersectionY = function() {
return yi;
}
this.getLength = function( tx1, ty1, tx2, ty2 ) {
var dx = tx2 - tx1;
var dy = ty2 - ty1;
return Math.sqrt(dx*dx + dy*dy);
}
}
var Relation = { LEFT:1, RIGHT:2, INTERSECTS:3, AHEAD:4, BEHIND:5, SEPARATE:6, UNDEFINED:7 };