Bug 13022 fixed: Vectorial export did not clip large segments
[scilab.git] / scilab / modules / scirenderer / src / org / scilab / forge / scirenderer / implementation / g2d / motor / AbstractDrawable3DObject.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2012 - 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.Graphics2D;
16 import java.awt.Shape;
17 import java.awt.geom.Path2D;
18 import java.awt.geom.Rectangle2D;
19 import java.util.HashSet;
20 import java.util.Set;
21
22 import org.scilab.forge.scirenderer.tranformations.Vector3d;
23
24 /**
25  * @author Calixte DENIZET
26  */
27 public abstract class AbstractDrawable3DObject {
28
29     public static final double PRECISION = 1e-8;
30
31     private static int defaultPrecedence = 0;
32
33     protected final Vector3d[] vertices;
34
35     protected final Color[] colors;
36     protected int precedence;
37     protected Boolean is2d;
38     protected Double zindex;
39     protected Vector3d v0;
40     protected Vector3d v1;
41     protected Vector3d v0v1;
42     protected double nv0v1;
43     protected Vector3d normal;
44     protected BoundingBox bbox;
45     protected boolean marked;
46     protected boolean marked2;
47     protected Boolean degenerated;
48     protected double[] clip = new double[] {Double.NaN, Double.NaN, Double.NaN, Double.NaN};
49
50     /**
51      * Default constructor
52      * @param vertices the vertices
53      */
54     public AbstractDrawable3DObject(Vector3d[] vertices, Color[] colors) throws InvalidPolygonException {
55         if (vertices == null || vertices.length == 0) {
56             throw new InvalidPolygonException("Invalid 3D Object: no vertices was given");
57         }
58         this.vertices = vertices;
59         this.colors = colors;
60         if (isDegenerate()) {
61             throw new InvalidPolygonException("Invalid 3D Object: two vertices are the same");
62         }
63         if (isNanOrInf()) {
64             throw new InvalidPolygonException("Invalid 3D Object: contains NaN or Inf coordinates");
65         }
66         setPrecedence(defaultPrecedence++);
67         getNormal();
68     }
69
70     /**
71      * Create a clip rect for 2D view according to clipping planes passed as arguments
72      * @param v the clipping plane equation
73      */
74     void makeClip(double[] v) {
75         if (v[1] == 0) {
76             double x = -v[3] / v[0];
77             if (Double.isNaN(clip[0])) {
78                 clip[0] = x;
79             } else if (clip[0] > x) {
80                 clip[1] = clip[0];
81                 clip[0] = x;
82             } else {
83                 clip[1] = x;
84             }
85         } else {
86             double y = -v[3] / v[1];
87             if (Double.isNaN(clip[2])) {
88                 clip[2] = y;
89             } else if (clip[2] > y) {
90                 clip[3] = clip[2];
91                 clip[2] = y;
92             } else {
93                 clip[3] = y;
94             }
95         }
96     }
97
98     /**
99      * Get the clipping shape (for 2D view)
100      * @return the clipping shape
101      */
102     Shape getClip() {
103         if (!Double.isNaN(clip[0]) && !Double.isNaN(clip[1]) && !Double.isNaN(clip[2]) && !Double.isNaN(clip[3])) {
104             return new Rectangle2D.Double(clip[0], clip[2], clip[1] - clip[0], clip[3] - clip[2]);
105         }
106
107         return null;
108     }
109
110     /**
111      * Get the bounding box
112      * @return the bounding box
113      */
114     final BoundingBox getBBox() {
115         if (bbox == null) {
116             bbox = BoundingBox.getBoundingBox(this);
117         }
118
119         return bbox;
120     }
121
122     /**
123      * Test if an array of colors contains only one color or not
124      * @param colors the colors array
125      * @return true if the array is monochromatic
126      */
127     public static boolean isMonochromatic(Color[] colors) {
128         if (colors != null && colors.length > 0) {
129             Color c = colors[0];
130             for (int i = 1; i < colors.length; i++) {
131                 if (!c.equals(colors[i])) {
132                     return false;
133                 }
134             }
135         }
136
137         return true;
138     }
139
140     /**
141      * Draw this object on a Graphics2D object
142      * @param g2d the Graphics2d object where to draw
143      */
144     public abstract void draw(Graphics2D g2d);
145
146     /**
147      * Reset the default precedence
148      */
149     public static void resetDefaultPrecedence() {
150         defaultPrecedence = 0;
151     }
152
153     /**
154      * Set the precedence of this object.
155      * @param precedence the precedence of this object
156      */
157     public void setPrecedence(int precedence) {
158         this.precedence = precedence;
159     }
160
161     /**
162      * Get the precedence of this object, i.e. its position in the list of the draw objects.
163      * The first object has a precedence of 0, the second has a precedence of 1, ...
164      * @param the precedence
165      */
166     public int getPrecedence() {
167         return precedence;
168     }
169
170     /**
171      * Determinates if this object is 2D in looking at the z coordinates
172      * (when all the drawn objects are 2D, we can avoid the projection)
173      */
174     public boolean is2D() {
175         if (is2d == null) {
176             for (Vector3d v : vertices) {
177                 if (v.getZ() != 0) {
178                     is2d = Boolean.FALSE;
179                     return is2d;
180                 }
181             }
182             is2d = Boolean.TRUE;
183         }
184
185         return is2d;
186     }
187
188     /**
189      * Get the normal vector.
190      * If no normal vector has been set then it is calculated in using the cross product of the first two vectors.
191      * @return the normal vector.
192      */
193     public Vector3d getProvidedNormal() {
194         return normal;
195     }
196
197     /**
198      * Get the normal vector.
199      * If no normal vector has been set then it is calculated in using the cross product of the first two vectors.
200      * @return the normal vector.
201      */
202     public Vector3d getNormal() {
203         if (v0v1 == null) {
204             setNormal();
205         }
206
207         return v0v1;
208     }
209
210     /**
211      * Set the normal vector
212      */
213     protected void setNormal() {
214         v0 = vertices[1].minus(vertices[0]);
215         if (vertices.length >= 3) {
216             v1 = vertices[2].minus(vertices[0]);
217             v0v1 = Vector3d.product(v0, v1);
218             nv0v1 = v0v1.getNorm();
219             v0v1 = v0v1.times(1 / nv0v1);;
220         } else {
221             v0v1 = new Vector3d(0, 0, 0);
222         }
223     }
224
225     /**
226      * Determinates if the object is contained into a plane
227      * @return true if the object is planar
228      */
229     protected boolean isPlanar() {
230         Vector3d n = getNormal();
231         if (n.isZero() || vertices.length == 3) {
232             return true;
233         }
234
235         for (int i = 1; i < vertices.length; i++) {
236             if (!isNull(n.scalar(vertices[0].minus(vertices[i])))) {
237                 return false;
238             }
239         }
240
241         return true;
242     }
243
244     public int isBehind(Vector3d v, double a) {
245         double[] mM = minmax3D(this, v);
246
247         if (isPositiveOrNull(mM[0] + a)) {
248             return 1;
249         }
250         if (mM[1] + a < 0) {
251             return -1;
252         }
253
254         return 0;
255     }
256
257     public static boolean isBehind(Vector3d M, Vector3d v, double a) {
258         return isPositiveOrNull(M.scalar(v) + a);
259     }
260
261     /**
262      * Get the projected polyline of this object
263      * @return a path 2D
264      */
265     protected Path2D getProjectedPolyLine() {
266         Path2D.Double path = new Path2D.Double();
267         path.moveTo(vertices[0].getX(), vertices[0].getY());
268         for (int i = 1; i < vertices.length; i++) {
269             path.lineTo(vertices[i].getX(), vertices[i].getY());
270         }
271
272         return path;
273     }
274
275     /**
276      * Get the projected contour (i.e. a closed polyline) of this object
277      * @return a path 2D
278      */
279     protected Path2D getProjectedContour() {
280         Path2D path = getProjectedPolyLine();
281         path.closePath();
282
283         return path;
284     }
285
286     /**
287      * @param d a number
288      * @return true if d is near zero
289      */
290     protected final static boolean isNull(final double d) {
291         return Math.abs(d) <= PRECISION;
292     }
293
294     /**
295      * @param d a number
296      * @return true if d is greater than zero
297      */
298     protected final static boolean isPositiveOrNull(final double d) {
299         return d >= 0 || isNull(d);
300     }
301
302     /**
303      * @param d a number
304      * @return true if d is greater than zero
305      */
306     protected final static boolean isNegativeOrNull(final double d) {
307         return d <= 0 || isNull(d);
308     }
309
310     /**
311      * @param d1 a number
312      * @param d2 a number
313      * @return true if d1 is greater than d2
314      */
315     protected final static boolean isGreaterOrEqual(final double d1, final double d2) {
316         return isPositiveOrNull(d1 - d2);
317     }
318
319     /**
320      * @param d1 a number
321      * @param d2 a number
322      * @return true if d1 is lower than d2
323      */
324     protected final static boolean isLowerOrEqual(final double d1, final double d2) {
325         return isPositiveOrNull(d2 - d1);
326     }
327
328     /**
329      * @param d1 a number
330      * @param d2 a number
331      * @return true if d1 is equal to d2
332      */
333     protected final static boolean isEqual(final double d1, final double d2) {
334         return isNull(d1 - d2);
335     }
336
337     /**
338      * Get min-max of the projections of the vertices of o on v
339      * @param o an object
340      * @param v a vector
341      * @return an array of size 2 containing min-max.
342      */
343     protected static final double[] minmax3D(final AbstractDrawable3DObject o, final Vector3d v) {
344         double min = v.scalar(o.vertices[0]);
345         double max = min;
346
347         for (int i = 1; i < o.vertices.length; i++) {
348             double s = v.scalar(o.vertices[i]);
349             if (s < min) {
350                 min = s;
351             } else if (s > max) {
352                 max = s;
353             }
354         }
355
356         return new double[] {min, max};
357     }
358
359     /**
360      * Get min-max of the projections of the vertices of o on v
361      * @param o an object
362      * @param v a vector
363      * @return an array of size 2 containing min-max.
364      */
365     protected static final double[] minmax2D(final AbstractDrawable3DObject o, final double x, final double y) {
366         double min = x * o.vertices[0].getX() + y * o.vertices[0].getY();
367         double max = min;
368
369         for (int i = 1; i < o.vertices.length; i++) {
370             double s = x * o.vertices[i].getX() + y * o.vertices[i].getY();
371             if (s < min) {
372                 min = s;
373             } else if (s > max) {
374                 max = s;
375             }
376         }
377
378         return new double[] {min, max};
379     }
380
381     protected static final Color getColorsBarycenter(final Color c1, final Color c2, final double w1, final double w2) {
382         if (c1 != null && c2 != null && !c1.equals(c2)) {
383             float[] comp1 = c1.getComponents(null);
384             float[] comp2 = c2.getComponents(null);
385
386             return new Color((float) (comp1[0] * w1 + comp2[0] * w2),
387                              (float) (comp1[1] * w1 + comp2[1] * w2),
388                              (float) (comp1[2] * w1 + comp2[2] * w2),
389                              (float) (comp1[3] * w1 + comp2[3] * w2));
390         }
391
392         return c1;
393     }
394
395     /**
396      * @return true if there are two vertices which are indentical
397      */
398     protected boolean isDegenerate() {
399         if (degenerated == null) {
400             Set<Vector3d> set = new HashSet<Vector3d>();
401             for (Vector3d v : vertices) {
402                 set.add(v);
403             }
404
405             degenerated = set.size() != vertices.length;
406         }
407
408         return degenerated;
409     }
410
411     protected boolean isNanOrInf() {
412         for (Vector3d v : vertices) {
413             if (isNanOrInf(v)) {
414                 return true;
415             }
416         }
417
418         return false;
419     }
420
421     public static final boolean isNanOrInf(final Vector3d v) {
422         final double x = v.getX();
423         if (Double.isNaN(x) || Double.isInfinite(x)) {
424             return true;
425         }
426         final double y = v.getY();
427         if (Double.isNaN(y) || Double.isInfinite(y)) {
428             return true;
429         }
430         final double z = v.getZ();
431         if (Double.isNaN(z) || Double.isInfinite(z)) {
432             return true;
433         }
434         return false;
435     }
436 }