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.js329
1 files changed, 196 insertions, 133 deletions
diff --git a/js/lib/geom/brush-stroke.js b/js/lib/geom/brush-stroke.js
index 9a934928..4c42539a 100755
--- a/js/lib/geom/brush-stroke.js
+++ b/js/lib/geom/brush-stroke.js
@@ -32,13 +32,24 @@ 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 = [1, 0.4, 0.4, 1.0];
36 this._strokeHardness = 100;
35 this._strokeMaterial = null; 37 this._strokeMaterial = null;
36 this._strokeStyle = "Solid"; 38 this._strokeStyle = "Solid";
39 this._strokeDoSmoothing = false;
40 this._strokeUseCalligraphic = false;
41 this._strokeAngle = 0;
37 42
38 //the wetness of the brush (currently this is multiplied to the square of the stroke width, but todo should be changed to not depend on stroke width entirely 43 //the wetness of the brush (currently this is multiplied to the square of the stroke width, but todo should be changed to not depend on stroke width entirely
39 //smaller value means more samples for the path 44 //smaller value means more samples for the path
40 this._WETNESS_FACTOR = 0.25; 45 this._WETNESS_FACTOR = 0.25;
41 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
42 //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
43 this._MAX_ALLOWED_SAMPLES = 500; 54 this._MAX_ALLOWED_SAMPLES = 500;
44 55
@@ -114,7 +125,7 @@ var BrushStroke = function GLBrushStroke() {
114 //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
115 var numPoints = this._Points.length; 126 var numPoints = this._Points.length;
116 if (numPoints>0) { 127 if (numPoints>0) {
117 var threshold = this._WETNESS_FACTOR*this._strokeWidth; 128 var threshold = this._MIN_SAMPLE_DISTANCE_THRESHOLD;//this._WETNESS_FACTOR*this._strokeWidth;
118 var prevPt = this._Points[numPoints-1]; 129 var prevPt = this._Points[numPoints-1];
119 var diffPt = [prevPt[0]-pt[0], prevPt[1]-pt[1]]; 130 var diffPt = [prevPt[0]-pt[0], prevPt[1]-pt[1]];
120 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]);
@@ -173,6 +184,26 @@ var BrushStroke = function GLBrushStroke() {
173 this._strokeColor = c; 184 this._strokeColor = c;
174 }; 185 };
175 186
187 this.setSecondStrokeColor = function(c){
188 this._secondStrokeColor=c;
189 }
190
191 this.setStrokeHardness = function(h){
192 this._strokeHardness=h;
193 }
194
195 this.setDoSmoothing = function(s){
196 this._strokeDoSmoothing = s;
197 }
198
199 this.setStrokeUseCalligraphic = function(c){
200 this._strokeUseCalligraphic = c;
201 }
202
203 this.setStrokeAngle = function(a){
204 this._strokeAngle = a;
205 }
206
176 this.getStrokeStyle = function () { 207 this.getStrokeStyle = function () {
177 return this._strokeStyle; 208 return this._strokeStyle;
178 }; 209 };
@@ -219,7 +250,8 @@ var BrushStroke = function GLBrushStroke() {
219 var numPoints = this._Points.length; 250 var numPoints = this._Points.length;
220 251
221 //**** add samples to the path if needed...linear interpolation for now 252 //**** add samples to the path if needed...linear interpolation for now
222 if (numPoints>1) { 253 //if (numPoints>1) {
254 if (0){
223 var threshold = this._WETNESS_FACTOR*this._strokeWidth; 255 var threshold = this._WETNESS_FACTOR*this._strokeWidth;
224 var prevPt = this._Points[0]; 256 var prevPt = this._Points[0];
225 var prevIndex = 0; 257 var prevIndex = 0;
@@ -251,6 +283,66 @@ var BrushStroke = function GLBrushStroke() {
251 } 283 }
252 } 284 }
253 285
286 //todo 4-point subdivision iterations over continuous regions of 'long' segments
287 // look at http://www.gvu.gatech.edu/~jarek/Split&Tweak/ for formula
288 //**** add samples to the long sections of the path --- Catmull-Rom spline interpolation
289 if (this._strokeDoSmoothing && numPoints>1) {
290 var numInsertedPoints = 0;
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
293 var prevPt = this._Points[0];
294 newPoints.push(this._Points[0]);
295 for (var i=1;i<numPoints;i++){
296 var pt = this._Points[i];
297 var diff = [pt[0]-prevPt[0], pt[1]-prevPt[1]];
298 var distance = Math.sqrt(diff[0]*diff[0]+diff[1]*diff[1]);
299 if (distance>threshold){
300 //build the control polygon for the Catmull-Rom spline (prev. 2 points and next 2 points)
301 var prev = (i===1) ? i-1 : i-2;
302 var next = (i===numPoints-1) ? i : i+1;
303 var ctrlPts = [this._Points[prev], this._Points[i-1], this._Points[i], this._Points[next]];
304 //insert points along the prev. to current point
305 var numNewPoints = Math.floor(distance/threshold);
306 for (var j=0;j<numNewPoints;j++){
307 var param = (j+1)/(numNewPoints+1);
308 var newpt = this._CatmullRomSplineInterpolate(ctrlPts, param);
309 //insert new point before point i
310 //this._Points.splice(i, 0, newpt);
311 //i++;
312 newPoints.push(newpt);
313 numInsertedPoints++;
314 }
315 this._dirty=true;
316 }
317 newPoints.push(pt);
318 prevPt=pt;
319 //update numPoints to match the new length
320 numPoints = this._Points.length;
321
322 //end this function if the numPoints has gone above the max. size specified
323 if (numPoints> this._MAX_ALLOWED_SAMPLES){
324 console.log("leaving the resampling because numPoints is greater than limit:"+this._MAX_ALLOWED_SAMPLES);
325 break;
326 }
327 }
328 this._Points = newPoints;
329 numPoints = this._Points.length;
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 }
344 }
345
254 // *** compute the bounding box ********* 346 // *** compute the bounding box *********
255 this._BBoxMin = [Infinity, Infinity, Infinity]; 347 this._BBoxMin = [Infinity, Infinity, Infinity];
256 this._BBoxMax = [-Infinity, -Infinity, -Infinity]; 348 this._BBoxMax = [-Infinity, -Infinity, -Infinity];
@@ -271,11 +363,20 @@ var BrushStroke = function GLBrushStroke() {
271 }//for every dimension d from 0 to 2 363 }//for every dimension d from 0 to 2
272 } 364 }
273 } 365 }
274 //increase the bbox given the stroke width 366
275 for (var d = 0; d < 3; d++) { 367 //increase the bbox given the stroke width and the angle (in case of calligraphic brush)
276 this._BBoxMin[d]-= this._strokeWidth/2; 368 var bboxPadding = this._strokeWidth/2;
277 this._BBoxMax[d]+= this._strokeWidth/2; 369 if (this.__strokeUseCalligraphic) {
278 }//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 }
279 } 380 }
280 this._dirty = false; 381 this._dirty = false;
281 }; 382 };
@@ -309,138 +410,85 @@ var BrushStroke = function GLBrushStroke() {
309 var bboxHeight = bboxMax[1] - bboxMin[1]; 410 var bboxHeight = bboxMax[1] - bboxMin[1];
310 ctx.clearRect(0, 0, bboxWidth, bboxHeight); 411 ctx.clearRect(0, 0, bboxWidth, bboxHeight);
311 412
312 /* 413 if (this._strokeUseCalligraphic) {
313 ctx.lineWidth = this._strokeWidth; 414 //build the stamp for the brush stroke
314 ctx.strokeStyle = "black"; 415 var t=0;
315 if (this._strokeColor) 416 var numTraces = this._strokeWidth;
316 ctx.strokeStyle = MathUtils.colorToHex( this._strokeColor ); 417 var halfNumTraces = numTraces/2;
317 ctx.fillStyle = "blue"; 418 var opaqueRegionHalfWidth = 0.5*this._strokeHardness*numTraces*0.01; //the 0.01 is to convert the strokeHardness from [0,100] to [0,1]
318 if (this._fillColor) 419 var maxTransparentRegionHalfWidth = halfNumTraces-opaqueRegionHalfWidth;
319 ctx.fillStyle = MathUtils.colorToHex( this._fillColor ); 420
320 var lineCap = ['butt','round','square']; 421 //build an angled (calligraphic) brush stamp
321 ctx.lineCap = lineCap[1]; 422 var deltaDisplacement = [Math.cos(this._strokeAngle),Math.sin(this._strokeAngle)];
322 ctx.beginPath(); 423 deltaDisplacement = VecUtils.vecNormalize(2, deltaDisplacement, 1);
323 var firstPoint = this._Points[0]; 424 var startPos = [-halfNumTraces*deltaDisplacement[0],-halfNumTraces*deltaDisplacement[1]];
324 ctx.moveTo(firstPoint[0]-bboxMin[0], firstPoint[1]-bboxMin[1]); 425
325 for (var i = 1; i < numPoints; i++) { 426 var brushStamp = [];
326 var pt = this._Points[i]; 427 for (t=0;t<numTraces;t++){
327 ctx.lineTo(pt[0]-bboxMin[0], pt[1]-bboxMin[1]); 428 var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]];
328 } 429 brushStamp.push(brushPt);
329 ctx.stroke();
330 */
331
332 /*
333 var isDebug = false;
334 var prevPt = this._Points[0];
335 var prevX = prevPt[0]-bboxMin[0];
336 var prevY = prevPt[1]-bboxMin[1];
337 prevPt = [prevX,prevY];
338 for (var i = 1; i < numPoints; i++) {
339 var pt = this._Points[i];
340 ctx.globalCompositeOperation = 'source-over';
341 var x = pt[0]-bboxMin[0];
342 var y = pt[1]-bboxMin[1];
343 pt = [x,y];
344
345 //vector from prev to current pt
346 var seg = VecUtils.vecSubtract(2, pt, prevPt);
347 var segDir = VecUtils.vecNormalize(2, seg, 1.0);
348