1 /*
  2  * Polyline3D.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 //          Object3DBranch.js
 23 //          geom.js
 24 //          stroke.js
 25 
 26 
 27 /**
 28  * Creates the 3D polyline matching the given home <code>polyline</code>.
 29  * @param {Polyline} polyline
 30  * @param {Home} home
 31  * @param {boolean} waitModelAndTextureLoadingEnd
 32  * @constructor
 33  * @extends Object3DBranch
 34  * @author Emmanuel Puybaret
 35  */
 36 function Polyline3D(polyline, home, waitModelAndTextureLoadingEnd) {
 37   Object3DBranch.call(this);
 38   this.setUserData(polyline);
 39   
 40   if (Polyline3D.ARROW === null) {
 41     Polyline3D.ARROW = new java.awt.geom.GeneralPath();
 42     Polyline3D.ARROW.moveTo(-5, -2);
 43     Polyline3D.ARROW.lineTo(0, 0);
 44     Polyline3D.ARROW.lineTo(-5, 2);
 45   }
 46   
 47   this.setCapability(Group3D.ALLOW_CHILDREN_EXTEND);
 48   
 49   this.update();
 50 }
 51 Polyline3D.prototype = Object.create(Object3DBranch.prototype);
 52 Polyline3D.prototype.constructor = Polyline3D;
 53 
 54 Polyline3D.ARROW = null;
 55 
 56 Polyline3D.prototype.update = function() {
 57   var polyline = this.getUserData();
 58   if (polyline.isVisibleIn3D() 
 59       && (polyline.getLevel() == null 
 60           || polyline.getLevel().isViewableAndVisible())) {
 61     var stroke = ShapeTools.getStroke(polyline.getThickness(), polyline.getCapStyle(), 
 62         polyline.getJoinStyle(), polyline.getDashPattern(), polyline.getDashOffset());
 63     var polylineShape = ShapeTools.getPolylineShape(polyline.getPoints(), 
 64         polyline.getJoinStyle() === Polyline.JoinStyle.CURVED, polyline.isClosedPath());
 65     
 66     var firstPoint = null;
 67     var secondPoint = null;
 68     var beforeLastPoint = null;
 69     var lastPoint = null;
 70     for (var it = polylineShape.getPathIterator(null, 0.5); !it.isDone(); it.next()) {
 71       var pathPoint = [0, 0];
 72       if (it.currentSegment(pathPoint) !== java.awt.geom.PathIterator.SEG_CLOSE) {
 73         if (firstPoint === null) {
 74           firstPoint = pathPoint;
 75         } else if (secondPoint === null) {
 76           secondPoint = pathPoint;
 77         }
 78         beforeLastPoint = lastPoint;
 79         lastPoint = pathPoint;
 80       }
 81     }
 82 
 83     var angleAtStart = Math.atan2(firstPoint[1] - secondPoint[1], 
 84         firstPoint[0] - secondPoint[0]);
 85     var angleAtEnd = Math.atan2(lastPoint[1] - beforeLastPoint[1], 
 86         lastPoint[0] - beforeLastPoint[0]);
 87     var arrowDelta = polyline.getCapStyle() !== Polyline.CapStyle.BUTT 
 88         ? polyline.getThickness() / 2 
 89         : 0;
 90     var polylineShapes = [this.getArrowShape(firstPoint, angleAtStart, polyline.getStartArrowStyle(), polyline.getThickness(), arrowDelta), 
 91                           this.getArrowShape(lastPoint, angleAtEnd, polyline.getEndArrowStyle(), polyline.getThickness(), arrowDelta), 
 92                           stroke.createStrokedShape(polylineShape)];
 93     var polylineArea = new java.awt.geom.Area();
 94     for (var i = 0; i < polylineShapes.length; i++) {
 95       var shape = polylineShapes[i];
 96       if (shape != null) {
 97           polylineArea.add(new java.awt.geom.Area(shape));
 98       }
 99     }
100     var vertices = [];
101     var polylinePoints = this.getAreaPoints(polylineArea, 0.5, false);
102     var stripCounts = new Array(polylinePoints.length);
103     var currentShapeStartIndex = 0;
104     for (var i = 0; i < polylinePoints.length; i++) {
105       var points = polylinePoints[i];
106       for (var j = 0; j < points.length; j++) {
107         var point = points[j];
108         vertices.push(vec3.fromValues(point[0], 0, point[1]));
109       }
110       stripCounts[i] = vertices.length - currentShapeStartIndex;
111       currentShapeStartIndex = vertices.length;
112     }
113     
114     var geometryInfo = new GeometryInfo3D(GeometryInfo3D.POLYGON_ARRAY);
115     geometryInfo.setCoordinates(vertices.slice(0));
116     var normals = new Array(vertices.length);
117     var normal = vec3.fromValues(0, 1, 0);
118     for (var i = 0; i < vertices.length; i++) {
119       normals [i] = normal;
120     }
121     geometryInfo.setNormals(normals);
122     geometryInfo.setStripCounts(stripCounts);
123     var geometryArray = geometryInfo.getIndexedGeometryArray();
124     if (this.getChildren().length === 0) {
125       var group = new BranchGroup3D();
126       var transformGroup = new TransformGroup3D();
127       transformGroup.setCapability(TransformGroup3D.ALLOW_TRANSFORM_WRITE);
128       group.addChild(transformGroup);
129       
130       var appearance = new Appearance3D();
131       this.updateAppearanceMaterial(appearance, Object3DBranch.DEFAULT_COLOR, Object3DBranch.DEFAULT_AMBIENT_COLOR, 0);
132       appearance.setCullFace(Appearance3D.CULL_NONE);
133       var shape = new Shape3D(geometryArray, appearance);
134       shape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
135       transformGroup.addChild(shape);
136       this.addChild(group);
137     } else {
138       var shape = this.getChild(0).getChild(0).getChild(0);
139       shape.removeGeometry(0);
140       shape.addGeometry(geometryArray);
141     }
142     
143     var transformGroup = this.getChild(0).getChild(0);
144     var transform = mat4.create();
145     mat4.fromTranslation(transform, vec3.fromValues(0, polyline.getGroundElevation() + (polyline.getElevation() < 0.05 ? 0.05 : 0), 0));
146     transformGroup.setTransform(transform);
147     this.updateAppearanceMaterial(transformGroup.getChild(0).getAppearance(), polyline.getColor(), polyline.getColor(), 0);
148   } else {
149     this.removeAllChildren();
150   }
151 }
152 
153 /**
154  * Returns the shape of polyline arrow at the given point and orientation.
155  * @param {Array} point
156  * @param {number} angle
157  * @param {Polyline.ArrowStyle} arrowStyle
158  * @param {number} thickness
159  * @param {number} arrowDelta
160  * @return {Object}
161  * @private
162  */
163 Polyline3D.prototype.getArrowShape = function (point, angle, arrowStyle, thickness, arrowDelta) {
164   if (arrowStyle != null 
165       && arrowStyle !== Polyline.ArrowStyle.NONE) {
166     var transform = java.awt.geom.AffineTransform.getTranslateInstance(point[0], point[1]);
167     transform.rotate(angle);
168     transform.translate(arrowDelta, 0);
169     var scale = Math.pow(thickness, 0.66) * 2;
170     transform.scale(scale, scale);
171     var arrowPath = new java.awt.geom.GeneralPath();
172     switch (arrowStyle) {
173       case Polyline.ArrowStyle.DISC:
174         arrowPath.append(new java.awt.geom.Ellipse2D.Float(-3.5, -2, 4, 4), false);
175         break;
176       case Polyline.ArrowStyle.OPEN:
177         var arrowStroke = new java.awt.BasicStroke((thickness / scale / 0.9), java.awt.BasicStroke.CAP_BUTT, java.awt.BasicStroke.JOIN_MITER);
178         arrowPath.append(arrowStroke.createStrokedShape(Polyline3D.ARROW).getPathIterator(java.awt.geom.AffineTransform.getScaleInstance(0.9, 0.9), 0), false);
179         break;
180       case Polyline.ArrowStyle.DELTA:
181         var deltaPath = new java.awt.geom.GeneralPath(Polyline3D.ARROW);
182         deltaPath.closePath();
183         arrowPath.append(deltaPath.getPathIterator(java.awt.geom.AffineTransform.getTranslateInstance(1.65, 0), 0), false);
184         break;
185       default:
186         return null;
187     }
188     return arrowPath.createTransformedShape(transform);
189   }
190   return null;
191 }
192