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  * @class
 27  * @author Emmanuel Puybaret
 28  */
 29 var ShapeTools = {
 30     parsedShapes : {}
 31 };
 32 
 33 /**
 34  * Returns the line stroke matching the given line styles.
 35  * @param {number} thickness
 36  * @param {Polyline.CapStyle} capStyle
 37  * @param {Polyline.JoinStyle} joinStyle
 38  * @param {number[]} dashPattern
 39  * @param {number} dashOffset
 40  * @return {Object}
 41  */
 42 ShapeTools.getStroke = function (thickness, capStyle, joinStyle, dashPattern, dashOffset) {
 43   var strokeCapStyle;
 44   switch (capStyle) {
 45     case Polyline.CapStyle.ROUND:
 46       strokeCapStyle = java.awt.BasicStroke.CAP_ROUND;
 47       break;
 48     case Polyline.CapStyle.SQUARE:
 49       strokeCapStyle = java.awt.BasicStroke.CAP_SQUARE;
 50       break;
 51     default:
 52       strokeCapStyle = java.awt.BasicStroke.CAP_BUTT;
 53       break;
 54   }
 55   
 56   var strokeJoinStyle;
 57   switch (joinStyle) {
 58     case Polyline.JoinStyle.ROUND:
 59     case Polyline.JoinStyle.CURVED:
 60       strokeJoinStyle = java.awt.BasicStroke.JOIN_ROUND;
 61       break;
 62     case Polyline.JoinStyle.BEVEL:
 63       strokeJoinStyle = java.awt.BasicStroke.JOIN_BEVEL;
 64       break;
 65     default:
 66       strokeJoinStyle = java.awt.BasicStroke.JOIN_MITER;
 67       break;
 68   }
 69   
 70   var dashPhase = 0;
 71   if (dashPattern != null) {
 72     if (!Array.isArray(dashPattern)) {
 73       dashPattern = undefined;
 74       dashPhase = undefined;
 75     } else {
 76       dashPattern = dashPattern.slice(0);
 77       for (var i = 0; i < dashPattern.length; i++) {
 78         dashPattern [i] *= thickness;
 79         dashPhase += dashPattern [i];
 80       }
 81       dashPhase *= dashOffset;
 82     }
 83   }
 84   return new java.awt.BasicStroke(thickness, strokeCapStyle, strokeJoinStyle, 10, dashPattern, dashPhase);
 85 }
 86 
 87 /**
 88  * Returns the shape of a polyline.
 89  * @param {Array} points
 90  * @param {boolean} curved
 91  * @param {boolean} closedPath
 92  * @return {Object}
 93  */
 94 ShapeTools.getPolylineShape = function (points, curved, closedPath) {
 95   if (curved) {
 96     var polylineShape = new java.awt.geom.GeneralPath();
 97     for (var i = 0, n = closedPath ? points.length : points.length - 1; i < n; i++) {
 98       var curve2D = new java.awt.geom.CubicCurve2D.Float();
 99       var previousPoint = points[i === 0 ? points.length - 1 : i - 1];
100       var point = points[i];
101       var nextPoint = points[i === points.length - 1 ? 0 : i + 1];
102       var vectorToBisectorPoint = [nextPoint[0] - previousPoint[0], nextPoint[1] - previousPoint[1]];
103       var nextNextPoint = points[(i + 2) % points.length];
104       var vectorToBisectorNextPoint = [point[0] - nextNextPoint[0], point[1] - nextNextPoint[1]];
105       curve2D.setCurve(point[0], point[1], 
106           point[0] + (i !== 0 || closedPath ? vectorToBisectorPoint[0] / 3.625 : 0), 
107           point[1] + (i !== 0 || closedPath ? vectorToBisectorPoint[1] / 3.625 : 0), 
108           nextPoint[0] + (i !== points.length - 2 || closedPath ? vectorToBisectorNextPoint[0] / 3.625 : 0), 
109           nextPoint[1] + (i !== points.length - 2 || closedPath ? vectorToBisectorNextPoint[1] / 3.625 : 0), 
110           nextPoint[0], nextPoint[1]);
111       polylineShape.append(curve2D, true);
112     }
113     return polylineShape;
114   } else {
115       return ShapeTools.getShape(points, closedPath, null);
116   }
117 }
118 
119 /**
120  * Returns the shape matching the coordinates in <code>points</code> array 
121  * or the shape matching the given <a href="http://www.w3.org/TR/SVG/paths.html">SVG path shape</a> 
122  * if the first parameter is a string.
123  * @param {Array|string} points array or a SVG path 
124  * @param {boolean} [closedPath]
125  * @param {java.awt.geom.AffineTransform} [transform]
126  * @return {Object}
127  * @protected
128  * @ignore
129  */
130 ShapeTools.getShape = function(points, closedPath, transform) {
131   if (points instanceof Array) {
132     var path = new java.awt.geom.GeneralPath();
133     path.moveTo(Math.fround(points[0][0]), Math.fround(points[0][1]));
134     for (var i = 1; i < points.length; i++) {
135       path.lineTo(Math.fround(points[i][0]), Math.fround(points[i][1]));
136     }
137     if (closedPath) {
138       path.closePath();
139     }
140     if (transform != null) {
141       path.transform(transform);
142     }
143     return path;
144   } else {
145     var svgPathShape = points;
146     var shape2D = ShapeTools.parsedShapes [svgPathShape];
147     if (!shape2D) {
148       shape2D = new java.awt.geom.Rectangle2D.Float(0, 0, 1, 1);
149       try {
150         var pathProducer = new org.apache.batik.parser.AWTPathProducer();
151         var pathParser = new org.apache.batik.parser.PathParser();
152         pathParser.setPathHandler(pathProducer);
153         pathParser.parse(svgPathShape);
154         shape2D = pathProducer.getShape();
155       } catch (ex) {
156         // Keep default value if Batik is not available or if the path is incorrect
157       }
158       ShapeTools.parsedShapes[svgPathShape] = shape2D;
159     }
160     return shape2D;
161   }
162 }