123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- /*******************************************************************************
- AToMPM - A Tool for Multi-Paradigm Modelling
- Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
- This file is part of AToMPM.
- AToMPM is free software: you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation, either version 3 of the License, or (at your option) any later
- version.
- AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public License along
- with AToMPM. If not, see <http://www.gnu.org/licenses/>.
- *******************************************************************************/
- /********************************** IMPORTS ***********************************/
- try
- {
- var _utils = (typeof(utils) == 'undefined' ? require('./utils') : utils);
- }
- catch(ex)
- {
- throw 'missing utils.js dependency';
- }
- var SVG = {'types':{},'fns':{}};
- /**************************** CONVENIENCE WRAPPERS ****************************/
- /*---------------------------------- PUBLIC ----------------------------------*/
- SVG.fns.getPointOnPathAtRatio =
- function(path,ratio)
- {
- return new SVG.types.LinearPath(path).getPointOnPathAtRatio(ratio);
- };
- /*--------------------------------- INTERNAL ---------------------------------*/
- SVG.fns.__getRotationMatrix =
- function(a,cx,cy)
- {
- cx = cx || 0;
- cy = cy || 0;
- a *= Math.PI/180;
- var cosa = Math.cos(a),
- sina = Math.sin(a),
- _T = SVG.fns.__getTranslationMatrix(cx,cy),
- R = new SVG.types.Matrix(cosa,sina,-sina,cosa,0,0),
- T = SVG.fns.__getTranslationMatrix(-cx,-cy);
- return T.mult(R).mult(_T);
- };
- SVG.fns.__getScalingMatrix =
- function(sx,sy,cx,cy)
- {
- cx = cx || 0;
- cy = cy || 0;
- sy = sy||sx;
- var _T = SVG.fns.__getTranslationMatrix(cx,cy),
- S = new SVG.types.Matrix(sx,0,0,sy,0,0),
- T = SVG.fns.__getTranslationMatrix(-cx,-cy);
- return T.mult(S).mult(_T);
- };
- /* return a transformation matrix given a transformation string
-
- supported string formats are SVG and RaphaelJS
- SVG: space separated mix of
- rotate(_[,_,_])
- translate(_,_)
- scale(_[,_])
- matrix(_,_,_,_,_,_)
- RaphaelJS: mix of
- r_[,_,_]
- t_,_
- s_[_[,_,_]]
- m_,_,_,_,_,_,_ */
- SVG.fns.__getTransformationMatrix =
- function(tstr)
- {
- if( tstr == undefined )
- return;
- var M = new SVG.types.Matrix(),
- f;
- if( tstr.indexOf('translate') >= 0 ||
- tstr.indexOf('rotate') >= 0 ||
- tstr.indexOf('scale') >= 0 )
- {
- tstr.split(' ').forEach(
- function(t)
- {
- if( (_ = t.match(/^rotate\((.*),(.*),(.*)\)$/)) ||
- (_ = t.match(/^rotate\((.*)\)$/)) )
- f = SVG.fns.__getRotationMatrix;
- else if( (_ = t.match(/^translate\((.*),(.*)\)$/)) )
- f = SVG.fns.__getTranslationMatrix;
- else if( (_ = t.match(/^scale\((.*),(.*)\)$/)) ||
- (_ = t.match(/^scale\((.*)\)$/)) )
- f = SVG.fns.__getScalingMatrix;
- else if( (_ = t.match(
- /^matrix\((.*),(.*),(.*),(.*),(.*),(.*)\)$/)) )
- f = SVG.types.Matrix;
- else
- throw 'invalid transformation string :: '+tstr;
- M = M.mult( f.apply(this,_.slice(1).map(parseFloat)) );
- });
- }
- else
- {
- tstr.match(/([rst][-\d\.,]+)/g).forEach(
- function(t)
- {
- if( (_ = t.match(/r(.*),(.*),(.*)$/)) ||
- (_ = t.match(/^r(.*)$/)) )
- f = SVG.fns.__getRotationMatrix;
- else if( (_ = t.match(/^t(.*),(.*)$/)) )
- f = SVG.fns.__getTranslationMatrix;
- else if( (_ = t.match(/^s(.*),(.*),(.*),(.*)$/)) ||
- (_ = t.match(/^s(.*),(.*)$/)) ||
- (_ = t.match(/^s(.*)$/)) )
- f = SVG.fns.__getScalingMatrix;
- else if( (_ = t.match(/^m(.*),(.*),(.*),(.*),(.*),(.*)$/)) )
- f = SVG.types.Matrix;
- else
- throw 'invalid transformation string :: '+tstr;
- M = M.mult( f.apply(this,_.slice(1).map(parseFloat)) );
- });
- }
- return M;
- };
- SVG.fns.__getTranslationMatrix =
- function(dx,dy)
- {
- return new SVG.types.Matrix(1,0,0,1,dx,dy);
- };
- /*********************************** TYPES ************************************/
- /*------------------------------ TRANSFORMABLE -------------------------------*/
- SVG.types.ATransformable =
- function()
- {
- throw 'can\'t instantiate abstract type ATransformable';
- };
- /* rotate this element by 'a' degrees around (cx,cy) or (0,0) */
- SVG.types.ATransformable.prototype.rotate =
- function(a,cx,cy)
- {
- return this.__transform(SVG.fns.__getRotationMatrix(a,cx,cy));
- };
- /* scale this element by 'sx,sy' w.r.t. (cx,cy) or (0,0) */
- SVG.types.ATransformable.prototype.scale =
- function(sx,sy,cx,cy)
- {
- return this.__transform(SVG.fns.__getScalingMatrix(sx,sy,cx,cy));
- };
- /* apply the given transformation string to this element */
- SVG.types.ATransformable.prototype.transform =
- function(tstr)
- {
- return this.__transform(SVG.fns.__getTransformationMatrix(tstr));
- }
- /* apply the given transformation matrix to this element */
- SVG.types.ATransformable.prototype.__transform =
- function(T)
- {
- throw 'ATransformable subtype must overwrite __transform :: '+
- this.constructor.name;
- };
- /* translate this element by 'dx,dy' */
- SVG.types.ATransformable.prototype.translate =
- function(dx,dy)
- {
- return this.__transform(SVG.fns.__getTranslationMatrix(dx,dy));
- };
- /*---------------------------------- MATRIX ----------------------------------*/
- /* an SVG matrix:
- [ a c e
- b d f
- 0 0 1 ]
- (default init is Identity) */
- SVG.types.Matrix =
- function(a,b,c,d,e,f)
- {
- if( !(this instanceof SVG.types.Matrix) )
- return new SVG.types.Matrix(a,b,c,d,e,f);
- if( a == undefined )
- {
- this.a = 1;
- this.b = 0;
- this.c = 0;
- this.d = 1;
- this.e = 0;
- this.f = 0;
- }
- else
- {
- this.a = a;
- this.b = b;
- this.c = c;
- this.d = d;
- this.e = e;
- this.f = f;
- }
- };
- /* return the result of multiplying this matrix by another */
- SVG.types.Matrix.prototype.mult =
- function(M)
- {
- return new SVG.types.Matrix(
- this.a*M.a + this.c*M.b,
- this.b*M.a + this.d*M.b,
- this.a*M.c + this.c*M.d,
- this.b*M.c + this.d*M.d,
- this.a*M.e + this.c*M.f + this.e,
- this.b*M.e + this.d*M.f + this.f );
- };
- /*---------------------------------- POINT -----------------------------------*/
- /* a 2D point:
- {x:_,y:_}
-
- callable via
- SVG.types.Point(x,y)
- SVG.types.Point(pointStr)
- SVG.types.Point(point) */
- SVG.types.Point =
- function(__1,__2)
- {
- if( !(this instanceof SVG.types.Point) )
- return new SVG.types.Point(__1,__2);
- if( typeof(__1) == 'string' )
- {
- var _p = __1.split(',');
- this.x = parseFloat(_p[0]);
- this.y = parseFloat(_p[1]);
- }
- else if( __1 instanceof SVG.types.Point )
- {
- this.x = __1.x;
- this.y = __1.y;
- }
- else
- {
- this.x = __1;
- this.y = __2;
- }
- this._x = this.x;
- this._y = this.y;
- };
- /* return the distance between this point and another */
- SVG.types.Point.prototype.dist =
- function(p)
- {
- return Math.sqrt(Math.pow(this.x-p.x,2) + Math.pow(this.y-p.y,2));
- };
- /* return the slope of an imaginary line between 2 points as the counter-
- clockwise angle between line p1->p2 and the X-axis */
- SVG.types.Point.prototype.slope =
- function(p)
- {
- var dx = p.x - this.x,
- dy = p.y - this.y;
- return Math.atan2(dy,dx)*180/Math.PI;
- };
- /* apply the given transformation matrix to this point */
- SVG.types.Point.prototype.__transform =
- function(T)
- {
- if( T )
- {
- var x = T.a*this.x + T.c*this.y + T.e,
- y = T.b*this.x + T.d*this.y + T.f;
- this.x = x;
- this.y = y;
- }
- else
- {
- this.x = this._x;
- this.y = this._y;
- }
- return this;
- };
- _utils.extend(SVG.types.Point, SVG.types.ATransformable);
- /*---------------------------------- PATH ------------------------------------*/
- /* an SVG path made only of Ms and Ls
-
- callable via
- SVG.types.LinearPath(pathstr)
- SVG.types.Point(points) */
- SVG.types.LinearPath =
- function(__1)
- {
- if( !(this instanceof SVG.types.LinearPath) )
- return new SVG.types.LinearPath(__1);
- if( typeof(__1) == 'string' )
- this.points = __1.split(/[ML]/).slice(1).map(SVG.types.Point);
- else
- this.points = _utils.clone(__1);
- this.length = -1;
- this.dists = [];
- };
- /* compute total length while remembering each control point's distance
- from the start */
- SVG.types.LinearPath.prototype.__buildControlPointToLengthMap =
- function()
- {
- this.length = 0;
- this.dists = [0];
-
- for( var i = 1; i<this.points.length; i++ )
- {
- this.length += this.points[i-1].dist(this.points[i]);
- this.dists.push(this.length);
- }
- };
- /* return the coordinates and orientation of the point along the path at the
- specified ratio of the path's length
-
- 1. return first point if ratio is <= 0
- 2. return last point if ratio is >= 1
- 3. otherwise,
- a. traverse 'dists' until find control points between which answer lies
- b. interpolate between said points and return answer
- #. in every case, the points orientation is returned along with it...
- this is computed by finding the slope between
- case 1: first and second point
- case 2: next-to-last and last point
- case 3: identified bounding points */
- SVG.types.LinearPath.prototype.getPointOnPathAtRatio =
- function(ratio)
- {
- var p;
- if( ratio <= 0 )
- {
- p = new SVG.types.Point(_utils.head(this.points));
- p.O = p.slope(this.points[1]);
- }
- else if( ratio >= 1 )
- {
- p = new SVG.types.Point(_utils.tail(this.points));
- p.O = this.points[this.points.length-2].slope(p);
- }
- else
- {
- if( this.length == -1 )
- this.__buildControlPointToLengthMap();
-
- for( var i = 1; i<this.points.length; i++ )
- if( this.dists[i]/this.length >= ratio )
- {
- var a = this.dists[i-1]/this.length,
- b = this.dists[i]/this.length,
- t = (ratio-a)/(b-a),
- p1 = this.points[i-1],
- p2 = this.points[i];
- p = new SVG.types.Point(
- p1.x+t*(p2.x-p1.x),
- p1.y+t*(p2.y-p1.y));
- p.O = p1.slope(p2);
- break;
- }
- }
- return p;
- };
- /* add/remove control points */
- SVG.types.LinearPath.prototype.splice =
- function(i,n,toadd)
- {
- //TBC
- this.__buildControlPointToLengthMap();
- };
- /* apply the given transformation matrix to this path */
- SVG.types.LinearPath.prototype.__transform =
- function(T)
- {
- this.points.forEach(
- function(p)
- {
- p.__transform(T);
- });
- return this;
- };
- _utils.extend(SVG.types.LinearPath, SVG.types.ATransformable);
- /*----------------------------------------------------------------------------*/
- SVG.types.CubicPath = function() {} //TBC
- SVG.types.Circle = function() {} //TBC
- SVG.types.Ellipse = function() {} //TBC
- SVG.types.Rectangle = function() {} //TBC
- SVG.types.Polygon = function() {} //TBC
- SVG.types.Star = function() {} //TBC
- SVG.types.Image = function() {} //TBC
- /* NOTE: 'exports' exists in back-end 'require', but not in browser import...
- this ensures no errors are reported during browser imports */
- var exports = exports || {};
- exports.SVG = SVG;
|