1 /*
2  * Polyline3D.java 11 sept. 2018
3  *
4  * Sweet Home 3D, Copyright (c) 2015 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 package com.eteks.sweethome3d.j3d;
21 
22 import java.awt.BasicStroke;
23 import java.awt.Shape;
24 import java.awt.Stroke;
25 import java.awt.geom.AffineTransform;
26 import java.awt.geom.Area;
27 import java.awt.geom.Ellipse2D;
28 import java.awt.geom.GeneralPath;
29 import java.awt.geom.PathIterator;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33 
34 import javax.media.j3d.Appearance;
35 import javax.media.j3d.BranchGroup;
36 import javax.media.j3d.GeometryArray;
37 import javax.media.j3d.Group;
38 import javax.media.j3d.PolygonAttributes;
39 import javax.media.j3d.Shape3D;
40 import javax.media.j3d.Transform3D;
41 import javax.media.j3d.TransformGroup;
42 import javax.vecmath.Point3f;
43 import javax.vecmath.Vector3d;
44 import javax.vecmath.Vector3f;
45 
46 import com.eteks.sweethome3d.model.Home;
47 import com.eteks.sweethome3d.model.Polyline;
48 import com.sun.j3d.utils.geometry.GeometryInfo;
49 
50 /**
51  * Root of a polyline branch.
52  * @author Emmanuel Puybaret
53  */
54 public class Polyline3D extends Object3DBranch {
55   private static final PolygonAttributes  DEFAULT_POLYGON_ATTRIBUTES =
56       new PolygonAttributes(PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_NONE, 0, false);
57   private static final GeneralPath ARROW;
58 
59   static {
60     ARROW = new GeneralPath();
61     ARROW.moveTo(-5, -2);
62     ARROW.lineTo(0, 0);
63     ARROW.lineTo(-5, 2);
64   }
65 
Polyline3D(Polyline polyline, Home home)66   public Polyline3D(Polyline polyline, Home home) {
67     setUserData(polyline);
68 
69     // Allow branch to be removed from its parent
70     setCapability(BranchGroup.ALLOW_DETACH);
71     setCapability(BranchGroup.ALLOW_CHILDREN_READ);
72     setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
73     setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
74 
75     update();
76   }
77 
78   @Override
update()79   public void update() {
80     Polyline polyline = (Polyline)getUserData();
81     if (polyline.isVisibleIn3D()
82         && (polyline.getLevel() == null
83             || polyline.getLevel().isViewableAndVisible())) {
84       Stroke stroke = ShapeTools.getStroke(polyline.getThickness(), polyline.getCapStyle(), polyline.getJoinStyle(),
85           polyline.getDashStyle() != Polyline.DashStyle.SOLID ? polyline.getDashPattern() : null, // null renders better closed shapes with a solid style,
86           polyline.getDashOffset());
87       Shape polylineShape = ShapeTools.getPolylineShape(polyline.getPoints(),
88           polyline.getJoinStyle() == Polyline.JoinStyle.CURVED, polyline.isClosedPath());
89 
90       // Search angle at start and at end
91       float [] firstPoint = null;
92       float [] secondPoint = null;
93       float [] beforeLastPoint = null;
94       float [] lastPoint = null;
95       for (PathIterator it = polylineShape.getPathIterator(null, 0.5); !it.isDone(); it.next()) {
96         float [] pathPoint = new float [2];
97         if (it.currentSegment(pathPoint) != PathIterator.SEG_CLOSE) {
98           if (firstPoint == null) {
99             firstPoint = pathPoint;
100           } else if (secondPoint == null) {
101             secondPoint = pathPoint;
102           }
103           beforeLastPoint = lastPoint;
104           lastPoint = pathPoint;
105         }
106       }
107       float angleAtStart = (float)Math.atan2(firstPoint [1] - secondPoint [1],
108           firstPoint [0] - secondPoint [0]);
109       float angleAtEnd = (float)Math.atan2(lastPoint [1] - beforeLastPoint [1],
110           lastPoint [0] - beforeLastPoint [0]);
111       float arrowDelta = polyline.getCapStyle() != Polyline.CapStyle.BUTT
112           ? polyline.getThickness() / 2
113           : 0;
114       Shape [] polylineShapes = {getArrowShape(firstPoint, angleAtStart, polyline.getStartArrowStyle(), polyline.getThickness(), arrowDelta),
115                                  getArrowShape(lastPoint, angleAtEnd, polyline.getEndArrowStyle(), polyline.getThickness(), arrowDelta),
116                                  stroke.createStrokedShape(polylineShape)};
117       Area polylineArea = new Area();
118       for (Shape shape : polylineShapes) {
119         if (shape != null) {
120           polylineArea.add(new Area(shape));
121         }
122       }
123       List<Point3f> vertices = new ArrayList<Point3f>(4);
124       List<float [][]> polylinePoints = getAreaPoints(polylineArea, 0.5f, false);
125       int [] stripCounts = new int [polylinePoints.size()];
126       int currentShapeStartIndex = 0;
127       for (int i = 0; i < polylinePoints.size(); i++) {
128         for (float [] point : polylinePoints.get(i)) {
129           vertices.add(new Point3f(point [0], 0, point [1]));
130         }
131         stripCounts [i] = vertices.size() - currentShapeStartIndex;
132         currentShapeStartIndex = vertices.size();
133       }
134 
135       GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
136       geometryInfo.setCoordinates(vertices.toArray(new Point3f [vertices.size()]));
137       Vector3f [] normals = new Vector3f [vertices.size()];
138       Arrays.fill(normals, new Vector3f(0, 1, 0));
139       geometryInfo.setNormals(normals);
140       geometryInfo.setStripCounts(stripCounts);
141       GeometryArray geometryArray = geometryInfo.getGeometryArray(true, true, false);
142 
143       if (numChildren() == 0) {
144         BranchGroup group = new BranchGroup();
145         group.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
146         group.setCapability(BranchGroup.ALLOW_DETACH);
147 
148         TransformGroup transformGroup = new TransformGroup();
149         // Allow the change of the transformation that sets polyline position and orientation
150         transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
151         transformGroup.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
152         group.addChild(transformGroup);
153 
154         Appearance appearance = new Appearance();
155         appearance.setMaterial(getMaterial(DEFAULT_COLOR, DEFAULT_AMBIENT_COLOR, 0));
156         appearance.setCapability(Appearance.ALLOW_MATERIAL_WRITE);
157         appearance.setPolygonAttributes(DEFAULT_POLYGON_ATTRIBUTES);
158 
159         Shape3D shape = new Shape3D(geometryArray, appearance);
160         shape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
161         shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
162         transformGroup.addChild(shape);
163 
164         addChild(group);
165       } else {
166         Shape3D shape = (Shape3D)((TransformGroup)(((Group)getChild(0)).getChild(0))).getChild(0);
167         shape.setGeometry(geometryArray);
168       }
169 
170       TransformGroup transformGroup = (TransformGroup)(((Group)getChild(0)).getChild(0));
171       // Apply elevation
172       Transform3D transform = new Transform3D();
173       transform.setTranslation(new Vector3d(0, polyline.getGroundElevation() + (polyline.getElevation() < 0.05f ? 0.05f : 0), 0));
174       transformGroup.setTransform(transform);
175       ((Shape3D)transformGroup.getChild(0)).getAppearance().setMaterial(getMaterial(polyline.getColor(), polyline.getColor(), 0));
176     } else {
177       removeAllChildren();
178     }
179   }
180 
181   /**
182    * Returns the shape of polyline arrow at the given point and orientation.
183    */
getArrowShape(float [] point, float angle, Polyline.ArrowStyle arrowStyle, float thickness, float arrowDelta)184   private Shape getArrowShape(float [] point, float angle,
185                               Polyline.ArrowStyle arrowStyle, float thickness, float arrowDelta) {
186     if (arrowStyle != null
187         && arrowStyle != Polyline.ArrowStyle.NONE) {
188       AffineTransform transform = AffineTransform.getTranslateInstance(point [0], point [1]);
189       transform.rotate(angle);
190       transform.translate(arrowDelta, 0);
191       double scale = Math.pow(thickness, 0.66f) * 2;
192       transform.scale(scale, scale);
193       GeneralPath arrowPath = new GeneralPath();
194       switch (arrowStyle) {
195         case DISC :
196           arrowPath.append(new Ellipse2D.Float(-3.5f, -2, 4, 4), false);
197           break;
198         case OPEN :
199           BasicStroke arrowStroke = new BasicStroke((float)(thickness / scale / 0.9), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
200           arrowPath.append(arrowStroke.createStrokedShape(ARROW).getPathIterator(AffineTransform.getScaleInstance(0.9, 0.9), 0), false);
201           break;
202         case DELTA :
203           GeneralPath deltaPath = new GeneralPath(ARROW);
204           deltaPath.closePath();
205           arrowPath.append(deltaPath.getPathIterator(AffineTransform.getTranslateInstance(1.65f, 0), 0), false);
206           break;
207         default:
208           return null;
209       }
210       return arrowPath.createTransformedShape(transform);
211     }
212     return null;
213   }
214 }
215