aboutsummaryrefslogtreecommitdiff
path: root/js/lib/geom/brush-stroke.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/lib/geom/brush-stroke.js')
-rwxr-xr-xjs/lib/geom/brush-stroke.js245
1 files changed, 81 insertions, 164 deletions
diff --git a/js/lib/geom/brush-stroke.js b/js/lib/geom/brush-stroke.js
index a92657a4..595a6a63 100755
--- a/js/lib/geom/brush-stroke.js
+++ b/js/lib/geom/brush-stroke.js
@@ -32,7 +32,7 @@ var BrushStroke = function GLBrushStroke() {
32 //stroke information 32 //stroke information
33 this._strokeWidth = 0.0; 33 this._strokeWidth = 0.0;
34 this._strokeColor = [0.4, 0.4, 0.4, 1.0]; 34 this._strokeColor = [0.4, 0.4, 0.4, 1.0];
35 this._secondStrokeColor = this._strokeColor; 35 this._secondStrokeColor = [1, 0.4, 0.4, 1.0];
36 this._strokeHardness = 100; 36 this._strokeHardness = 100;
37 this._strokeMaterial = null; 37 this._strokeMaterial = null;
38 this._strokeStyle = "Solid"; 38 this._strokeStyle = "Solid";
@@ -44,6 +44,12 @@ var BrushStroke = function GLBrushStroke() {
44 //smaller value means more samples for the path 44 //smaller value means more samples for the path
45 this._WETNESS_FACTOR = 0.25; 45 this._WETNESS_FACTOR = 0.25;
46 46
47 //threshold that tells us whether two samples are too far apart
48 this._MAX_SAMPLE_DISTANCE_THRESHOLD = 5;
49
50 //threshold that tells us whether two samples are too close
51 this._MIN_SAMPLE_DISTANCE_THRESHOLD = 2;
52
47 //prevent extremely long paths that can take a long time to render 53 //prevent extremely long paths that can take a long time to render
48 this._MAX_ALLOWED_SAMPLES = 500; 54 this._MAX_ALLOWED_SAMPLES = 500;
49 55
@@ -119,7 +125,7 @@ var BrushStroke = function GLBrushStroke() {
119 //add the point only if it is some epsilon away from the previous point 125 //add the point only if it is some epsilon away from the previous point
120 var numPoints = this._Points.length; 126 var numPoints = this._Points.length;
121 if (numPoints>0) { 127 if (numPoints>0) {
122 var threshold = 2;//this._WETNESS_FACTOR*this._strokeWidth; 128 var threshold = this._MIN_SAMPLE_DISTANCE_THRESHOLD;//this._WETNESS_FACTOR*this._strokeWidth;
123 var prevPt = this._Points[numPoints-1]; 129 var prevPt = this._Points[numPoints-1];
124 var diffPt = [prevPt[0]-pt[0], prevPt[1]-pt[1]]; 130 var diffPt = [prevPt[0]-pt[0], prevPt[1]-pt[1]];
125 var diffPtMag = Math.sqrt(diffPt[0]*diffPt[0] + diffPt[1]*diffPt[1]); 131 var diffPtMag = Math.sqrt(diffPt[0]*diffPt[0] + diffPt[1]*diffPt[1]);
@@ -276,13 +282,16 @@ var BrushStroke = function GLBrushStroke() {
276 } 282 }
277 } 283 }
278 } 284 }
285
279 //todo 4-point subdivision iterations over continuous regions of 'long' segments 286 //todo 4-point subdivision iterations over continuous regions of 'long' segments
280 // look at http://www.gvu.gatech.edu/~jarek/Split&Tweak/ for formula 287 // look at http://www.gvu.gatech.edu/~jarek/Split&Tweak/ for formula
281 //**** add samples to the long sections of the path --- Catmull-Rom spline interpolation 288 //**** add samples to the long sections of the path --- Catmull-Rom spline interpolation
282 if (this._strokeDoSmoothing && numPoints>1) { 289 if (this._strokeDoSmoothing && numPoints>1) {
283 var numInsertedPoints = 0; 290 var numInsertedPoints = 0;
284 var threshold = 5;//0.25*this._strokeWidth; //this determines whether a segment between two sample is too long 291 var newPoints = [];
292 var threshold = this._MAX_SAMPLE_DISTANCE_THRESHOLD;//this determines whether a segment between two sample is long enough to warrant checking for angle
285 var prevPt = this._Points[0]; 293 var prevPt = this._Points[0];
294 newPoints.push(this._Points[0]);
286 for (var i=1;i<numPoints;i++){ 295 for (var i=1;i<numPoints;i++){
287 var pt = this._Points[i]; 296 var pt = this._Points[i];
288 var diff = [pt[0]-prevPt[0], pt[1]-prevPt[1]]; 297 var diff = [pt[0]-prevPt[0], pt[1]-prevPt[1]];
@@ -298,12 +307,14 @@ var BrushStroke = function GLBrushStroke() {
298 var param = (j+1)/(numNewPoints+1); 307 var param = (j+1)/(numNewPoints+1);
299 var newpt = this._CatmullRomSplineInterpolate(ctrlPts, param); 308 var newpt = this._CatmullRomSplineInterpolate(ctrlPts, param);
300 //insert new point before point i 309 //insert new point before point i
301 this._Points.splice(i, 0, newpt); 310 //this._Points.splice(i, 0, newpt);
302 i++; 311 //i++;
312 newPoints.push(newpt);
303 numInsertedPoints++; 313 numInsertedPoints++;
304 } 314 }
305 this._dirty=true; 315 this._dirty=true;
306 } 316 }
317 newPoints.push(pt);
307 prevPt=pt; 318 prevPt=pt;
308 //update numPoints to match the new length 319 //update numPoints to match the new length
309 numPoints = this._Points.length; 320 numPoints = this._Points.length;
@@ -314,7 +325,22 @@ var BrushStroke = function GLBrushStroke() {
314 break; 325 break;
315 } 326 }
316 } 327 }
328 this._Points = newPoints;
329 numPoints = this._Points.length;
317 console.log("Inserted "+numInsertedPoints+" additional CatmullRom points"); 330 console.log("Inserted "+numInsertedPoints+" additional CatmullRom points");
331
332 //now do 3-4 iterations of Laplacian smoothing (setting the points to the average of their neighbors)
333 var numLaplacianIterations = 3; //todo figure out the proper number of Laplacian iterations (perhaps as a function of stroke width)
334 for (var n=0;n<numLaplacianIterations;n++){
335 newPoints = this._Points;
336 for (var i=1;i<numPoints-1;i++){
337 var avgPos = [ 0.5*(this._Points[i-1][0] + this._Points[i+1][0]),
338 0.5*(this._Points[i-1][1] + this._Points[i+1][1]),
339 0.5*(this._Points[i-1][2] + this._Points[i+1][2])] ;
340 newPoints[i] = avgPos;
341 }
342 this._Points = newPoints;
343 }
318 } 344 }
319 345
320 // *** compute the bounding box ********* 346 // *** compute the bounding box *********
@@ -337,11 +363,20 @@ var BrushStroke = function GLBrushStroke() {
337 }//for every dimension d from 0 to 2 363 }//for every dimension d from 0 to 2
338 } 364 }
339 } 365 }
340 //increase the bbox given the stroke width 366
341 for (var d = 0; d < 3; d++) { 367 //increase the bbox given the stroke width and the angle (in case of calligraphic brush)
342 this._BBoxMin[d]-= this._strokeWidth/2; 368 var bboxPadding = this._strokeWidth/2;
343 this._BBoxMax[d]+= this._strokeWidth/2; 369 if (this.__strokeUseCalligraphic) {
344 }//for every dimension d from 0 to 2 370 this._BBoxMin[0]-= bboxPadding*Math.cos(this._strokeAngle);
371 this._BBoxMin[1]-= bboxPadding*Math.sin(this._strokeAngle);
372 this._BBoxMax[0]+= bboxPadding*Math.cos(this._strokeAngle);
373 this._BBoxMax[1]+= bboxPadding*Math.sin(this._strokeAngle);
374 } else {
375 for (var d = 0; d < 3; d++) {
376 this._BBoxMin[d]-= bboxPadding;
377 this._BBoxMax[d]+= bboxPadding;
378 }//for every dimension d from 0 to 2
379 }
345 } 380 }
346 this._dirty = false; 381 this._dirty = false;
347 }; 382 };
@@ -375,183 +410,66 @@ var BrushStroke = function GLBrushStroke() {
375 var bboxHeight = bboxMax[1] - bboxMin[1]; 410 var bboxHeight = bboxMax[1] - bboxMin[1];
376 ctx.clearRect(0, 0, bboxWidth, bboxHeight); 411 ctx.clearRect(0, 0, bboxWidth, bboxHeight);
377 412
378 /*
379 ctx.lineWidth = this._strokeWidth;
380 ctx.strokeStyle = "black";
381 if (this._strokeColor)
382 ctx.strokeStyle = MathUtils.colorToHex( this._strokeColor );
383 ctx.fillStyle = "blue";
384 if (this._fillColor)
385 ctx.fillStyle = MathUtils.colorToHex( this._fillColor );
386 var lineCap = ['butt','round','square'];
387 ctx.lineCap = lineCap[1];
388 ctx.beginPath();
389 var firstPoint = this._Points[0];
390 ctx.moveTo(firstPoint[0]-bboxMin[0], firstPoint[1]-bboxMin[1]);
391 for (var i = 1; i < numPoints; i++) {
392 var pt = this._Points[i];
393 ctx.lineTo(pt[0]-bboxMin[0], pt[1]-bboxMin[1]);
394 }
395 ctx.stroke();
396 */
397
398 /*
399 var isDebug = false;
400 var prevPt = this._Points[0];
401 var prevX = prevPt[0]-bboxMin[0];
402 var prevY = prevPt[1]-bboxMin[1];
403 prevPt = [prevX,prevY];
404 for (var i = 1; i < numPoints; i++) {
405 var pt = this._Points[i];
406 ctx.globalCompositeOperation = 'source-over';
407 var x = pt[0]-bboxMin[0];
408 var y = pt[1]-bboxMin[1];
409 pt = [x,y];
410
411 //vector from prev to current pt
412 var seg = VecUtils.vecSubtract(2, pt, prevPt);
413 var segDir = VecUtils.vecNormalize(2, seg, 1.0);
414
415 var segMidPt = VecUtils.vecInterpolate(2, pt, prevPt, 0.5);
416 var w2 = this._strokeWidth*0.5;
417 var segDirOrtho = [w2*segDir[1], -w2*segDir[0]];
418
419 //add half the strokewidth to the segMidPt
420 var lgStart = VecUtils.vecAdd(2, segMidPt, segDirOrtho);
421 var lgEnd = VecUtils.vecSubtract(2, segMidPt, segDirOrtho);
422
423 ctx.save();
424 ctx.beginPath();
425
426 if (isDebug) {
427 ctx.strokeStyle="black";
428 ctx.lineWidth = 1;
429
430 ctx.moveTo(lgStart[0], lgStart[1]);
431 ctx.lineTo(lgEnd[0], lgEnd[1]);
432 ctx.stroke();
433 }
434
435 var lg = ctx.createLinearGradient(lgStart[0], lgStart[1], lgEnd[0], lgEnd[1]);
436 lg.addColorStop(1, 'rgba(0,0,0,0.0)');
437 lg.addColorStop(0.5,'rgba(255,0,0,1.0)');
438 lg.addColorStop(0, 'rgba(0,0,0,0.0)');
439 ctx.fillStyle = lg;
440
441 if (isDebug){
442 ctx.strokeStyle="blue";
443 ctx.lineWidth=0.5;
444 }
445 ctx.moveTo(prevX-w2, prevY);
446 ctx.lineTo(prevX+w2, prevY);
447 ctx.lineTo(x+w2, y);
448 ctx.lineTo(x-w2, y);
449 ctx.lineTo(prevX-w2, prevY);
450 ctx.fill();
451 ctx.closePath();
452
453 ctx.restore();
454
455 prevPt = pt;
456 prevX = x;
457 prevY = y;
458 }
459
460
461 if (isDebug)
462 ctx.stroke();
463
464 if (isDebug){
465 //draw the skeleton of this stroke
466 ctx.lineWidth = 1;
467 ctx.strokeStyle = "black";
468 var pt = this._Points[0];