/** * Creates an instance of Point * * * @constructor * @this {Point} * @param {Number} x The x coordinate of point. * @param {Number} y The y coordinate of point. * @author Alex Gheorghiu * Note: Even if it is named Point this class should be named Dot as Dot is closer * then Point from math perspective. **/ function Point(x, y){ /**The x coordinate of point*/ this.x = x; /**The y coordinate of point*/ this.y = y; /**The {@link Style} of the Point*/ this.style = new Style(); /**Serialization type*/ this.oType = 'Point'; //object type used for JSON deserialization } /**Creates a {Point} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {Point} a newly constructed Point *@author Alex Gheorghiu **/ Point.load = function(o){ var newPoint = new Point(Number(o.x), Number(o.y)); newPoint.style = Style.load(o.style); return newPoint; } /**Creates an array of points from an array of {JSONObject}s *@param {Array} v - the array of JSONObjects *@return an {Array} of {Point}s **/ Point.loadArray = function(v){ var newPoints = []; for(var i=0; i< v.length; i++){ newPoints.push(Point.load(v[i])); } return newPoints; } /**Clones an array of points *@param {Array} v - the array of {Point}s *@return an {Array} of {Point}s **/ Point.cloneArray = function(v){ var newPoints = []; for(var i=0; i< v.length; i++){ newPoints.push(v[i].clone()); } return newPoints; } Point.prototype = { constructor : Point, /* *Transform a point by a tranformation matrix. *It is done by multiplication *Pay attention on the order of multiplication: The tranformation {Matrix} is *multiplied with the {Point} matrix. * P' = M x P *@param matrix is a 3x3 matrix *@see http://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations **/ transform:function(matrix){ if(this.style!=null){ this.style.transform(matrix); } var oldX = this.x; var oldY = this.y; this.x = matrix[0][0] * oldX + matrix[0][1] * oldY + matrix[0][2]; this.y = matrix[1][0] * oldX + matrix[1][1] * oldY + matrix[1][2]; }, /**Paint current {Point} withing a context *If you want to use a different style then the default one change the style **/ paint:function(context){ if(this.style != null){ this.style.setupContext(context); } if(this.style.strokeStyle != ""){ context.fillStyle = this.style.strokeStyle; context.beginPath(); var width = 1; if(this.style.lineWidth != null){ width = parseInt(this.style.lineWidth); } context.arc(this.x, this.y, width, 0,Math.PI/180*360,false); context.fill(); } }, /**Tests if this point is similar to other point *@param {Point} anotherPoint - the other point **/ equals:function(anotherPoint){ if(! (anotherPoint instanceof Point) ){ return false; } return (this.x == anotherPoint.x) && (this.y == anotherPoint.y) && this.style.equals(anotherPoint.style); }, /**Clone current Point **/ clone: function(){ var newPoint = new Point(this.x, this.y); newPoint.style = this.style.clone(); return newPoint; }, /**Tests to see if a point (x, y) is within a range of current Point *@param {Numeric} x - the x coordinate of tested point *@param {Numeric} y - the x coordinate of tested point *@param {Numeric} radius - the radius of the vicinity *@author Alex Gheorghiu **/ near:function(x, y, radius){ var distance = Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2)); return (distance <= radius); }, contains: function(x,y){ return this.x == x && this.y == y; }, toString:function(){ return 'point(' + this.x + ',' + this.y + ')'; }, getPoints:function(){ return [this]; }, getBounds:function(){ return Util.getBounds(this.getPoints()); }, /** *We will draw a point a circle. The "visual" color and thicknes of the point will *be created by the SVG's element style * *@see http://tutorials.jenkov.com/svg/circle-element.html * *Example: * **/ toSVG: function(){ var r = ''; r += "\n" + repeat("\t", INDENTATION) + ' **/ function Line(startPoint, endPoint){ /**Starting {@link Point} of the line*/ this.startPoint = startPoint; /**Ending {@link Point} of the line*/ this.endPoint = endPoint; /**The {@link Style} of the line*/ this.style = new Style(); this.style.gradientBounds = this.getBounds(); /**Serialization type*/ this.oType = 'Line'; //object type used for JSON deserialization } /**Creates a {Line} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {Line} a newly constructed Line *@author Alex Gheorghiu **/ Line.load = function(o){ var newLine = new Line( Point.load(o.startPoint), Point.load(o.endPoint) ); newLine.style = Style.load(o.style); return newLine; } Line.prototype = { contructor: Line, transform:function(matrix){ this.startPoint.transform(matrix); this.endPoint.transform(matrix); if(this.style!=null){ this.style.transform(matrix); } }, paint:function(context){ context.beginPath(); if(this.style != null){ this.style.setupContext(context); } context.moveTo(this.startPoint.x, this.startPoint.y); if(this.style.dashLength==0){ context.lineTo(this.endPoint.x, this.endPoint.y); context.closePath(); // added for line's correct Chrome's displaying } else{ //get the length of the line var lineLength=Math.sqrt(Math.pow(this.startPoint.x-this.endPoint.x,2)+Math.pow(this.startPoint.y-this.endPoint.y,2)); //get the angle var angle = Util.getAngle(this.startPoint,this.endPoint); //draw a dotted line var move=false; for(var i=0; i **/ contains: function(x, y){ // if the point is inside rectangle bounds of the segment if (Math.min(this.startPoint.x, this.endPoint.x) <= x && x <= Math.max(this.startPoint.x, this.endPoint.x) && Math.min(this.startPoint.y, this.endPoint.y) <= y && y <= Math.max(this.startPoint.y, this.endPoint.y)) { // check for vertical line if (this.startPoint.x == this.endPoint.x) { return x == this.startPoint.x; } else { // usual (not vertical) line can be represented as y = a * x + b var a = (this.endPoint.y - this.startPoint.y) / (this.endPoint.x - this.startPoint.x); var b = this.startPoint.y - a * this.startPoint.x; return y == a * x + b; } } else { return false; } }, /* *See if we are near a {Line} by a certain radius (also includes the extremities into computation) *@param {Number} x - the x coordinates *@param {Number} y - the y coordinates *@param {Number} radius - the radius to search for *@see http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line *@see "Mathematics for Computer Graphics, 2nd Ed., by John Vice, page 227" *@author Zack Newsham *@author Arty *@author Alex **/ near:function(x,y,radius){ if(this.endPoint.x === this.startPoint.x){ //Vertical line, so the vicinity area is a rectangle return ( (this.startPoint.y-radius<=y && this.endPoint.y+radius>=y) || (this.endPoint.y-radius<=y && this.startPoint.y+radius>=y)) && x > this.startPoint.x - radius && x < this.startPoint.x + radius ; } if(this.startPoint.y === this.endPoint.y){ //Horizontal line, so the vicinity area is a rectangle return ( (this.startPoint.x - radius<=x && this.endPoint.x+radius>=x) || (this.endPoint.x-radius<=x && this.startPoint.x+radius>=x)) && y>this.startPoint.y-radius && y=closestX && closestX>=startX && endY>=closestY && closestY>=startY ) //the projection of the point falls INSIDE of the segment || this.startPoint.near(x,y,radius) || this.endPoint.near(x,y,radius); //the projection of the point falls OUTSIDE of the segment return r; }, /**we need to create a new array each time, or we will affect the actual shape*/ getPoints:function(){ var points = []; points.push(this.startPoint); points.push(this.endPoint); return points; }, /**Return the {Point} corresponding the t certain t value * @param {Number} t the value of parameter t, where t in [0,1], t is like a percent*/ getPoint: function(t){ var Xp = t * (this.endPoint.x - this.startPoint.x) + this.startPoint.x; var Yp = t * (this.endPoint.y - this.startPoint.y) + this.startPoint.y; return new Point(Xp, Yp); }, /** * Returns the middle of the line * @return {Point} the middle point * */ getMiddle : function(){ return Util.getMiddle(this.startPoint, this.endPoint); }, getLength : function(){ return Util.getLength(this.startPoint, this.endPoint); }, /** *Get bounds for this line *@author Alex Gheorghiu **/ getBounds:function(){ return Util.getBounds(this.getPoints()); }, /**String representation*/ toString:function(){ return 'line(' + this.startPoint + ',' + this.endPoint + ')'; }, /**Render the SVG fragment for this primitive*/ toSVG:function(){ // var result = "\n" + repeat("\t", INDENTATION) + ' **/ function Polyline(){ /**An {Array} of {@link Point}s*/ this.points = []; /**The {@link Style} of the polyline*/ this.style = new Style(); this.style.gradientBounds = this.getBounds(); /**The starting {@link Point}. * Required for path, we could use getPoints(), but this existed first. * Also its a lot simpler. Each other element used in path already has a startPoint * //TODO: (added by alex) This is bullshit....we need to remove this kind of junky code **/ this.startPoint = null; /**Serialization type*/ this.oType = 'Polyline'; //object type used for JSON deserialization } /**Creates a {Polyline} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {Polyline} a newly constructed Polyline *@author Alex Gheorghiu **/ Polyline.load = function(o){ var newPolyline = new Polyline(); newPolyline.points = Point.loadArray(o.points); newPolyline.style = Style.load(o.style); newPolyline.startPoint = Point.load(o.startPoint); return newPolyline; }; Polyline.prototype = { constructor : Polyline, addPoint:function(point){ if(this.points.length==0){ this.startPoint=point; } this.points.push(point); // update bound coordinates for gradient this.style.gradientBounds = this.getBounds(); }, transform:function(matrix){ if(this.style!=null){ this.style.transform(matrix); } for(var i=0; i l * t ){ break; } walked += Util.distance(this.points[i], this.points[i+1]); } var rest = l * t - walked; var currentSegmentLength = Util.distance(this.points[i], this.points[i+1]); //find the position/ration of the middle of Polyline on current segment var segmentPercent = rest / currentSegmentLength; var THEpoint = new Line(this.points[i], this.points[i+1]).getPoint(segmentPercent); return THEpoint; }, getBounds:function(){ return Util.getBounds(this.getPoints()); }, clone:function(){ var ret=new Polyline(); for(var i=0; i var result = "\n" + repeat("\t", INDENTATION) + ' **/ function Polygon(){ /**An {Array} of {@link Point}s*/ this.points = []; /**The {@link Style} of the polygon*/ this.style = new Style(); this.style.gradientBounds = this.getBounds(); /**Serialization type*/ this.oType = 'Polygon'; //object type used for JSON deserialization } /**Creates a {Polygon} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {Polygon} a newly constructed Polygon *@author Alex Gheorghiu **/ Polygon.load = function(o){ var newPolygon = new Polygon(); newPolygon.points = Point.loadArray(o.points); newPolygon.style = Style.load(o.style); return newPolygon; } Polygon.prototype = { contructor : Polygon, addPoint:function(point){ this.points.push(point); // update bound coordinates for gradient this.style.gradientBounds = this.getBounds(); }, getPosition:function(){ return [this.points[0].x,[this.points[0].y]]; }, paint:function(context){ context.beginPath(); if(this.style!=null){ this.style.setupContext(context); } if(this.points.length > 1){ context.moveTo(this.points[0].x, this.points[0].y); for(var i=1; i} - returns [minX, minY, maxX, maxY] - bounds, where * all points are in the bounds.*/ getBounds:function(){ return Util.getBounds(this.getPoints()); }, fill:function(context,color){ context.fillStyle=color; context.beginPath(); context.moveTo(this.points[0].x, this.points[0].y); for(var i=1; i var result = "\n" + repeat("\t", INDENTATION) + ''; return result; } } /** * Creates an instance of a DottedPolygon. * DottedPolygon is a poligon with all edges dotted or with a certain pattern. * As for now (Summer 2012) context does not support lines with a pattern * this had to be created * * @constructor * @this {DottedPolygon} * @param {Array} pattern - an array of dots and spaces Ex: [10,2,2 4,7] means 10 dotts, 2 spaces, 2 dots and 7 spaces. * @author Alex Gheorghiu **/ function DottedPolygon(pattern){ /**An {Array} of {@link Point}s*/ this.points = []; /**The {@link Style} of the polygon*/ this.style = new Style(); this.style.gradientBounds = this.getBounds(); /**An {Array} of {Integer}s*/ this.pattern = pattern; /**Serialization type*/ this.oType = 'DottedPolygon'; //object type used for JSON deserialization } /**Creates a {Polygon} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {Polygon} a newly constructed Polygon *@author Alex Gheorghiu **/ DottedPolygon.load = function(o){ var newPolygon = new DottedPolygon(o.pattern); newPolygon.points = Point.loadArray(o.points); newPolygon.style = Style.load(o.style); return newPolygon; } DottedPolygon.prototype = { contructor : DottedPolygon, addPoint:function(point){ this.points.push(point); }, getPosition:function(){ return [this.points[0].x,[this.points[0].y]]; }, paint:function(context){ if(this.style != null){ this.style.setupContext(context); } //simply ignore anything that don't have at least 2 points if(this.points.length < 2){ return; } var clonnedPoints = Point.cloneArray(this.points); //first fill if(this.style.fillStyle != null && this.style.fillStyle != ""){ context.beginPath(); context.moveTo(clonnedPoints[0].x, clonnedPoints[0].y); for(i=1;i} - returns [minX, minY, maxX, maxY] - bounds, where * all points are in the bounds.*/ getBounds:function(){ return Util.getBounds(this.getPoints()); }, near:function(x,y,radius){ var i=0; for(i=0; i< this.points.length-1; i++){ var l=new Line(this.points[i], this.points[i+1]); if(l.near(x,y,radius)){ return true; } } l = new Line(this.points[i], this.points[0]); if(l.near(x,y,radius)){ return true; } return false; }, equals:function(anotherPolygon){ if(!anotherPolygon instanceof DottedPolygon){ return false; } if(anotherPolygon.points.length == this.points.length){ for(var i=0; iDottedPolygon:toSVG() - no implemented'; return result; } } /** * Creates an instance of a quad curve. * A curved line determined by 2 normal points (startPoint and endPoint) and 1 control point (controlPoint) * * @constructor * @this {QuadCurve} * @param {Point} startPoint - starting point of the line * @param {Point} controlPoint - the control point of the line * @param {Point} endPoint - the ending point of the line * @see http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves **/ function QuadCurve(startPoint, controlPoint, endPoint){ /**The start {@link Point}*/ this.startPoint = startPoint; /**The controll {@link Point}*/ this.controlPoint = controlPoint; /**The end {@link Point}*/ this.endPoint = endPoint; /**The {@link Style} of the quad*/ this.style = new Style(); this.style.gradientBounds = this.getBounds(); /**Serialization type*/ this.oType = 'QuadCurve'; //object type used for JSON deserialization } /**Creates a {QuadCurve} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {QuadCurve} a newly constructed QuadCurve *@author Alex Gheorghiu **/ QuadCurve.load = function(o){ var newQuad = new QuadCurve( Point.load(o.startPoint), Point.load(o.controlPoint), Point.load(o.endPoint) ); newQuad.style = Style.load(o.style); return newQuad; }; /**Creates an {Array} of {QuadCurve} out of JSON parsed object *@param {JSONObject} v - the JSON parsed object (actually an {Array} of {JSONObject}s *@return {Array} of {QuadCurve}s *@author Alex Gheorghiu **/ QuadCurve.loadArray = function(v){ var quads = []; for(var i=0; ihttp://rosettacode.org/wiki/Bitmap/B%C3%A9zier_curves/Quadratic */ deprecated__near:function(x, y, radius){ var polls=100; if(!Util.isPointInside(new Point(x,y), [this.startPoint, this.controlPoint, this.endPoint]) && !this.startPoint.near(x,y,radius) && ! this.endPoint.near(x,y,radius)){ return false;//not inside the control points, so can't be near the line } var low=0; var high=polls; var i=(high-low)/2; while(i >= low && i <= high && high-low>0.01){//high-low indicates>0.01 stops us from taking increasingly tiny steps i=low+(high-low)/2 //we want the mid point //don't fully understand this var t = i / polls; var fromEnd = Math.pow((1.0 - t), 2); //get how far from end we are and square it var a = 2.0 * t * (1.0 - t); var fromStart = Math.pow(t, 2); //get how far from start we are and square it var newX = fromEnd * this.startPoint.x + a * this.controlPoint.x + this.fromStart * this.endPoint.x;//? var newY = fromEnd * this.startPoint.y + a * this.controlPoint.y + this.fromStart * this.endPoint.y;//? var p = new Point(newX,newY); if(p.near(x, y, radius)){ return true; } //get distance between start and the point we are looking for, and the current point on line var pToStart=Math.sqrt(Math.pow(this.startPoint.x-p.x,2)+Math.pow(this.startPoint.y-p.y,2)); var myToStart=Math.sqrt(Math.pow(this.startPoint.x-x,2)+Math.pow(this.startPoint.y-y,2)); //if our point is closer to start, we know that our cursor must be between start and where we are if(myToStartjava.awt.geom.QuadCurve2D * @author (just converted to JavaScript) alex@scriptoid.com */ deprecated_2_contains:function(x,y) { var x1 = this.startPoint.x; var y1 = this.startPoint.y; var xc = this.controlPoint.x; var yc = this.controlPoint.y; var x2 = this.endPoint.x; var y2 = this.endPoint.y; /* * We have a convex shape bounded by quad curve Pc(t) * and ine Pl(t). * * P1 = (x1, y1) - start point of curve * P2 = (x2, y2) - end point of curve * Pc = (xc, yc) - control point * * Pq(t) = P1*(1 - t)^2 + 2*Pc*t*(1 - t) + P2*t^2 = * = (P1 - 2*Pc + P2)*t^2 + 2*(Pc - P1)*t + P1 * Pl(t) = P1*(1 - t) + P2*t * t = [0:1] * * P = (x, y) - point of interest * * Let's look at second derivative of quad curve equation: * * Pq''(t) = 2 * (P1 - 2 * Pc + P2) = Pq'' * It's constant vector. * * Let's draw a line through P to be parallel to this * vector and find the intersection of the quad curve * and the line. * * Pq(t) is point of intersection if system of equations * below has the solution. * * L(s) = P + Pq''*s == Pq(t) * Pq''*s + (P - Pq(t)) == 0 * * | xq''*s + (x - xq(t)) == 0 * | yq''*s + (y - yq(t)) == 0 * * This system has the solution if rank of its matrix equals to 1. * That is, determinant of the matrix should be zero. * * (y - yq(t))*xq'' == (x - xq(t))*yq'' * * Let's solve this equation with 't' variable. * Also let kx = x1 - 2*xc + x2 * ky = y1 - 2*yc + y2 * * t0q = (1/2)*((x - x1)*ky - (y - y1)*kx) / * ((xc - x1)*ky - (yc - y1)*kx) * * Let's do the same for our line Pl(t): * * t0l = ((x - x1)*ky - (y - y1)*kx) / * ((x2 - x1)*ky - (y2 - y1)*kx) * * It's easy to check that t0q == t0l. This fact means * we can compute t0 only one time. * * In case t0 < 0 or t0 > 1, we have an intersections outside * of shape bounds. So, P is definitely out of shape. * * In case t0 is inside [0:1], we should calculate Pq(t0) * and Pl(t0). We have three points for now, and all of them * lie on one line. So, we just need to detect, is our point * of interest between points of intersections or not. * * If the denominator in the t0q and t0l equations is * zero, then the points must be collinear and so the * curve is degenerate and encloses no area. Thus the * result is false. */ var kx = x1 - 2 * xc + x2; var ky = y1 - 2 * yc + y2; var dx = x - x1; var dy = y - y1; var dxl = x2 - x1; var dyl = y2 - y1; var t0 = (dx * ky - dy * kx) / (dxl * ky - dyl * kx); if (t0 < 0 || t0 > 1 || t0 != t0) { return false; } var xb = kx * t0 * t0 + 2 * (xc - x1) * t0 + x1; var yb = ky * t0 * t0 + 2 * (yc - y1) * t0 + y1; var xl = dxl * t0 + x1; var yl = dyl * t0 + y1; return (x >= xb && x < xl) || (x >= xl && x < xb) || (y >= yb && y < yl) || (y >= yl && y < yb); }, contains:function(x,y) { var points = this.getPoints(); var polyline = new Polyline(); polyline.points = points; return polyline.contains(x, y); }, toString:function(){ return 'quad(' + this.startPoint + ',' + this.controlPoint + ',' + this.endPoint + ')'; }, /**Render the SVG fragment for this primitive *@see http://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands **/ toSVG:function(){ // var result = "\n" + repeat("\t", INDENTATION) + ''; return result; } }; /** * Creates an instance of a cubic curve. * A curved line determined by 2 normal points (startPoint and endPoint) and 2 control points (controlPoint1, controlPoint2) * * @constructor * @this {CubicCurve} * @param {Point} startPoint - starting point of the line * @param {Point} controlPoint1 - 1st control point of the line * @param {Point} controlPoint2 - 2nd control point of the line * @param {Point} endPoint - the ending point of the line * @see http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves **/ function CubicCurve(startPoint, controlPoint1, controlPoint2, endPoint){ /**The start {@link Point}*/ this.startPoint = startPoint; /**The first controll {@link Point}*/ this.controlPoint1 = controlPoint1; /**The second controll {@link Point}*/ this.controlPoint2 = controlPoint2; /**The end {@link Point}*/ this.endPoint = endPoint; /**The {@link Style} of the quad*/ this.style = new Style(); this.style.gradientBounds = this.getBounds(); /**Object type used for JSON deserialization*/ this.oType = 'CubicCurve'; } /**Creates a {CubicCurve} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {CubicCurve} a newly constructed CubicCurve *@author Alex Gheorghiu **/ CubicCurve.load = function(o){ var newCubic = new CubicCurve( Point.load(o.startPoint), Point.load(o.controlPoint1), Point.load(o.controlPoint2), Point.load(o.endPoint) ); newCubic.style = Style.load(o.style); return newCubic; } CubicCurve.prototype = { constructor : CubicCurve, transform:function(matrix){ if(this.style != null){ this.style.transform(matrix); } this.startPoint.transform(matrix); this.controlPoint1.transform(matrix); this.controlPoint2.transform(matrix); this.endPoint.transform(matrix); }, paint:function(context){ context.beginPath(); // Log.group("CubicCurve:paint() "); if(this.style != null){ this.style.setupContext(context); Log.info("stroke style : " + this.style.strokeStyle); } context.beginPath(); context.moveTo(this.startPoint.x, this.startPoint.y); context.bezierCurveTo( this.controlPoint1.x, this.controlPoint1.y, this.controlPoint2.x, this.controlPoint2.y, this.endPoint.x, this.endPoint.y ); if(this.style.fillStyle != null && this.style.fillStyle != ""){ // Log.info("Fill provided"); context.fill(); } else{ // Log.info("Fill NOT provided") } if(this.style.strokeStyle != null && this.style.strokeStyle != ""){ // Log.info("Stroke provided"); context.stroke(); } else{ // Log.info("Stroke NOT provided") } Log.groupEnd(); }, clone:function(){ var ret = new CubicCurve(this.startPoint.clone(),this.controlPoint1.clone(), this.controlPoint2.clone(),this.endPoint.clone()); ret.style = this.style.clone(); return ret; }, equals:function(anotherCubicCurve){ if(!anotherCubicCurve instanceof CubicCurve){ return false; } return this.startPoint.equals(anotherCubicCurve.startPoint) && this.controlPoint1.equals(anotherCubicCurve.controlPoint1) && this.controlPoint2.equals(anotherCubicCurve.controlPoint2) && this.endPoint.equals(anotherCubicCurve.endPoint); }, /** * Inspired by java.awt.geom.CubicCurve2D * @author Alex */ contains:function(x, y) { /*This piece of code is kept as a reference. The "old" idea was to treat the curve as a polygon thus closing it and apply a similar algorithm as for polygon. Of course it does not offer precision :( // if (!(x * 0.0 + y * 0.0 == 0.0)) { // Either x or y was infinite or NaN. // A NaN always produces a negative response to any test // and Infinity values cannot be "inside" any path so // they should return false as well. // return false; } // We count the "Y" crossings to determine if the point is // inside the curve bounded by its closing line. var x1 = this.startPoint.x //getX1(); var y1 = this.startPoint.y //getY1(); var x2 = this.endPoint.x //getX2(); var y2 = this.endPoint.y //getY2(); var crossings = (Util.pointCrossingsForLine(x, y, x1, y1, x2, y2) + Util.pointCrossingsForCubic(x, y, x1, y1, this.controlPoint1.x, this.controlPoint1.y, this.controlPoint2.x, this.controlPoint2.y, x2, y2, 0)); return ((crossings & 1) == 1); */ /*Algorithm: split the Bezier curve into an aproximative polyline and * use {Polyline}'s contains(...) method * */ var poly = new Polyline(); for(var t=0; t<=1; t=t+0.01){ //101 points :D var a = Math.pow((1 - t), 3); var b = 3 * t * Math.pow((1 - t), 2); var c = 3 * Math.pow(t, 2) * (1 - t); var d = Math.pow(t, 3); var Xp = a * this.startPoint.x + b * this.controlPoint1.x + c * this.controlPoint2.x + d * this.endPoint.x; var Yp = a * this.startPoint.y + b * this.controlPoint1.y + c * this.controlPoint2.y + d * this.endPoint.y; poly.addPoint(new Point(Xp, Yp)); } //Log.info("CubicCurve: Aproximative polyline " + poly); return poly.contains(x, y); }, /** *Tests to see if a point is close enough to a cubic curve *@param {Number} x - the x coordinates of the point *@param {Number} y - the x coordinates of the point *@param {Number} radius - the radius of vicinity *@author alex *@see http://rosettacode.org/wiki/Bitmap/B%C3%A9zier_curves/Cubic */ near:function(x, y, radius){ /*Algorithm: split the Bezier curve into an aproximative polyline and use polyline's near method*/ var poly = new Polyline(); for(var t=0; t<=1; t+=0.01){ var a = Math.pow((1 - t), 3); var b = 3 * t * Math.pow((1 - t), 2); var c = 3 * Math.pow(t, 2) * (1 - t); var d = Math.pow(t, 3); var Xp = a * this.startPoint.x + b * this.controlPoint1.x + c * this.controlPoint2.x + d * this.endPoint.x; var Yp = a * this.startPoint.y + b * this.controlPoint1.y + c * this.controlPoint2.y + d * this.endPoint.y; poly.addPoint(new Point(Xp, Yp)); } return poly.near(x, y, radius); }, //TODO: dynamically adjust until the length of a segment is small enough //(1 unit)? getPoints:function(){ var STEP = 0.01; var points = []; for(var t = 0; t<=1; t+=STEP){ points.push(this.getPoint(t)); } return points; }, getBounds:function(){ return Util.getBounds(this.getPoints()); }, /**Computes the length of the Cubic Curve * TODO: This is by far not the best algorithm but only an aproximation. * */ getLength:function(){ /*Algorithm: split the Bezier curve into an aproximative polyline and use polyline's near method*/ var poly = new Polyline(); poly.points = this.getPoints(); return poly.getLength(); }, /**Return the {Point} corresponding a t value, from parametric equation of Curve * @param {Number} t the value of parameter t, where t in [0,1]*/ getPoint: function(t){ var a = Math.pow((1 - t), 3); var b = 3 * t * Math.pow((1 - t), 2); var c = 3 * Math.pow(t, 2) * (1 - t); var d = Math.pow(t, 3); var Xp = a * this.startPoint.x + b * this.controlPoint1.x + c * this.controlPoint2.x + d * this.endPoint.x; var Yp = a * this.startPoint.y + b * this.controlPoint1.y + c * this.controlPoint2.y + d * this.endPoint.y; return new Point(Xp, Yp); }, /**Return the {Point} corresponding the t certain t value. * NOTE: t is the visual percentage of a point from the start of the * primitive and the result is * different than the t from getPoint(...) method * @param {Number} t the value of parameter t, where t in [0,1]*/ getVisualPoint:function (t){ var points = this.getPoints(); var polyline = new Polyline(); polyline.points = points; return polyline.getVisualPoint(t); }, toString:function(){ return 'quad(' + this.startPoint + ',' + this.controlPoint1 + ',' + this.controlPoint2 + ',' + this.endPoint + ')'; }, /**Render the SVG fragment for this primitive *@see http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands **/ toSVG:function(){ // var result = "\n" + repeat("\t", INDENTATION) + ''; return result; } } /** * Draws an arc. * To allow easy transformations of the arc we will simulate the arc by a series of curves * * @constructor * @this {Arc} * @param {Number} x - the X coordinates of the "imaginary" circle of which the arc is part of * @param {Number} y - the Y coordinates of the "imaginary" circle of which the arc is part of * @param {Number} radius - the radius of the arc * @param {Number} startAngle - the degrees (not radians as in Canvas'specs) of the starting angle for this arc * @param {Number} endAngle - the degrees (not radians as in Canvas'specs) of the starting angle for this arc * @param {Number} direction - the direction of drawing. For now it's from startAngle to endAngle (anticlockwise). Not really used * @param {Number} styleFlag (optional) - * 1: close path between start of arc and end, * 2: draw pie slice, line to center point, line to start point * default: empty/0/anything else: just draw the arc * TODO: make it a class constant * @see http://STACKoverflow.com/questions/2688808/drawing-quadratic-bezier-circles-with-a-given-radius-how-to-determine-control-po **/ function Arc(x, y, radius, startAngle, endAngle, direction, styleFlag){ /**End angle. Required for dashedArc*/ this.endAngle = endAngle; /**Start angle. required for dashedArc*/ this.startAngle = startAngle; /**The center {@link Point} of the circle*/ this.middle = new Point(x,y); /**The radius of the circle*/ this.radius = radius; /**An {Array} of {@link QuadCurve}s used to draw the arc*/ this.curves = []; /**Accuracy. It tells the story of how many QuadCurves we will use*/ var numControlPoints = 8; /**The start {@link Point}*/ this.startPoint = null; /**The end {@link Point}*/ this.endPoint = null; /**The start angle, in radians*/ this.startAngleRadians = 0; /**The end angle, in radians*/ this.endAngleRadians = 0; //code shamelessly stollen from the above site. var start = Math.PI/180 * startAngle; //convert the angles back to radians this.startAngleRadians = start; this.endAngleRadians = Math.PI/180 * endAngle; var arcLength = (Math.PI/180*(endAngle-startAngle))/ numControlPoints; for (var i = 0; i < numControlPoints; i++) { if (i < 1) { this.startPoint = new Point(x + radius * Math.cos(arcLength * i),y + radius * Math.sin(arcLength * i)) } var startPoint=new Point(x + radius * Math.cos(arcLength * i),y + radius * Math.sin(arcLength * i)) //control radius formula //where does it come from, why does it work? var controlRadius = radius / Math.cos(arcLength * .5); //the control point is plotted halfway between the arcLength and uses the control radius var controlPoint=new Point(x + controlRadius * Math.cos(arcLength * (i + 1) - arcLength * .5),y + controlRadius * Math.sin(arcLength * (i + 1) - arcLength * .5)) if (i == (numControlPoints - 1)) { this.endPoint = new Point(x + radius * Math.cos(arcLength * (i + 1)),y + radius * Math.sin(arcLength * (i + 1))); } var endPoint=new Point(x + radius * Math.cos(arcLength * (i + 1)),y + radius * Math.sin(arcLength * (i + 1))); //if we arent starting at 0, rotate it to where it needs to be //move to origin (O) startPoint.transform(Matrix.translationMatrix(-x,-y)); controlPoint.transform(Matrix.translationMatrix(-x,-y)); endPoint.transform(Matrix.translationMatrix(-x,-y)); //rotate by angle (start) startPoint.transform(Matrix.rotationMatrix(start)); controlPoint.transform(Matrix.rotationMatrix(start)); endPoint.transform(Matrix.rotationMatrix(start)); //move it back to where it was startPoint.transform(Matrix.translationMatrix(x,y)); controlPoint.transform(Matrix.translationMatrix(x,y)); endPoint.transform(Matrix.translationMatrix(x,y)); this.curves.push(new QuadCurve(startPoint,controlPoint,endPoint)); } /**The style flag - see contructor's arguments*/ this.styleFlag = styleFlag; /**The {@link Style} of the arc*/ this.style = new Style(); this.style.gradientBounds = this.getBounds(); /**Adding a reference to the end point makes the transform code hugely cleaner*/ this.direction = direction; /**Object type used for JSON deserialization*/ this.oType = 'Arc'; } /**Creates a {Arc} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {Arc} a newly constructed Arc *@author Alex Gheorghiu **/ Arc.load = function(o){ var newArc = new Arc(); newArc.endAngle = o.endAngle; newArc.startAngle = o.startAngle; newArc.middle = Point.load(o.middle); newArc.radius = o.radius newArc.curves = QuadCurve.loadArray(o.curves); /*we need to load these 'computed' values as they are computed only in constructor :( *TODO: maybe make a new function setUp() that deal with this*/ newArc.startPoint = Point.load(o.startPoint); newArc.endPoint = Point.load(o.endPoint); newArc.startAngleRadians = o.startAngleRadians; newArc.endAngleRadians = o.endAngleRadians; newArc.styleFlag = o.styleFlag; newArc.style = Style.load(o.style); newArc.direction = o.direction; return newArc; } /**Creates a {Arc} out of an Array of JSON parsed object *@param {Array} v - an {Array} of JSON parsed objects *@return {Array}of newly constructed Arcs *@author Alex Gheorghiu **/ Arc.loadArray = function(v){ var newArcs = []; for(var i=0; i element. *Note: We might use the SVG's arc command but what if the arc got distorted by a transformation? *@see http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands *@see http://tutorials.jenkov.com/svg/path-element.html *@author Alex **/ toSVG: function(){ var r = "\n" + repeat("\t", INDENTATION) + 'http://www.codeguru.com/cpp/g-m/gdi/article.php/c131 * @see http://www.tinaja.com/glib/ellipse4.pdf * @author Zack Newsham **/ function Ellipse(centerPoint, width, height) { /**"THE" constant*/ var EToBConst = 0.2761423749154; /**Width offset*/ var offsetWidth = width * 2 * EToBConst; /**Height offset*/ var offsetHeight = height * 2 * EToBConst; /**The center {@link Point}*/ this.centerPoint = centerPoint; /**Top left {@link CubicCurve}*/ this.topLeftCurve = new CubicCurve(new Point(centerPoint.x-width,centerPoint.y),new Point(centerPoint.x-width,centerPoint.y-offsetHeight),new Point(centerPoint.x-offsetWidth,centerPoint.y-height),new Point(centerPoint.x,centerPoint.y-height)); /**Top right {@link CubicCurve}*/ this.topRightCurve = new CubicCurve(new Point(centerPoint.x,centerPoint.y-height),new Point(centerPoint.x+offsetWidth,centerPoint.y-height),new Point(centerPoint.x+width,centerPoint.y-offsetHeight),new Point(centerPoint.x+width,centerPoint.y)); /**Bottom right {@link CubicCurve}*/ this.bottomRightCurve = new CubicCurve(new Point(centerPoint.x+width,centerPoint.y),new Point(centerPoint.x+width,centerPoint.y+offsetHeight),new Point(centerPoint.x+offsetWidth,centerPoint.y+height),new Point(centerPoint.x,centerPoint.y+height)); /**Bottom left {@link CubicCurve}*/ this.bottomLeftCurve = new CubicCurve(new Point(centerPoint.x,centerPoint.y+height),new Point(centerPoint.x-offsetWidth,centerPoint.y+height),new Point(centerPoint.x-width,centerPoint.y+offsetHeight),new Point(centerPoint.x-width,centerPoint.y)); /**The matrix array*/ this.matrix = null; //TODO: do we really need this? /**The {@link Style} used*/ this.style = new Style(); this.style.gradientBounds = this.getBounds(); /**Oject type used for JSON deserialization*/ this.oType = 'Ellipse'; } /**Creates a new {Ellipse} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {Ellipse} a newly constructed Ellipse *@author Alex Gheorghiu **/ Ellipse.load = function(o){ var newEllipse= new Ellipse(new Point(0,0), 0, 0); //fake ellipse (if we use a null centerPoint we got errors) newEllipse.offsetWidth = o.offsetWidth; newEllipse.offsetHeight = o.offsetHeight; newEllipse.centerPoint = Point.load(o.centerPoint); newEllipse.topLeftCurve = CubicCurve.load(o.topLeftCurve); newEllipse.topRightCurve = CubicCurve.load(o.topRightCurve); newEllipse.bottomRightCurve = CubicCurve.load(o.bottomRightCurve); newEllipse.bottomLeftCurve = CubicCurve.load(o.bottomLeftCurve); this.matrix = Matrix.clone(o.matrix); newEllipse.style = Style.load(o.style); return newEllipse; } Ellipse.prototype = { constructor: Ellipse, transform:function(matrix){ this.topLeftCurve.transform(matrix); this.topRightCurve.transform(matrix); this.bottomLeftCurve.transform(matrix); this.bottomRightCurve.transform(matrix); this.centerPoint.transform(matrix); if(this.style){ this.style.transform(matrix); } }, paint:function(context){ if(this.style!=null){ this.style.setupContext(context); } context.beginPath(); context.moveTo(this.topLeftCurve.startPoint.x, this.topLeftCurve.startPoint.y); context.bezierCurveTo(this.topLeftCurve.controlPoint1.x, this.topLeftCurve.controlPoint1.y, this.topLeftCurve.controlPoint2.x, this.topLeftCurve.controlPoint2.y, this.topLeftCurve.endPoint.x, this.topLeftCurve.endPoint.y); context.bezierCurveTo(this.topRightCurve.controlPoint1.x, this.topRightCurve.controlPoint1.y, this.topRightCurve.controlPoint2.x, this.topRightCurve.controlPoint2.y, this.topRightCurve.endPoint.x, this.topRightCurve.endPoint.y); context.bezierCurveTo(this.bottomRightCurve.controlPoint1.x, this.bottomRightCurve.controlPoint1.y, this.bottomRightCurve.controlPoint2.x, this.bottomRightCurve.controlPoint2.y, this.bottomRightCurve.endPoint.x, this.bottomRightCurve.endPoint.y); context.bezierCurveTo(this.bottomLeftCurve.controlPoint1.x, this.bottomLeftCurve.controlPoint1.y, this.bottomLeftCurve.controlPoint2.x, this.bottomLeftCurve.controlPoint2.y, this.bottomLeftCurve.endPoint.x, this.bottomLeftCurve.endPoint.y); //first fill if(this.style.fillStyle!=null && this.style.fillStyle!=""){ context.fill(); } //then stroke if(this.style.strokeStyle!=null && this.style.strokeStyle!=""){ context.stroke(); } }, contains:function(x,y){ var points = this.topLeftCurve.getPoints(); var curves = [this.topRightCurve, this.bottomRightCurve, this.bottomLeftCurve]; for(var i=0; ihttp://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands *@author Alex Gheorghiu **/ toSVG: function(){ var result = "\n" + repeat("\t", INDENTATION) + ''; return result; }, getPoints:function(){ var points = []; var curves = [this.topLeftCurve, this.topRightCurve,this.bottomRightCurve,this.bottomLeftCurve]; for(var i=0; ihttp://www.codeguru.com/cpp/g-m/gdi/article.php/c131 * @see http://www.tinaja.com/glib/ellipse4.pdf * @author Zack Newsham **/ function DashedArc(x, y, radius, startAngle, endAngle, direction, styleFlag, dashGap){ /**The "under the hood" {@link Arc}*/ this.arc = new Arc(x, y, radius, startAngle, endAngle, direction, styleFlag); /*The {@link Style} used**/ this.style = this.arc.style; /**The gap between dashes*/ this.dashWidth = dashGap; /**An {Array} or {@link Arc}s*/ this.lines = []; //an {Array} of {Arc}s //init the parts for(var i=0; i<100; i += this.dashWidth){ var a = new Arc(x, y, radius+this.style.lineWidth/2, (endAngle-startAngle)/100*i, (endAngle-startAngle)/100*(i+1), false); a.style.strokeStyle = this.style.strokeStyle; this.lines.push(a); } /**Object type used for JSON deserialization*/ this.oType = 'DashedArc'; } /**Creates a new {Ellipse} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {DashedArc} a newly constructed DashedArc *@author Alex Gheorghiu **/ DashedArc.load = function(o){ var newDashedArc = new DashedArc(100,100,30,0,360,false,0,6); //fake dashed (if we do not use it we got errors - endless loop) newDashedArc.style.fillStyle="#ffffff" newDashedArc.arc = Arc.load(o.arc); newDashedArc.style = newDashedArc.arc.style; //strange but... newDashedArc.dashWidth = o.dashWidth; newDashedArc.lines = Arc.loadArray(o.lines); return newDashedArc; } DashedArc.prototype = { constructor: DashedArc, transform:function(matrix){ this.arc.transform(matrix); for (var i=0; i **/ Path.load = function(o){ var newPath = new Path(); //fake path newPath.style = Style.load(o.style); for(var i=0; i< o.primitives.length; i++){ /**We can not use instanceof Point construction as *the JSON objects are typeless... so JSONObject are simply objects */ if(o.primitives[i].oType == 'Point'){ newPath.primitives.push(Point.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Line'){ newPath.primitives.push(Line.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Polyline'){ newPath.primitives.push(Polyline.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Polygon'){ newPath.primitives.push(Polygon.load(o.primitives[i])) } else if(o.primitives[i].oType == 'QuadCurve'){ newPath.primitives.push(QuadCurve.load(o.primitives[i])) } else if(o.primitives[i].oType == 'CubicCurve'){ newPath.primitives.push(CubicCurve.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Arc'){ newPath.primitives.push(Arc.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Ellipse'){ newPath.primitives.push(Ellipse.load(o.primitives[i])) } else if(o.primitives[i].oType == 'DashedArc'){ newPath.primitives.push(DashedArc.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Path'){ newPath.primitives.push(Path.load(o.primitives[i])) } } return newPath; } Path.prototype = { constructor : Path, transform:function(matrix){ for(var i = 0; i*/ equals : function(anotherPath){ if(!anotherPath instanceof Path){ return false; } for(var i=0; ihttp://tutorials.jenkov.com/svg/path-element.html *@example * <path d="M50,50 * A30,30 0 0,1 35,20 * L100,100 * M110,110 * L100,0" * style="stroke:#660000; fill:none;"/> */ toSVG: function(){ var result = "\n" + repeat("\t", INDENTATION) + ' throw 'Path:toSVG()->Arc - not implemented'; } else if(primitive instanceof Polyline){ //TODO: implement me throw 'Path:toSVGPolylineArc - not implemented'; } else{ throw 'Path:toSVG()->unknown primitive rendering not implemented'; } previousPrimitive = primitive; }//end for result += '" '; //end of primitive shapes // result += ' fill="none" stroke="#0F0F00" /> '; //end of path result += this.style.toSVG(); //end of path result += ' />'; //end of path return result; } } /**A figure is simply a collection of basic primitives: Points, Lines, Arcs, Curves and Paths * A figure should not care about grouping primitives into Paths, each shape should draw itself. * The Figure only delegate the painting to the composing shape. * * @constructor * @this {Figure} * @param {String} name - the name of the figure * **/ function Figure(name) { /**Each Figure will have an unique Id on canvas*/ this.id = STACK.generateId(); /**Figure's name*/ this.name = name; /**An {Array} of primitives that make the figure*/ this.primitives = []; /**the Group'id to which this figure belongs to*/ this.groupId = -1; /*Keeps track of all the handles for a figure*/ //this.handles = []; //this.handleSelectedIndex=-1; //this.builder = new Builder(this.id); /**An {Array} of {@link BuilderProperty} objects*/ this.properties = []; /**The {@link Style} use to draw this figure*/ this.style = new Style(); this.style.gradientBounds = this.getBounds(); /**We keep the figure position by having different points *[central point of the figure, the middle of upper edge] * An {Array} or {@link Point}s **/ this.rotationCoords = []; /**A {String} that point to a location*/ this.url = ''; /**Object type used for JSON deserialization*/ this.oType = 'Figure'; /**Object CCForm_Shape used for ccflow */ this.CCForm_Shape = null; /**Object CCForm_MyPK used for ccflow */ this.CCForm_MyPK = null; } /**Creates a new {Figure} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {Figure} a newly constructed Figure *@author Alex Gheorghiu **/ Figure.load = function(o){ var newFigure = new Figure(); //fake dashed (if we do not use it we got errors - endless loop) newFigure.id = o.id; newFigure.name = o.name; for(var i=0; i< o.primitives.length; i++){ /**We can not use instanceof Point construction as *the JSON objects are typeless... so JSONObject are simply objects */ if(o.primitives[i].oType == 'Point'){ newFigure.addPrimitive(Point.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Line'){ newFigure.addPrimitive(Line.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Polyline'){ newFigure.addPrimitive(Polyline.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Polygon'){ newFigure.addPrimitive(Polygon.load(o.primitives[i])) } else if(o.primitives[i].oType == 'DottedPolygon'){ newFigure.addPrimitive(DottedPolygon.load(o.primitives[i])) } else if(o.primitives[i].oType == 'QuadCurve'){ newFigure.addPrimitive(QuadCurve.load(o.primitives[i])) } else if(o.primitives[i].oType == 'CubicCurve'){ newFigure.addPrimitive(CubicCurve.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Arc'){ newFigure.addPrimitive(Arc.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Ellipse'){ newFigure.addPrimitive(Ellipse.load(o.primitives[i])) } else if(o.primitives[i].oType == 'DashedArc'){ newFigure.addPrimitive(DashedArc.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Text'){ newFigure.addPrimitive(Text.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Path'){ newFigure.addPrimitive(Path.load(o.primitives[i])) } else if(o.primitives[i].oType == 'Figure'){ newFigure.addPrimitive(Figure.load(o.primitives[i])); //kinda recursevly } else if(o.primitives[i].oType == 'ImageFrame'){ newFigure.addPrimitive(ImageFrame.load(o.primitives[i])); //kinda recursevly } }//end for newFigure.groupId = o.groupId; newFigure.properties = BuilderProperty.loadArray(o.properties); newFigure.style = Style.load(o.style); newFigure.rotationCoords = Point.loadArray(o.rotationCoords); newFigure.url = o.url; newFigure.CCForm_Shape = o.CCForm_Shape; newFigure.CCForm_MyPK = o.CCForm_MyPK; return newFigure ; } /**Creates a new {Array} of {Figure}s out of JSON parsed object *@param {JSONObject} v - the JSON parsed object *@return {Array} of newly constructed {Figure}s *@author Alex Gheorghiu *@author Janis Sejans **/ Figure.loadArray = function(v){ var newFigures = []; for(var i=0; i *TODO: From Janis: we don`t have Undo for this operation *@deprecated */ applyAnotherFigureStyle: function (anotherFigure) { this.style = anotherFigure.style.clone(); var newText = this.getText(); //will contain new text object //TODO: From Janis: there is some problem if applying text twice, the getText returns empty string, this means it is not properly cloned if (newText instanceof Text) { var currTextStr = newText.getTextStr(); //remember text str var currTextVector = newText.vector; //remember text vector newText = anotherFigure.getText().clone(); newText.setTextStr(currTextStr); //restore text str newText.vector = currTextVector; //restore text vector this.setText(newText); } }, contains: function (x, y) { var points = []; for (var i = 0; i < this.primitives.length; i++) { if (this.primitives[i].contains(x, y)) { return true; } points = points.concat(this.primitives[i].getPoints()); } return Util.isPointInside(new Point(x, y), points); }, /** * @return {Array} - returns [minX, minY, maxX, maxY] - bounds, where * all points are in the bounds. */ getBounds: function () { var points = []; for (var i = 0; i < this.primitives.length; i++) { var bounds = this.primitives[i].getBounds(); points.push(new Point(bounds[0], bounds[1])); points.push(new Point(bounds[2], bounds[3])); } return Util.getBounds(points); }, paint: function (context) { if (this.style) { this.style.setupContext(context); } for (var i = 0; i < this.primitives.length; i++) { context.save(); var primitive = this.primitives[i]; var oldStyle = null; if (primitive.style) { //save primitive's style oldStyle = primitive.style.clone(); } if (primitive.style == null) { //if primitive does not have a style use Figure's one primitive.style = this.style.clone(); } else { //if primitive has a style merge it primitive.style.merge(this.style); } primitive.paint(context); primitive.style = oldStyle; // if(this.style.image != null){ //TODO: should a figure has a Style can't just delegate all to primitives? // //clip required for background images, there were two methods, this was the second I tried // //neither work in IE // context.clip(); // context.save(); // if(this.rotationCoords.length != 0){ // var angle=Util.getAngle(this.rotationCoords[0], this.rotationCoords[1]); // if(IE && angle==0){ // angle=0.00000001;//stupid excanves, without this it puts all images down and right of the correct location // //and by an amount relative to the distane from the top left corner // } // // //if we perform a rotation on the actual rotationCoords[0] (centerPoint), when we try to translate it back, // //rotationCoords[0] will = 0,0, so we create a clone that does not get changed // var rotPoint = this.rotationCoords[0].clone(); // // //move to origin, make a rotation, move back in place // this.transform(Matrix.translationMatrix(-rotPoint.x, -rotPoint.y)) // this.transform(Matrix.rotationMatrix(-angle)); // this.transform(Matrix.translationMatrix(rotPoint.x, rotPoint.y)) // // //TODO: these are not used...so why the whole acrobatics ? // //this was the second method that is also not supported by IE, get the image, place it in // //the correct place, then shrink it, so its still an 'image mask' but it is only a small image // //context.scale below is also part of this // //var shrinkBounds = this.getBounds(); // // //move back to origin, 'undo' the rotation, move back in place // this.transform(Matrix.translationMatrix(-rotPoint.x, -rotPoint.y)) // this.transform(Matrix.rotationMatrix(angle)); // this.transform(Matrix.translationMatrix(rotPoint.x, rotPoint.y)) // // //rotate current canvas to prepare it to draw the image (you can not roate the image...:D) // context.translate(rotPoint.x,rotPoint.y); // context.rotate(angle); // //context.scale(0.01,0.01)//1/getCanvas().width*shrinkBounds[0]+(shrinkBounds[2]-shrinkBounds[0])/2,1/getCanvas().width*shrinkBounds[1]+(shrinkBounds[3]-shrinkBounds[1])/2) // context.translate(-rotPoint.x,-rotPoint.y); // } // //draw image // /*context.fill(); // context.beginPath(); // context.globalCompositeOperation = "source-atop" // clip works best,but this works too, neither will work in IE*/ // //context.fill(); // context.drawImage(this.style.image,this.rotationCoords[0].x-this.style.image.width/2,this.rotationCoords[0].y-this.style.image.height/2,this.style.image.width,this.style.image.height) // // context.restore(); // } // else if (this.style.image!=null){ // context.fill(); // } context.restore(); } }, equals: function (anotherFigure) { if (!anotherFigure instanceof Figure) { Log.info("Figure:equals() 0"); return false; } if (this.primitives.length == anotherFigure.primitives.length) { for (var i = 0; i < this.primitives.length; i++) { if (!this.primitives[i].equals(anotherFigure.primitives[i])) { Log.info("Figure:equals() 1"); return false; } } } else { Log.info("Figure:equals() 2"); return false; } //test group if (this.groupId != anotherFigure.groupId) { return false; } //test rotation coords if (this.rotationCoords.length == anotherFigure.rotationCoords.length) { for (var i in this.rotationCoords) { if (!this.rotationCoords[i].equals(anotherFigure.rotationCoords[i])) { return false; } } } else { return false; } //test style if (!this.style.equals(anotherFigure.style)) { return false; } //test url if (!this.url == anotherFigure.url) { return false; } return true; }, near: function (x, y, radius) { for (var i = 0; i < this.primitives.length; i++) { if (this.primitives[i].near(x, y, radius)) { return true; } } return false; }, toString: function () { var result = this.name + ' [id: ' + this.id + '] ('; for (var i = 0; i < this.primitives.length; i++) { result += this.primitives[i].toString(); } result += ')'; return result; }, toSVG: function () { var tempSVG = ''; tempSVG += "\n" + repeat("\t", INDENTATION) + ""; for (var i = 0; i < this.primitives.length; i++) { var primitive = this.primitives[i]; var oldStyle = null; if (primitive.style) { //save primitive's style oldStyle = primitive.style.clone(); } if (primitive.style == null) { //if primitive does not have a style use Figure's one primitive.style = this.style; } else { //if primitive has a style merge it primitive.style.merge(this.style); } tempSVG += this.primitives[i].toSVG(); //URL not exported throw Exception("Figure->toSVG->URL not exported"); //restore primitives style primitive.style = oldStyle; } tempSVG += "\n" + repeat("\t", INDENTATION) + "" + "\n"; return tempSVG; } } /** * Implements a NURBS component in Diagramo * @param {Array} points - an {Array} of {Point}s * @see http://en.wikipedia.org/wiki/Non-uniform_rational_B-spline * * http://www.w3.org/Graphics/SVG/IG/resources/svgprimer.html#path_Q * http://math.stackexchange.com/questions/92246/aproximate-n-grade-bezier-through-cubic-and-or-quadratic-bezier-curves * @see http://stackoverflow.com/questions/1257168/how-do-i-create-a-bezier-curve-to-represent-a-smoothed-polyline * @see http://www.codeproject.com/KB/graphics/BezierSpline.aspx Draw a Smooth Curve through a Set of 2D Points with Bezier Primitives * "Paul de Casteljau, a brilliant engineer at Citroen" * @see http://stackoverflow.com/questions/8369488/splitting-a-bezier-curve * @see http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/ * @see http://devmag.org.za/2011/06/23/bzier-path-algorithms/ * @see http://drdobbs.com/cpp/184403417 (Forward Difference Calculation of Bezier Curves) * @see http://www.timotheegroleau.com/Flash/articles/cubic_bezier_in_flash.htm * @see http://www.algorithmist.net/bezier3.html * @see http://www.caffeineowl.com/graphics/2d/vectorial/bezierintro.html * @author Alex Gheorghiu **/ function NURBS(points){ if(points.length < 2){ throw "NURBS: contructor() We need minimum 3 points to have a NURBS"; } /**The initial {@link Point}s*/ this.points = Point.cloneArray(points); /**The array of {CubicCurve} s from which the NURB will be made*/ this.fragments = this.nurbsPoints(this.points); /**The {@link Style} of the line*/ this.style = new Style(); this.style.gradientBounds = this.getBounds(); /**Serialization type*/ this.oType = 'NURBS'; //object type used for JSON deserialization } /**Creates a {NURBS} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {NURBS} a newly constructed NURBS *@author Alex Gheorghiu **/ NURBS.load = function(o){ var newNURBS = new NURBS(Point.loadArray(o.points)); newNURBS.style = Style.load(o.style); return newNURBS; }; NURBS.prototype = { /**Computes a series of Bezier(Cubic) curves to aproximate a curve modeled *by a set of points *@param {Array}- P and {Array} of {Point}s *@return an {Array} of {CubicCurve} (provided also as {Array}) *Example: * [ * [p1, p2, p3, p4], * [p1', p2', p3', p4'], * etc * ] * See /documents/specs/spline-to-bezier.pdf (pages 5 and 6 for a description) **/ nurbsPoints : function (P){ var n = P.length; /**Contains the gathered sub curves*/ var sol = []; if(n === 2){ sol.push(new Line(P[0], P[1])); return sol; } else if(n === 3){ sol.push(new QuadCurve(P[0], P[1], P[2])); return sol; } else if(n === 4){ sol.push(new CubicCurve(P[0], P[1], P[2], P[3])); return sol; } /**Computes factorial * @param {Number} k the number * */ function fact(k){ if(k===0 || k===1){ return 1; } else{ return k * fact(k-1); } } /**Computes Bernstain*/ function B(i,n,u){ return fact(n) / (fact(i) * fact(n-i))* Math.pow(u, i) * Math.pow(1-u, n-i); } /**Computes the sum between two points *@param p1 - {Point} *@param p2 - {Point} *@return {Point} the sum of initial points **/ function sum(p1, p2){ return new Point(p1.x + p2.x, p1.y + p2.y); } /**Computes the difference between first {Point} and second {Point} *@param p1 - {Point} *@param p2 - {Point} *@return {Point} the sum of initial points **/ function minus(p1, p2){ return new Point(p1.x - p2.x, p1.y - p2.y); } /**Computes the division of a {Point} by a number *@param p - {Point} *@param nr - {Number} *@return {Point} **/ function divide(p, nr){ if(nr == 0){ throw "Division by zero not allowed (yet :) " + this.callee ; } return new Point(p.x/nr, p.y/nr); } /**Computes the multiplication of a {Point} by a number *@param p - {Point} *@param nr - {Number} *@return {Point} **/ function multiply(p, nr){ return new Point (p.x * nr, p.y * nr); } /* *I do not get why first 4 must be 0 and last 3 of same value..... *but otherwise we will get division by zero */ var k = [0,0,0]; var j; for(j=0;j<=n-3;j++){ k.push(j); } k.push(n-3, n-3); for(i=1; i<=n-3; i++){ //q1 - compute start point var q1 = divide( sum( multiply(P[i], k[i+4] - k[i+2]), multiply(P[i+1], k[i+2] - k[i+1]) ), k[i+4] - k[i+1]); //q0 - compute 1st controll point var q_01 = (k[i+3] - k[i+2]) / (k[i+3] - k[i+1]); var q_02 = divide( sum( multiply(P[i-1],k[i+3] - k[i+2]), multiply(P[i], k[i+2] - k[i])), k[i+3] - k[i]); var q_03 = multiply(q1, ( k[i+2] - k[i+1])/ (k[i+3] - k[i+1]) ); var q0 = sum(multiply(q_02, q_01), q_03); //q2 - compute 2nd controll point var q2 = divide( sum( multiply(P[i], k[i+4] - k[i+3]), multiply(P[i+1], k[i+3] - k[i+1]) ), k[i+4] - k[i+1] ); //q3 - compute end point var q_31 = (k[i+3] - k[i+2]) / (k[i+4] - k[i+2]); var q_32 = divide( sum( multiply(P[i+1], k[i+5] - k[i+3]), multiply(P[i+2], k[i+3] - k[i+2]) ) , k[i+5] - k[i+2]); var q_33 = multiply(q2, (k[i+4] - k[i+3])/(k[i+4] - k[i+2]) ); var q3 = sum(multiply(q_32, q_31), q_33); //store solution sol.push( new CubicCurve(q0, q1, q2, q3) ); } return sol; }, /**Paint the NURBS*/ paint : function(context){ context.beginPath(); if(this.style != null){ this.style.setupContext(context); } //Log.info("Nr of cubic curves " + this.fragments.length); for(var f=0; f **/ contains: function(x, y){ for(var f=0; f this.getLength()/2) break; collectedLength += this.fragments[ci].getLength(); } // if (ci == 0 || ci == this.fragments.length) // throw "Assert ci it should not be " + ci; var l = this.getLength()/2 - collectedLength; var t = l/this.fragments[ci].getLength(); return this.fragments[ci].getVisualPoint(t); }, getMiddle : function() { var points = this.getPoints(); var poly = new Polyline(); poly.points = points; return poly.getVisualPoint(0.5); }, equals : function (object){ throw Exception("Not implemented"); }, toString : function(){ throw Exception("Not implemented"); }, toSVG : function() { var result = "\n" + repeat("\t", INDENTATION) + ''; return result; }, clone : function() { throw Exception("Not implemented"); }, getBounds: function(){ return Util.getBounds(this.getPoints()); }, near : function(x, y, radius){ for(var f=0; fhttp://en.wikipedia.org/wiki/Rotation_matrix */ var R90 = [ [Math.cos(0.0872664626),-Math.sin(0.0872664626), 0], [Math.sin(0.0872664626), Math.cos(0.0872664626), 0], [0, 0, 1] ]; /** * A predefined matrix of a 90 degree anti-clockwise rotation * *@see http://en.wikipedia.org/wiki/Rotation_matrix */ var R90A = [ [Math.cos(0.0872664626), Math.sin(0.0872664626), 0], [-Math.sin(0.0872664626), Math.cos(0.0872664626), 0], [0, 0, 1] ]; /** * The identity matrix */ var IDENTITY=[[1,0,1],[0,1,0],[0,0,1]]; if(typeof(document) == 'undefined'){ //test only from console print("\n--==Point==--\n"); p = new Point(10, 10); print(p); print("\n"); p.transform(R90); print(p) print("\n--==Line==--\n"); l = new Line(new Point(10, 23), new Point(34, 50)); print(l); print("\n"); print("\n--==Polyline==--\n"); polyline = new Polyline(); for(var i=0;i<5; i++){ polyline.addPoint(new Point(i, i*i)); } print(polyline); print("\n"); print("\n--==Quad curve==--\n"); q = new QuadCurve(new Point(75,25), new Point(25,25), new Point(25,62)) print(q) print("\n"); q.transform(R90); print(q) print("\n--==Cubic curve==--\n"); q = new CubicCurve(new Point(75,40), new Point(75,37), new Point(70,25), new Point(50,25)) print(q) print("\n"); q.transform(R90); print(q) print("\n--==Figure==--\n"); f = new Figure(); f.addPrimitive(p); f.addPrimitive(q); print(f); f.transform(R90); print("\n"); print(f); print("\n"); //f.draw(); }