1 /*
  2  * ShapeTools.js
  3  *
  4  * Sweet Home 3D, Copyright (c) 2018 Emmanuel PUYBARET / eTeks <info@eteks.com>
  5  *
  6  * This program is free software; you can redistribute it and/or modify
  7  * it under the terms of the GNU General Public License as published by
  8  * the Free Software Foundation; either version 2 of the License, or
  9  * (at your option) any later version.
 10  *
 11  * This program is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  * GNU General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU General Public License
 17  * along with this program; if not, write to the Free Software
 18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 19  */
 20 
 21 // Requires scene3d.js
 22 
 23 
 24 /**
 25  * Gathers some useful tools for shapes.
 26  * @author Emmanuel Puybaret
 27  */
 28 var ShapeTools = {
 29     parsedShapes : {}
 30 };
 31 
 32 /**
 33  * Returns the line stroke matching the given line styles.
 34  * @param {number} thickness
 35  * @param {Polyline.CapStyle} capStyle
 36  * @param {Polyline.JoinStyle} joinStyle
 37  * @param {number []} dashPattern
 38  * @param {number} dashOffset
 39  * @return {Object}
 40  */
 41 ShapeTools.getStroke = function (thickness, capStyle, joinStyle, dashPattern, dashOffset) {
 42   var strokeCapStyle;
 43   switch (capStyle) {
 44     case Polyline.CapStyle.ROUND:
 45       strokeCapStyle = java.awt.BasicStroke.CAP_ROUND;
 46       break;
 47     case Polyline.CapStyle.SQUARE:
 48       strokeCapStyle = java.awt.BasicStroke.CAP_SQUARE;
 49       break;
 50     default:
 51       strokeCapStyle = java.awt.BasicStroke.CAP_BUTT;
 52       break;
 53   }
 54   
 55   var strokeJoinStyle;
 56   switch (joinStyle) {
 57     case Polyline.JoinStyle.ROUND:
 58     case Polyline.JoinStyle.CURVED:
 59       strokeJoinStyle = java.awt.BasicStroke.JOIN_ROUND;
 60       break;
 61     case Polyline.JoinStyle.BEVEL:
 62       strokeJoinStyle = java.awt.BasicStroke.JOIN_BEVEL;
 63       break;
 64     default:
 65       strokeJoinStyle = java.awt.BasicStroke.JOIN_MITER;
 66       break;
 67   }
 68   
 69   var dashPhase = 0;
 70   if (dashPattern != null) {
 71     if (!Array.isArray(dashPattern)) {
 72       dashPattern = undefined;
 73       dashPhase = undefined;
 74     } else {
 75       dashPattern = dashPattern.slice(0);
 76       for (var i = 0; i < dashPattern.length; i++) {
 77         dashPattern [i] *= thickness;
 78         dashPhase += dashPattern [i];
 79       }
 80       dashPhase *= dashOffset;
 81     }
 82   }
 83   return new java.awt.BasicStroke(thickness, strokeCapStyle, strokeJoinStyle, 10, dashPattern, dashPhase);
 84 }
 85 
 86 /**
 87  * Returns the shape of a polyline.
 88  * @param {Array} points
 89  * @param {boolean} curved
 90  * @param {boolean} closedPath
 91  * @return {Object}
 92  */
 93 ShapeTools.getPolylineShape = function (points, curved, closedPath) {
 94   if (curved) {
 95     var polylineShape = new java.awt.geom.GeneralPath();
 96     for (var i = 0, n = closedPath ? points.length : points.length - 1; i < n; i++) {
 97       var curve2D = new java.awt.geom.CubicCurve2D.Float();
 98       var previousPoint = points[i === 0 ? points.length - 1 : i - 1];
 99       var point = points[i];
100       var nextPoint = points[i === points.length - 1 ? 0 : i + 1];
101       var vectorToBisectorPoint = [nextPoint[0] - previousPoint[0], nextPoint[1] - previousPoint[1]];
102       var nextNextPoint = points[(i + 2) % points.length];
103       var vectorToBisectorNextPoint = [point[0] - nextNextPoint[0], point[1] - nextNextPoint[1]];
104       curve2D.setCurve(point[0], point[1], 
105           point[0] + (i !== 0 || closedPath ? vectorToBisectorPoint[0] / 3.625 : 0), 
106           point[1] + (i !== 0 || closedPath ? vectorToBisectorPoint[1] / 3.625 : 0), 
107           nextPoint[0] + (i !== points.length - 2 || closedPath ? vectorToBisectorNextPoint[0] / 3.625 : 0), 
108           nextPoint[1] + (i !== points.length - 2 || closedPath ? vectorToBisectorNextPoint[1] / 3.625 : 0), 
109           nextPoint[0], nextPoint[1]);
110       polylineShape.append(curve2D, true);
111     }
112     return polylineShape;
113   } else {
114       return ShapeTools.getShape(points, closedPath, null);
115   }
116 }
117 
118 /**
119  * Returns the shape matching the coordinates in <code>points</code> array 
120  * or the shape matching the given <a href="http://www.w3.org/TR/SVG/paths.html">SVG path shape</a> 
121  * if the first parameter is a string.
122  * @param {Array|string} points array or a SVG path 
123  * @param {boolean} [closedPath]
124  * @param {java.awt.geom.AffineTransform} [transform]
125  * @return {Object}
126  * @protected
127  * @ignore
128  */
129 ShapeTools.getShape = function(points, closedPath, transform) {
130   if (points instanceof Array) {
131     var path = new java.awt.geom.GeneralPath();
132     path.moveTo(Math.fround(points[0][0]), Math.fround(points[0][1]));
133     for (var i = 1; i < points.length; i++) {
134       path.lineTo(Math.fround(points[i][0]), Math.fround(points[i][1]));
135     }
136     if (closedPath) {
137       path.closePath();
138     }
139     if (transform != null) {
140       path.transform(transform);
141     }
142     return path;
143   } else {
144     var svgPathShape = points;
145     var shape2D = ShapeTools.parsedShapes [svgPathShape];
146     if (!shape2D) {
147       shape2D = new java.awt.geom.Rectangle2D.Float(0, 0, 1, 1);
148       try {
149         var pathProducer = new org.apache.batik.parser.AWTPathProducer();
150         var pathParser = new org.apache.batik.parser.PathParser();
151         pathParser.setPathHandler(pathProducer);
152         pathParser.parse(svgPathShape);
153         shape2D = pathProducer.getShape();
154       } catch (ex) {
155         // Keep default value if Batik is not available or if the path is incorrect
156       }
157       ShapeTools.parsedShapes[svgPathShape] = shape2D;
158     }
159     return shape2D;
160   }
161 }