diff options
Diffstat (limited to 'js/lib')
-rwxr-xr-x | js/lib/geom/brush-stroke.js | 329 | ||||
-rwxr-xr-x | js/lib/geom/sub-path.js | 84 |
2 files changed, 280 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 |