protected static string lastSegmentToCurve(int[][] stroke, float lineCurveThreshold) { // Here we tidy up things left unfinished // What's left unfinished there is the curve between the last points // in the stroke // We can also be called when there is only one point in the stroke (meaning, the // stroke was just a dot), in which case there is nothing for us to do. // So for "this curve" to be calc'ed we need 3 points // A, B, C // and 2 lines: // pre-line (from points A to B), // this line (from points B to C) // Well, actually, we don't need to *know* the point A, just the vector A->B // so, we really need points B, C and AB vector. var positionInStroke = stroke.Length - 1; // there must be at least 2 points in the stroke.for us to work. Hope calling code checks for that. var BCvector = new Vector(stroke[positionInStroke][0], stroke[positionInStroke][1]); var rounding = 2; string curvetemplate = "c {0} {1} {2} {3} {4} {5}"; string linetemplate = "l {0} {1}"; if (positionInStroke > 1 && BCvector.Length > lineCurveThreshold){ // we have at least 3 elems in stroke var ABvector = new Vector(stroke[positionInStroke - 1][0], stroke[positionInStroke - 1][1]); var ABCangle = BCvector.AngleTo(ABvector.Reversed); var minlenfraction = 0.05; var maxlen = BCvector.Length * 0.35; var BtoCP1vector = new Vector( ABvector.x + BCvector.x , ABvector.y + BCvector.y ).GetResizedTo( (float)(Math.Max(minlenfraction, ABCangle) * maxlen) ); return String.Format( curvetemplate , Math.Round( BtoCP1vector.x, rounding ) , Math.Round( BtoCP1vector.y, rounding ) , Math.Round( BCvector.x, rounding ) // CP2 is same as Cpoint , Math.Round( BCvector.y, rounding ) // CP2 is same as Cpoint , Math.Round( BCvector.x, rounding ) , Math.Round( BCvector.y, rounding ) ); } else { // Since there is no AB leg, there is no curve to draw. This is just line return String.Format( linetemplate , Math.Round( BCvector.x, rounding ) , Math.Round( BCvector.y, rounding ) ); } }
/** * Calculates the angle between 'this' vector and another. * @public * @function * @returns {Number} The angle between the two vectors as measured in PI. */ public float AngleTo(Vector vectorB) { var divisor = this.Length * vectorB.Length; if (divisor == 0) { return 0; } else { // JavaScript floating point math is screwed up. // because of it, the core of the formula can, on occasion, have values // over 1.0 and below -1.0. return (float)( Math.Acos( Math.Min( Math.Max( ( this.x * vectorB.x + this.y * vectorB.y ) / divisor , -1.0 ) , 1.0 ) ) / Math.PI ); } }
protected static string segmentToCurve(int[][] stroke, int positionInStroke, float lineCurveThreshold) { // long lines (ones with many pixels between them) do not look good when they are part of a large curvy stroke. // You know, the jaggedy crocodile spine instead of a pretty, smooth curve. Yuck! // We want to approximate pretty curves in-place of those ugly lines. // To approximate a very nice curve we need to know the direction of line before and after. // Hence, on long lines we actually wait for another point beyond it to come back from // mousemoved before we draw this curve. // So for "prior curve" to be calc'ed we need 4 points // A, B, C, D (we are on D now, A is 3 points in the past.) // and 3 lines: // pre-line (from points A to B), // this line (from points B to C), (we call it "this" because if it was not yet, it's the only one we can draw for sure.) // post-line (from points C to D) (even through D point is 'current' we don't know how we can draw it yet) // // Well, actually, we don't need to *know* the point A, just the vector A->B // Again, we can only derive curve between points positionInStroke-1 and positionInStroke // Thus, since we can only draw a line if we know one point ahead of it, we need to shift our focus one point ahead. positionInStroke += 1; // Let's hope the code that calls us knows we do that and does not call us with positionInStroke = index of last point. var CDvector = new Vector(stroke[positionInStroke][0], stroke[positionInStroke][1]); // Again, we have a chance here to draw only PREVIOUS line segment - BC // So, let's start with BC curve. // if there is only 2 points in stroke array (C, D), we don't have "history" long enough to have point B, let alone point A. // so positionInStroke should start with 2, ie // we are here when there are at least 3 points in stroke array. var BCvector = new Vector(stroke[positionInStroke - 1][0], stroke[positionInStroke - 1][1]); Vector ABvector; var rounding = 2; string curvetemplate = "c {0} {1} {2} {3} {4} {5}"; string linetemplate = "l {0} {1}"; if ( BCvector.Length > lineCurveThreshold ){ // Yey! Pretty curves, here we come! if(positionInStroke > 2) { ABvector = new Vector(stroke[positionInStroke - 2][0], stroke[positionInStroke - 2][1]); } else { ABvector = new Vector(0,0); } var minlenfraction = 0.05f; var maxlen = BCvector.Length * 0.35; var ABCangle = BCvector.AngleTo(ABvector.Reversed); var BCDangle = CDvector.AngleTo(BCvector.Reversed); var BtoCP1vector = new Vector( ABvector.x + BCvector.x , ABvector.y + BCvector.y ).GetResizedTo( (float)(Math.Max(minlenfraction, ABCangle) * maxlen) ); var CtoCP2vector = new Vector( BCvector.x + CDvector.x , BCvector.y + CDvector.y ).Reversed.GetResizedTo( (float)(Math.Max(minlenfraction, BCDangle) * maxlen) ); var BtoCP2vector = new Vector(BCvector.x + CtoCP2vector.x, BCvector.y + CtoCP2vector.y); // returing curve for BC segment // all coords are vectors against Bpoint return String.Format( curvetemplate , Math.Round( BtoCP1vector.x, rounding ) , Math.Round( BtoCP1vector.y, rounding ) , Math.Round( BtoCP2vector.x, rounding ) , Math.Round( BtoCP2vector.y, rounding ) , Math.Round( BCvector.x, rounding ) , Math.Round( BCvector.y, rounding ) ); } else { return String.Format( linetemplate , Math.Round( BCvector.x, rounding ) , Math.Round( BCvector.y, rounding ) ); } }