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