Bug 12212 fixed: Export a polyline in 2D broke it into several segments
[scilab.git] / scilab / modules / scirenderer / src / org / scilab / forge / scirenderer / implementation / g2d / motor / PolyLine.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2013 - Scilab Enterprises - Calixte Denizet
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
10  */
11
12 package org.scilab.forge.scirenderer.implementation.g2d.motor;
13
14 import java.awt.Color;
15 import java.awt.Stroke;
16 import java.awt.geom.Line2D;
17 import java.awt.geom.Path2D;
18 import java.awt.Graphics2D;
19 import java.util.ArrayList;
20 import java.util.List;
21
22 import org.scilab.forge.scirenderer.tranformations.Vector3d;
23 import org.scilab.forge.scirenderer.tranformations.Vector4d;
24
25 /**
26  * Class to represent a polyline object. This is used only in 2D (in 3D, a line could begin
27  * on front of a face and could end behind it).
28  * This allows to draw a polyline like a polyline ! and not as a set of segments.
29  * Notice that a PolyLine is NOT a convex object... but in 2D it does not matter, algorithms
30  * which use the convexity are not applyed.
31  *
32  * @author Calixte DENIZET
33  */
34 public class PolyLine extends ConvexObject {
35
36     private boolean monochromatic;
37     private G2DStroke stroke;
38
39     /**
40      * Default constructor
41      * @param vertices the polyline vertices
42      * @param colors the vertices color
43      * @param stroke the stroke to used
44      */
45     public PolyLine(Vector3d[] vertices, Color[] colors, G2DStroke stroke) throws InvalidPolygonException {
46         super(vertices, colors);
47         if (vertices.length <= 1) {
48             throw new InvalidPolygonException("A polyline cannot have one or zero point");
49         }
50
51         this.monochromatic = isMonochromatic(colors);
52         this.stroke = stroke;
53     }
54
55     /**
56      * Get a set of polylines. The Nan of Inf vectors are removed and so the polyline is splitted.
57      * @param vertices the polyline vertices
58      * @param colors the vertices color
59      * @param stroke the stroke to used
60      * @param loop if true a looping polyline is created
61      * @return a set of polylines
62      */
63     public static List<PolyLine> getPolyLines(Vector3d[] vertices, Color[] colors, G2DStroke stroke, boolean loop) {
64         if (loop) {
65             Vector3d[] v = new Vector3d[vertices.length + 1];
66             Color[] c = new Color[vertices.length + 1];
67             for (int i = 0; i < vertices.length; i++) {
68                 v[i] = vertices[i];
69                 c[i] = colors[i];
70             }
71             v[vertices.length] = v[0];
72             c[vertices.length] = c[0];
73             vertices = v;
74             colors = c;
75         }
76
77         int pos = 0;
78         List<PolyLine> list = new ArrayList<PolyLine>(1);
79         while ((pos = trimLeft(vertices, pos)) != -1) {
80             final int second = findNanOrInf(vertices, pos + 1);
81             final int len = second - pos;
82             final Vector3d[] newVertices = new Vector3d[len];
83             final Color[] newColors = new Color[len];
84             for (int i = 0; i < len; i++) {
85                 newVertices[i] = vertices[pos + i];
86                 newColors[i] = colors[pos + i];
87             }
88             pos = second + 1;
89             try {
90                 list.add(new PolyLine(newVertices, newColors, stroke));
91             } catch (InvalidPolygonException e) { }
92         }
93
94         return list;
95     }
96
97     /**
98      * {@inheritDoc}
99      */
100     public List<ConvexObject> breakObject(Vector4d v) {
101         final double[] vv = v.getData();
102         final Vector3d np = new Vector3d(vv);
103         final List<ConvexObject> list = new ArrayList<ConvexObject>(1);
104
105         int pos = 0;
106         boolean prev = false;
107
108         for (int i = 0; i < vertices.length; i++) {
109             final boolean b = isBehind(vertices[i], np, vv[3]);
110             if (b && !prev) {
111                 pos = i;
112                 prev = true;
113             } else if (!b && prev) {
114                 prev = false;
115                 try {
116                     list.add(cut(pos, i, np, vv[3]));
117                 } catch (InvalidPolygonException e) { }
118             }
119         }
120
121         if (prev) {
122             try {
123                 list.add(cut(pos, vertices.length, np, vv[3]));
124             } catch (InvalidPolygonException e) { }
125         }
126
127         return list;
128     }
129
130     /**
131      * {@inheritDoc}
132      */
133     public List<ConvexObject> breakObject(ConvexObject o) {
134         return null;
135     }
136
137     /**
138      * {@inheritDoc}
139      */
140     protected boolean isDegenerate() {
141         return false;
142     }
143
144     /**
145      * {@inheritDoc}
146      */
147     protected boolean isNanOrInf() {
148         return false;
149     }
150
151     /**
152      * {@inheritDoc}
153      */
154     public Vector3d getNormal() {
155         // Never used
156         return null;
157     }
158
159     /**
160      * {@inheritDoc}
161      */
162     protected boolean isPlanar() {
163         // Never used
164         return true;
165     }
166
167     /**
168      * Cut a polyline on a clipping plane
169      * @param first the first vertex position
170      * @param second the second vertex position
171      * @param np the normal vector of the clipping plane
172      * @param C the constant part of the hyperplane
173      * @return a cutted PolyLine
174      */
175     private PolyLine cut(int first, int second, Vector3d np, double C) throws InvalidPolygonException {
176         final boolean cutAtBegin = first != 0;
177         final boolean cutAtEnd = second != vertices.length;
178
179         if (!cutAtBegin && !cutAtEnd) {
180             return this;
181         }
182
183         if (cutAtBegin && cutAtEnd) {
184             final int len = second - first + 2;
185
186             Vector3d[] newVertices = new Vector3d[len];
187             Color[] newColors = new Color[len];
188
189             for (int i = 1; i < len - 1; i++) {
190                 newVertices[i] = vertices[first + i - 1];
191                 newColors[i] = colors[first + i - 1];
192             }
193
194             double c = (C + vertices[first].scalar(np)) / vertices[first].minus(vertices[first - 1]).scalar(np);
195             newVertices[0] = Vector3d.getBarycenter(vertices[first - 1], vertices[first], c, 1 - c);
196             newColors[0] = getColorsBarycenter(colors[first - 1], colors[first], c, 1 - c);
197
198             c = (C + vertices[second].scalar(np)) / vertices[second].minus(vertices[second - 1]).scalar(np);
199             newVertices[len - 1] = Vector3d.getBarycenter(vertices[second - 1], vertices[second], c, 1 - c);
200             newColors[len - 1] = getColorsBarycenter(colors[second - 1], colors[second], c, 1 - c);
201
202             return new PolyLine(newVertices, newColors, this.stroke);
203         }
204
205         if (cutAtBegin) {
206             final double c = (C + vertices[first].scalar(np)) / vertices[first].minus(vertices[first - 1]).scalar(np);
207             final int len = second - first + 1;
208
209             Vector3d[] newVertices = new Vector3d[len];
210             Color[] newColors = new Color[len];
211
212             for (int i = 1; i < len; i++) {
213                 newVertices[i] = vertices[first + i - 1];
214                 newColors[i] = colors[first + i - 1];
215             }
216
217             newVertices[0] = Vector3d.getBarycenter(vertices[first - 1], vertices[first], c, 1 - c);
218             newColors[0] = getColorsBarycenter(colors[first - 1], colors[first], c, 1 - c);
219
220             return new PolyLine(newVertices, newColors, this.stroke);
221         } else {
222             final double c = (C + vertices[second].scalar(np)) / vertices[second].minus(vertices[second - 1]).scalar(np);
223             final int len = second - first + 1;
224
225             Vector3d[] newVertices = new Vector3d[len];
226             Color[] newColors = new Color[len];
227
228             for (int i = 0; i < len - 1; i++) {
229                 newVertices[i] = vertices[first + i];
230                 newColors[i] = colors[first + i];
231             }
232
233             newVertices[len - 1] = Vector3d.getBarycenter(vertices[second - 1], vertices[second], c, 1 - c);
234             newColors[len - 1] = getColorsBarycenter(colors[second - 1], colors[second], c, 1 - c);
235             return new PolyLine(newVertices, newColors, this.stroke);
236         }
237     }
238
239
240     @Override
241     protected Path2D getProjectedPolyLine() {
242         Path2D.Double path = new Path2D.Double();
243
244         path.moveTo(vertices[0].getX(), vertices[0].getY());
245         for (int i = 1; i < vertices.length; i++) {
246             path.lineTo(vertices[i].getX(), vertices[i].getY());
247         }
248
249         return path;
250     }
251
252     @Override
253     public void draw(Graphics2D g2d) {
254         Stroke oldStroke = g2d.getStroke();
255
256         if (monochromatic) {
257             g2d.setColor(colors[0]);
258             g2d.setStroke(stroke);
259             g2d.draw(getProjectedPolyLine());
260         } else {
261             // on peut surement faire mieux ici
262             // avec un LinearGradientPaint
263             Vector3d start = vertices[0];
264             Color color = colors[0];
265             double cumLen = 0;
266             float[] dashArray = stroke.getDashArray();
267             float lwidth = stroke.getLineWidth();
268             for (int i = 1; i < vertices.length; i++) {
269                 Stroke nstroke = new G2DStroke(lwidth, dashArray, (float) cumLen);
270                 g2d.setStroke(nstroke);
271                 g2d.setColor(color);
272                 g2d.draw(new Line2D.Double(start.getX(), start.getY(), vertices[i].getX(), vertices[i].getY()));
273                 cumLen += Math.hypot(start.getX() - vertices[i].getX(), start.getY() - vertices[i].getY());
274             }
275         }
276
277         g2d.setStroke(oldStroke);
278     }
279
280     /**
281      * Get first non Nan (or Inf) vector position
282      * @param v the array to trim
283      * @param start the starting position
284      * @return index of the first non Nan vector or -1 if not found
285      */
286     private static int trimLeft(Vector3d[] v, int start) {
287         for (int i = start; i < v.length; i++) {
288             if (!isNanOrInf(v[i])) {
289                 return i;
290             }
291         }
292
293         return -1;
294     }
295
296     /**
297      * Get first Nan (or Inf) vector position
298      * @param v the array to trim
299      * @param start the starting position
300      * @return index of the first Nan vector
301      */
302     private static int findNanOrInf(Vector3d[] v, int start) {
303         for (int i = start; i < v.length; i++) {
304             if (isNanOrInf(v[i])) {
305                 return i;
306             }
307         }
308
309         return v.length;
310     }
311 }