diff options
Diffstat (limited to 'js/lib')
-rwxr-xr-x | js/lib/geom/brush-stroke.js | 245 |
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]; | ||
469 | ctx.beginPath(); | ||
470 | ctx.moveTo(pt[0]-bboxMin[0],pt[1]-bboxMin[1]); | ||
471 | for (var i = 1; i < numPoints; i++) { | ||
472 |