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