Path: blob/main/projects/missiles/lib/Path.js
1835 views
1// Setup inheritance2Path.prototype = new Shape();3Path.prototype.constructor = Path;4Path.superclass = Shape.prototype;5678// Class constants910Path.COMMAND = 0;11Path.NUMBER = 1;12Path.EOD = 2;1314Path.PARAMS = {15A: [ "rx", "ry", "x-axis-rotation", "large-arc-flag", "sweep-flag", "x", "y" ],16a: [ "rx", "ry", "x-axis-rotation", "large-arc-flag", "sweep-flag", "x", "y" ],17C: [ "x1", "y1", "x2", "y2", "x", "y" ],18c: [ "x1", "y1", "x2", "y2", "x", "y" ],19H: [ "x" ],20h: [ "x" ],21L: [ "x", "y" ],22l: [ "x", "y" ],23M: [ "x", "y" ],24m: [ "x", "y" ],25Q: [ "x1", "y1", "x", "y" ],26q: [ "x1", "y1", "x", "y" ],27S: [ "x2", "y2", "x", "y" ],28s: [ "x2", "y2", "x", "y" ],29T: [ "x", "y" ],30t: [ "x", "y" ],31V: [ "y" ],32v: [ "y" ],33Z: [],34z: []35};363738/*****39*40* constructor41*42*****/43function Path(svgNode) {44if ( arguments.length > 0 ) {45this.init(svgNode);46}47}484950/*****51*52* init53*54*****/55Path.prototype.init = function(svgNode) {56if ( svgNode == null || svgNode.localName != "path" )57throw new Error("Path.init: Invalid localName: " + svgNode.localName);5859// Call superclass method60Path.superclass.init.call(this, svgNode);6162// Convert path data to segments63this.segments = null;64this.parseData( svgNode.getAttributeNS(null, "d") );65};666768/*****69*70* realize71*72*****/73Path.prototype.realize = function() {74for ( var i = 0; i < this.segments.length; i++ ) {75this.segments[i].realize();76}7778this.svgNode.addEventListener("mousedown", this, false);79};808182/*****83*84* unrealize85*86*****/87Path.prototype.unrealize = function() {88for ( var i = 0; i < this.segments.length; i++ ) {89this.segments[i].unrealize();90}9192this.svgNode.removeEventListener("mousedown", this, false);93};94959697/*****98*99* refresh100*101*****/102Path.prototype.refresh = function() {103var d = new Array();104105for ( var i = 0; i < this.segments.length; i++ ) {106d.push( this.segments[i].toString() );107}108109this.svgNode.setAttributeNS(null, "d", d.join(" "));110};111112113/*****114*115* registerHandles116*117*****/118Path.prototype.registerHandles = function() {119for ( var i = 0; i < this.segments.length; i++ ) {120this.segments[i].registerHandles();121}122};123124125/*****126*127* unregisterHandles128*129*****/130Path.prototype.unregisterHandles = function() {131for ( var i = 0; i < this.segments.length; i++ ) {132this.segments[i].unregisterHandles();133}134};135136137/*****138*139* selectHandles140*141*****/142Path.prototype.selectHandles = function(select) {143for ( var i = 0; i < this.segments.length; i++ ) {144this.segments[i].selectHandles(select);145}146};147148149/*****150*151* showHandles152*153*****/154Path.prototype.showHandles = function(state) {155for ( var i = 0; i < this.segments.length; i++ ) {156this.segments[i].showHandles(state);157}158};159160161/*****162*163* appendPathSegment164*165*****/166Path.prototype.appendPathSegment = function(segment) {167segment.previous = this.segments[this.segments.length-1];168169this.segments.push(segment);170};171172173/*****174*175* parseData176*177*****/178/* XXX BM. This function has been heavilly hacked to get elliptical arcs working XXX */179180Path.prototype.parseData = function(d) {181// convert path data to token array182var tokens = this.tokenize(d);183184// point to first token in array185var index = 0;186187// get the current token188var token = tokens[index];189190// set mode to signify new path191var mode = "BOD";192193// init segment array194// NOTE: should destroy previous segment handles here195this.segments = new Array();196197// Process all tokens198while ( !token.typeis(Path.EOD) ) {199var param_length;200var params = new Array();201202if ( mode == "BOD" ) {203// Start of new path. Must be a moveto command204if ( token.text == "M" || token.text == "m" ) {205// Advance past command token206index++;207208// Get count of numbers that must follow this command209param_length = Path.PARAMS[token.text].length;210211// Set new parsing mode212mode = token.text;213} else {214// Oops. New path didn't start with a moveto command215throw new Error("Path data must begin with a moveto command");216}217} else {218// Currently in a path definition219if ( token.typeis(Path.NUMBER) ) {220// Many commands allow you to keep repeating parameters221// without specifying the command again. This handles222// that case.223param_length = Path.PARAMS[mode].length;224} else {225// Advance past command token226index++;227228// Get count of numbers that must follow this command229param_length = Path.PARAMS[token.text].length;230231// Set new parsing mode232mode = token.text;233}234}235236// Make sure we have enough tokens left to satisfy the number237// of parameters we need for the last command238if ( (index + param_length) < tokens.length ) {239// Get each parameter240for (var i = index; i < index + param_length; i++) {241var number = tokens[i];242243// Make sure each parameter is a number.244if ( number.typeis(Path.NUMBER) )245params[params.length] = number.text;246else247throw new Error("Parameter type is not a number: " + mode + "," + number.text);248}249250// NOTE: Should create add an appendPathSegment (careful, that251// effects RelativePathSegments252var segment;253var length = this.segments.length;254var previous = ( length == 0 ) ? null : this.segments[length-1];255switch (mode) {256case "A":257var startPoint = previous.getLastPoint();258var x1 = startPoint.x;259var y1 = startPoint.y;260var bezier_param_list = arcToCurve.apply(this,[x1,y1].concat(params));261for (var i=0; i<bezier_param_list.length/6; i++) {262var bezier_params = new Array();263for (var j=0; j<6; j++) {264bezier_params.push(bezier_param_list.shift());265}266this.segments.push(new AbsoluteCurveto3(bezier_params, this, previous));267previous = this.segments[length-1];268}269break;270271case "C": this.segments.push(new AbsoluteCurveto3( params, this, previous )); break;272case "c": this.segments.push(new RelativeCurveto3( params, this, previous )); break;273case "H": this.segments.push(new AbsoluteHLineto( params, this, previous )); break;274case "L": this.segments.push(new AbsoluteLineto( params, this, previous )); break;275case "l": this.segments.push(new RelativeLineto( params, this, previous )); break;276case "M": this.segments.push(new AbsoluteMoveto( params, this, previous )); break;277case "m": this.segments.push(new RelativeMoveto( params, this, previous )); break;278case "Q": this.segments.push(new AbsoluteCurveto2( params, this, previous )); break;279case "q": this.segments.push(new RelativeCurveto2( params, this, previous )); break;280case "S": this.segments.push(new AbsoluteSmoothCurveto3( params, this, previous )); break;281case "s": this.segments.push(new RelativeSmoothCurveto3( params, this, previous )); break;282case "T": this.segments.push(new AbsoluteSmoothCurveto2( params, this, previous )); break;283case "t": this.segments.push(new RelativeSmoothCurveto2( params, this, previous )); break;284case "Z": this.segments.push(new RelativeClosePath( params, this, previous )); break;285case "z": this.segments.push(new RelativeClosePath( params, this, previous )); break;286default:287throw new Error("Unsupported segment type: " + mode);288};289290// advance to the next unused token291index += param_length;292293// get current token294token = tokens[index];295296// Lineto's follow moveto when no command follows moveto params297if ( mode == "M" ) mode = "L";298if ( mode == "m" ) mode = "l";299} else {300throw new Error("Path data ended before all parameters were found");301}302}303}304305306/*****307*308* tokenize309*310* Need to add support for scientific notation311*312*****/313Path.prototype.tokenize = function(d) {314var tokens = new Array();315316while ( d != "" ) {317if ( d.match(/^([ \t\r\n,]+)/) )318{319d = d.substr(RegExp.$1.length);320}321else if ( d.match(/^([aAcChHlLmMqQsStTvVzZ])/) )322{323tokens[tokens.length] = new Token(Path.COMMAND, RegExp.$1);324d = d.substr(RegExp.$1.length);325}326else if ( d.match(/^(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/) )327{328tokens[tokens.length] = new Token(Path.NUMBER, parseFloat(RegExp.$1));329d = d.substr(RegExp.$1.length);330}331else332{333throw new Error("Unrecognized segment command: " + d);334//d = "";335}336}337338tokens[tokens.length] = new Token(Path.EOD, null);339340return tokens;341}342343344/*****345*346* intersection methods347*348*****/349350/*****351*352* intersectShape353*354*****/355Path.prototype.intersectShape = function(shape) {356var result = new Intersection("No Intersection");357358for ( var i = 0; i < this.segments.length; i++ ) {359var inter = Intersection.intersectShapes(this.segments[i],shape);360361result.appendPoints(inter.points);362}363364if ( result.points.length > 0 ) result.status = "Intersection";365366return result;367};368369370/*****371*372* get/set methods373*374*****/375376/*****377*378* getIntersectionParams379*380*****/381Path.prototype.getIntersectionParams = function() {382return new IntersectionParams(383"Path",384[]385);386};387388389390391function arcToCurve (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {392// for more information of where this math came from visit:393// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes394var _120 = Math.PI * 120 / 180,395rad = Math.PI / 180 * (+angle || 0),396res = [],397xy,398rotate = function (x, y, rad) {399var X = x * Math.cos(rad) - y * Math.sin(rad),400Y = x * Math.sin(rad) + y * Math.cos(rad);401return {x: X, y: Y};402},403f1, f2,404cx, cy;405if (!recursive) {406xy = rotate(x1, y1, -rad);407x1 = xy.x;408y1 = xy.y;409xy = rotate(x2, y2, -rad);410x2 = xy.x;411y2 = xy.y;412var cos = Math.cos(Math.PI / 180 * angle),413sin = Math.sin(Math.PI / 180 * angle),414x = (x1 - x2) / 2,415y = (y1 - y2) / 2;416var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);417if (h > 1) {418h = Math.sqrt(h);419rx = h * rx;420ry = h * ry;421}422var rx2 = rx * rx,423ry2 = ry * ry,424k = (large_arc_flag == sweep_flag ? -1 : 1) *425Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));426427cx = k * rx * y / ry + (x1 + x2) / 2;428cy = k * -ry * x / rx + (y1 + y2) / 2;429430f1 = Math.asin(((y1 - cy) / ry).toFixed(9));431f2 = Math.asin(((y2 - cy) / ry).toFixed(9));432f1 = x1 < cx ? Math.PI - f1 : f1;433f2 = x2 < cx ? Math.PI - f2 : f2;434// f1 < 0 && (f1 = Math.PI * 2 + f1);435f1 = f1 < 0 ? Math.PI * 2 + f1 : f1;436f2 = f2 < 0 ? Math.PI * 2 + f2 : f2;437if (sweep_flag && f1 > f2) {438f1 = f1 - Math.PI * 2;439}440if (!sweep_flag && f2 > f1) {441f2 = f2 - Math.PI * 2;442}443} else {444f1 = recursive[0];445f2 = recursive[1];446cx = recursive[2];447cy = recursive[3];448}449var df = f2 - f1;450if (Math.abs(df) > _120) {451var f2old = f2,452x2old = x2,453y2old = y2;454f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);455x2 = cx + rx * Math.cos(f2);456y2 = cy + ry * Math.sin(f2);457res = arcToCurve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);458}459df = f2 - f1;460var c1 = Math.cos(f1),461s1 = Math.sin(f1),462c2 = Math.cos(f2),463s2 = Math.sin(f2),464t = Math.tan(df / 4),465hx = 4 / 3 * rx * t,466hy = 4 / 3 * ry * t,467m1 = [x1, y1],468m2 = [x1 + hx * s1, y1 - hy * c1],469m3 = [x2 + hx * s2, y2 - hy * c2],470m4 = [x2, y2];471m2[0] = 2 * m1[0] - m2[0];472m2[1] = 2 * m1[1] - m2[1];473if (recursive) {474return [m2, m3, m4].concat(res);475} else {476res = [m2, m3, m4].concat(res).join().split(",");477var newres = [];478for (var i = 0, ii = res.length; i < ii; i++) {479newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;480}481return newres;482}483}484485486