Merge remote-tracking branch 'origin/5.4'
[scilab.git] / scilab / modules / renderer / src / java / org / scilab / modules / renderer / JoGLView / axes / AxesDrawer.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009-2010 - DIGITEO - Pierre Lando
4  * Copyright (C) 2013 - Scilab Enterprises - Calixte DENIZET
5  *
6  * This file must be used under the terms of the CeCILL.
7  * This source file is licensed as described in the file COPYING, which
8  * you should have received as part of this distribution.  The terms
9  * are also available at
10  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
11  */
12
13 package org.scilab.modules.renderer.JoGLView.axes;
14
15 import org.scilab.forge.scirenderer.Canvas;
16 import org.scilab.forge.scirenderer.DrawingTools;
17 import org.scilab.forge.scirenderer.SciRendererException;
18 import org.scilab.forge.scirenderer.clipping.ClippingPlane;
19 import org.scilab.forge.scirenderer.shapes.appearance.Appearance;
20 import org.scilab.forge.scirenderer.shapes.geometry.Geometry.FaceCullingMode;
21 import org.scilab.forge.scirenderer.tranformations.DegenerateMatrixException;
22 import org.scilab.forge.scirenderer.tranformations.Transformation;
23 import org.scilab.forge.scirenderer.tranformations.TransformationFactory;
24 import org.scilab.forge.scirenderer.tranformations.TransformationStack;
25 import org.scilab.forge.scirenderer.tranformations.Vector3d;
26 import org.scilab.forge.scirenderer.tranformations.Vector4d;
27 import org.scilab.modules.graphic_objects.axes.Axes;
28 import org.scilab.modules.graphic_objects.axes.Box;
29 import org.scilab.modules.graphic_objects.contouredObject.Line;
30 import org.scilab.modules.graphic_objects.graphicController.GraphicController;
31 import org.scilab.modules.graphic_objects.graphicObject.GraphicObject;
32 import org.scilab.modules.graphic_objects.graphicObject.ClippableProperty;
33 import org.scilab.modules.graphic_objects.graphicObject.ClippableProperty.ClipStateType;
34 import org.scilab.modules.graphic_objects.figure.ColorMap;
35 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
36 import org.scilab.modules.renderer.JoGLView.axes.ruler.AxesRulerDrawer;
37 import org.scilab.modules.renderer.JoGLView.label.AxisLabelPositioner;
38 import org.scilab.modules.renderer.JoGLView.label.LabelManager;
39 import org.scilab.modules.renderer.JoGLView.label.LabelPositioner;
40 import org.scilab.modules.renderer.JoGLView.label.TitlePositioner;
41 import org.scilab.modules.renderer.JoGLView.label.YAxisLabelPositioner;
42 import org.scilab.modules.renderer.JoGLView.util.ColorFactory;
43
44 import java.awt.Dimension;
45 import java.awt.geom.Rectangle2D;
46 import java.util.HashMap;
47 import java.util.Map;
48
49 /**
50  *
51  * AxesDrawer are used by {@see DrawerVisitor} to draw {@see Axes}.
52  *
53  * @author Pierre Lando
54  */
55 public class AxesDrawer {
56     private static final double DEFAULT_THETA = 270.0;
57     private static final Line.LineType HIDDEN_BORDER_PATTERN = Line.LineType.DASH;
58
59     /** An epsilon value used to move the clipping planes in order to prevent strict clipping. */
60     private static final double CLIPPING_EPSILON = 1e-5;
61
62     private final DrawerVisitor visitor;
63     private final Geometries geometries;
64
65     private final AxesRulerDrawer rulerDrawer;
66
67     /** The front face culling mode. */
68     private FaceCullingMode frontFaceCullingMode;
69
70     /** The back face culling mode. */
71     private FaceCullingMode backFaceCullingMode;
72
73     /** The label manager. */
74     private final LabelManager labelManager;
75
76     /** The x-axis label positioner. */
77     private final Map<String, AxisLabelPositioner> xAxisLabelPositioner = new HashMap<String, AxisLabelPositioner>();
78
79     /** The y-axis label positioner. */
80     private final Map<String, AxisLabelPositioner> yAxisLabelPositioner = new HashMap<String, AxisLabelPositioner>();
81
82     /** The z-axis label positioner. */
83     private final Map<String, AxisLabelPositioner> zAxisLabelPositioner = new HashMap<String, AxisLabelPositioner>();
84
85     /** The title positioner. */
86     private final Map<String, TitlePositioner> titlePositioner = new HashMap<String, TitlePositioner>();
87
88     /**
89      * The current reversed bounds. Used by the functions converting
90      * between object and box coordinates.
91      */
92     private double[] reversedBounds;
93
94     /** The current reversed bounds intervals. */
95     private double[] reversedBoundsIntervals;
96
97     /** The current projection (from object to window coordinates) used when drawing objects. */
98     private Transformation currentProjection;
99
100     /**  The current data scale and translate transformation. */
101     private Transformation currentDataTransformation;
102
103     /** The set of object to window coordinate projections associated to all the Axes drawn by this drawer. */
104     private final Map<String, Transformation> projectionMap = new HashMap<String, Transformation>();
105
106     /** The set of object (in 2d view mode) to window coordinate projections associated to all the Axes drawn by this drawer. */
107     private final Map<String, Transformation> projection2dViewMap = new HashMap<String, Transformation>();
108
109     /** This is a __MAP__ */
110     private final Map<String, Transformation> sceneProjectionMap = new HashMap<String, Transformation>();
111
112     /**
113      * Default constructor.
114      * @param visitor the parent {@see DrawerVisitor}.
115      */
116     public AxesDrawer(DrawerVisitor visitor) {
117         this.visitor = visitor;
118         this.labelManager = visitor.getLabelManager();
119         this.geometries = new Geometries(visitor.getCanvas());
120         this.rulerDrawer = new AxesRulerDrawer(visitor.getCanvas());
121
122         reversedBounds = new double[6];
123         reversedBoundsIntervals = new double[3];
124     }
125
126     public Transformation getCurrentProjection(Axes axes) throws DegenerateMatrixException {
127         DrawingTools drawingTools = visitor.getDrawingTools();
128         Integer[] size = visitor.getFigure().getAxesSize();
129         Transformation zoneProjection = computeZoneProjection(axes);
130         Transformation transformation = computeBoxTransformation(axes, new Dimension(size[0], size[1]), false);
131         Transformation dataTransformation = computeDataTransformation(axes);
132         Transformation windowTrans = drawingTools.getTransformationManager().getWindowTransformation().getInverseTransformation();
133         Transformation current = zoneProjection.rightTimes(transformation);
134         current = current.rightTimes(dataTransformation);
135
136         return windowTrans.rightTimes(current);
137     }
138
139     /**
140      * Compute the graduations on the axes
141      * @param axes the axes
142      */
143     public void computeRulers(Axes axes) {
144         DrawingTools drawingTools = visitor.getDrawingTools();
145         ColorMap colorMap = visitor.getColorMap();
146         try {
147             Integer[] size = visitor.getFigure().getAxesSize();
148             double w = ((double) (int) size[0]) / 2;
149             double h = ((double) (int) size[1]) / 2;
150             Transformation windowTrans = TransformationFactory.getAffineTransformation(new Vector3d(w, h, 1), new Vector3d(w, h, 0));
151             Transformation zoneProjection = computeZoneProjection(axes);
152             Transformation transformation = computeBoxTransformation(axes, new Dimension(size[0], size[1]), false);
153             Transformation canvasTrans = windowTrans.rightTimes(zoneProjection).rightTimes(transformation);
154
155             rulerDrawer.computeRulers(axes, this, colorMap, drawingTools, transformation, canvasTrans);
156         } catch (DegenerateMatrixException e) {
157
158         }
159     }
160
161     /**
162      * Draw the given {@see Axes}.
163      * @param axes {@see Axes} to draw.
164      * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
165      */
166     public void draw(Axes axes) throws SciRendererException {
167         DrawingTools drawingTools = visitor.getDrawingTools();
168         Integer[] size = visitor.getFigure().getAxesSize();
169         Dimension canvasDimension = new Dimension(size[0], size[1]);
170         ColorMap colorMap = visitor.getColorMap();
171         TransformationStack modelViewStack = drawingTools.getTransformationManager().getModelViewStack();
172         TransformationStack projectionStack = drawingTools.getTransformationManager().getProjectionStack();
173
174         // Set axes zone.
175         Transformation zoneProjection = computeZoneProjection(axes);
176         projectionStack.push(zoneProjection);
177
178         // Set box projection.
179         Transformation transformation = computeBoxTransformation(axes, canvasDimension, false);
180         modelViewStack.pushRightMultiply(transformation);
181
182         /* Compute the data scale and translate transformation. */
183         Transformation dataTransformation = computeDataTransformation(axes);
184
185         currentDataTransformation = dataTransformation;
186
187         /* Compute the object to window coordinates projection. */
188         currentProjection = zoneProjection.rightTimes(transformation);
189         currentProjection = currentProjection.rightTimes(dataTransformation);
190
191         sceneProjectionMap.put(axes.getIdentifier(), currentProjection);
192
193         Transformation windowTrans = drawingTools.getTransformationManager().getInverseWindowTransformation();
194         currentProjection = windowTrans.rightTimes(currentProjection);
195
196         /* Update the projection maps with the resulting projections. */
197         addProjection(axes.getIdentifier(), currentProjection);
198
199         /* 2d view projection, to do: optimize computation */
200         if (axes.getRotationAngles()[0] != 0 || axes.getRotationAngles()[1] != DEFAULT_THETA) {
201             Transformation transformation2dView = computeBoxTransformation(axes, canvasDimension, true);
202             currentProjection = zoneProjection.rightTimes(transformation2dView);
203             currentProjection = currentProjection.rightTimes(dataTransformation);
204             currentProjection = windowTrans.rightTimes(currentProjection);
205         }
206
207         addProjection2dView(axes.getIdentifier(), currentProjection);
208
209         /**
210          * Draw the axes background.
211          */
212         drawBackground(axes, drawingTools, colorMap);
213
214         /**
215          * Mirror the transformation such that the corner with the maximum Z value was (-1, -1, -1).
216          * And draw the box.
217          */
218         Transformation cubeOrientation = computeCubeMirroring(transformation);
219         modelViewStack.pushRightMultiply(cubeOrientation);
220         drawBox(axes, drawingTools, colorMap);
221         modelViewStack.pop();
222
223         // Ruler are drawn in box coordinate.
224         rulerDrawer.drawRuler(axes, this, colorMap, drawingTools);
225
226         /* Compute reversed bounds. */
227         computeReversedBounds(axes);
228
229         /**
230          * Scale and translate for data fitting.
231          * And draw data.
232          */
233         modelViewStack.pushRightMultiply(dataTransformation);
234
235         /* Compute the front and back culling modes */
236         computeFaceCullingModes(axes);
237
238         visitor.askAcceptVisitor(axes.getChildren());
239         modelViewStack.pop();
240
241         // Reset transformation stacks.
242         modelViewStack.pop();
243         projectionStack.pop();
244     }
245
246     /**
247      * Draw the axes background.
248      * @param axes the {@see Axes}
249      * @param drawingTools the {@see DrawingTools} to use.
250      * @param colorMap the current {@see ColorMap}
251      * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
252      */
253     private void drawBackground(Axes axes, DrawingTools drawingTools, ColorMap colorMap) throws SciRendererException {
254         if (axes.getFilled()) {
255             Appearance appearance = new Appearance();
256             appearance.setFillColor(ColorFactory.createColor(colorMap, axes.getBackground()));
257             drawingTools.draw(geometries.getCubeGeometry(), appearance);
258             drawingTools.clearDepthBuffer();
259         }
260     }
261
262     /**
263      * Draw the box of the given {@see Axes}
264      * @param axes the given {@see Axes}.
265      * @param drawingTools the {@see DrawingTools} to use.
266      * @param colorMap the current {@see ColorMap}.
267      * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
268      */
269     private void drawBox(Axes axes, DrawingTools drawingTools, ColorMap colorMap) throws SciRendererException {
270         Box.BoxType boxed = axes.getBox().getBox();
271         if (boxed != Box.BoxType.OFF) {
272             Appearance appearance = new Appearance();
273
274             /**
275              * Draw hidden part of box.
276              */
277             if (!visitor.is2DView()) {
278                 appearance.setLineColor(ColorFactory.createColor(colorMap, axes.getHiddenAxisColor()));
279                 appearance.setLineWidth(axes.getLineThickness().floatValue());
280                 appearance.setLinePattern(HIDDEN_BORDER_PATTERN.asPattern());
281                 drawingTools.draw(geometries.getHiddenBoxBorderGeometry(), appearance);
282             }
283
284             if (boxed != Box.BoxType.HIDDEN_AXES) {
285
286                 /**
287                  * Draw box border.
288                  */
289                 appearance.setLineColor(ColorFactory.createColor(colorMap, axes.getLineColor()));
290                 appearance.setLineWidth(axes.getLineThickness().floatValue());
291                 appearance.setLinePattern(axes.getLine().getLineStyle().asPattern());
292                 drawingTools.draw(geometries.getBoxBorderGeometry(), appearance);
293
294
295                 if (boxed != Box.BoxType.BACK_HALF) {
296                     /**
297                      * Draw front part of box.
298                      */
299                     drawingTools.draw(geometries.getFrontBoxBorderGeometry(), appearance);
300                 }
301             }
302         }
303     }
304
305     /**
306      * Compute the mirroring matrix needed to have the [-1; -1; -1] point projected with the maximal Z value.
307      * @param transformation the current transformation.
308      * @return the mirroring matrix needed to have the [-1; -1; -1] point projected with the maximal Z value.
309      */
310     private Transformation computeCubeMirroring(Transformation transformation) {
311         double[] matrix = transformation.getMatrix();
312         try {
313             return TransformationFactory.getScaleTransformation(
314                        matrix[2] < 0 ? 1 : -1,
315                        matrix[6] < 0 ? 1 : -1,
316                        matrix[10] < 0 ? 1 : -1
317                    );
318         } catch (DegenerateMatrixException e) {
319             // Should never happen.
320             return TransformationFactory.getIdentity();
321         }
322     }
323
324     /**
325      * Compute zone where the axes is draw. In normalised window coordinate.
326      * @param axes the given {@see axes}.
327      * @return the zone where the axes is draw.
328      */
329     private Rectangle2D computeZone(Axes axes) {
330         Double[] axesBounds = axes.getAxesBounds();
331         Double[] margins = axes.getMargins();
332
333         // TODO :  zoom box.
334         double x = (axesBounds[0] + axesBounds[2] * margins[0]) * 2 - 1;
335         double y = (1.0 - axesBounds[1] - axesBounds[3] * (1.0 - margins[3])) * 2 - 1;
336         double w = (1 - margins[0] - margins[1]) * axesBounds[2];
337         double h = (1 - margins[2] - margins[3]) * axesBounds[3];
338
339         if (axes.getIsoview()) {
340             double minSize = Math.min(w, h);
341             y += (h - minSize);
342             h = minSize;
343             x += (w - minSize);
344             w = minSize;
345         }
346
347         return new Rectangle2D.Double(x, y, w, h);
348     }
349
350     /**
351      * Compute the projection for the given axes.
352      * @param axes the given axes.
353      * @return the projection matrix.
354      * @throws DegenerateMatrixException if axes represent a nul area.
355      */
356     private Transformation computeZoneProjection(Axes axes) throws DegenerateMatrixException {
357         Rectangle2D zone = computeZone(axes);
358         Transformation zoneTranslation = TransformationFactory.getTranslateTransformation(zone.getMaxX(), zone.getMaxY(), 0);
359
360         // We scale by 0.5 in Z to allow ruler to be drawn.
361         Transformation zoneScale = TransformationFactory.getScaleTransformation(zone.getWidth(), zone.getHeight(), .5);
362
363         return zoneTranslation.rightTimes(zoneScale);
364     }
365
366     /**
367      * Compute data transformation for the given {@see Axes}.
368      *
369      * The data transformation is applied to data to fit in axes box.
370      *
371      * @param axes the given {@see Axes}
372      * @return data transformation.
373      * @throws DegenerateMatrixException if data bounds are not corrects.
374      */
375     private Transformation computeDataTransformation(Axes axes) throws DegenerateMatrixException {
376         Double[] bounds = axes.getDisplayedBounds();
377
378         // Reverse data if needed.
379         Transformation transformation = TransformationFactory.getScaleTransformation(
380                                             axes.getAxes()[0].getReverse() ? 1 : -1,
381                                             axes.getAxes()[1].getReverse() ? 1 : -1,
382                                             axes.getAxes()[2].getReverse() ? 1 : -1
383                                         );
384
385         // Scale data.
386         Transformation scaleTransformation = TransformationFactory.getScaleTransformation(
387                 2.0 / (bounds[1] - bounds[0]),
388                 2.0 / (bounds[3] - bounds[2]),
389                 2.0 / (bounds[5] - bounds[4])
390                                              );
391         transformation = transformation.rightTimes(scaleTransformation);
392
393
394         // Translate data.
395         Transformation translateTransformation = TransformationFactory.getTranslateTransformation(
396                     -(bounds[0] + bounds[1]) / 2.0,
397                     -(bounds[2] + bounds[3]) / 2.0,
398                     -(bounds[4] + bounds[5]) / 2.0
399                 );
400         transformation = transformation.rightTimes(translateTransformation);
401         return transformation;
402     }
403
404
405     /**
406      * Compute box transformation for the given axes.
407      *
408      * The box transformation is applied to the axes box to fit in the canvas.
409      *
410      * @param axes the given {@see Axes}.
411      * @param canvasDimension the current canvas {@see Canvas}.
412      * @param use2dView specifies whether the default 2d view rotation angles must be used (true) or the given Axes' ones (false).
413      * @return box transformation for the given axes.
414      * @throws DegenerateMatrixException if data bounds are incorrect or canvas with or length are zero.
415      */
416     private Transformation computeBoxTransformation(Axes axes, Dimension canvasDimension, boolean use2dView) throws DegenerateMatrixException {
417         Double[] bounds = axes.getDisplayedBounds();
418
419         double theta;
420
421         double tmpX;
422         double tmpY;
423         double tmpZ;
424
425         Transformation transformation;
426
427         // Set zone aspect ratio.
428         Rectangle2D zone = computeZone(axes);
429         double axesRatio = zone.getWidth() / zone.getHeight();
430         transformation = TransformationFactory.getPreferredAspectRatioTransformation(canvasDimension, axesRatio);
431
432         // Rotate.
433         if (use2dView) {
434             theta = 2 * DEFAULT_THETA;
435         } else {
436             double alpha = -axes.getRotationAngles()[0];
437             theta = DEFAULT_THETA + axes.getRotationAngles()[1];
438             if (alpha != 0) {
439                 Transformation alphaRotation = TransformationFactory.getRotationTransformation(alpha, 1.0, 0.0, 0.0);
440                 transformation = transformation.rightTimes(alphaRotation);
441             }
442         }
443
444         Transformation thetaRotation = TransformationFactory.getRotationTransformation(theta, 0.0, 0.0, 1.0);
445         transformation = transformation.rightTimes(thetaRotation);
446
447         // If there is no cube scaling, we must take into account the distribution of data.
448         if (!axes.getCubeScaling()) {
449             tmpX = (bounds[1] - bounds[0]);
450             tmpY = (bounds[3] - bounds[2]);
451             tmpZ = (bounds[5] - bounds[4]);
452
453             /**
454              * Here, we should divide the values by their maximum.
455              * But the next operation will automatically.
456              */
457             if (tmpX != 1 || tmpY != 1 || tmpZ != 1) {
458                 Transformation cubeScale = TransformationFactory.getScaleTransformation(tmpX, tmpY, tmpZ);
459                 transformation = transformation.rightTimes(cubeScale);
460             }
461         }
462
463         // Compute bounds of projected data.
464         double[] matrix = transformation.getMatrix();
465         tmpX = 1 / (Math.abs(matrix[0]) + Math.abs(matrix[4]) + Math.abs(matrix[8]));
466         tmpY = 1 / (Math.abs(matrix[1]) + Math.abs(matrix[5]) + Math.abs(matrix[9]));
467         tmpZ = 1 / (Math.abs(matrix[2]) + Math.abs(matrix[6]) + Math.abs(matrix[10]));
468
469         // Scale projected data to fit in the cube.
470         Transformation isoScale;
471         if (axes.getIsoview()) {
472             double w = zone.getWidth();
473             double h = zone.getHeight();
474             double minScale = Math.min(tmpX * w, tmpY * h);
475             isoScale = TransformationFactory.getScaleTransformation(minScale / w, minScale / h, tmpZ);
476         } else {
477             isoScale = TransformationFactory.getScaleTransformation(tmpX, tmpY, tmpZ);
478         }
479         transformation = transformation.leftTimes(isoScale);
480
481         return transformation;
482     }
483
484
485     /**
486      * Computes and sets the reversed bounds of the given Axes.
487      * @param axes the given {@see Axes}.
488      */
489     private void computeReversedBounds(Axes axes) {
490         Double[] currentBounds = axes.getDisplayedBounds();
491
492         /* Reverse */
493         if (axes.getAxes()[0].getReverse()) {
494             reversedBounds[0] = currentBounds[1];
495             reversedBounds[1] = currentBounds[0];
496         } else {
497             reversedBounds[0] = currentBounds[0];
498             reversedBounds[1] = currentBounds[1];
499         }
500
501         if (axes.getAxes()[1].getReverse()) {
502             reversedBounds[2] = currentBounds[3];
503             reversedBounds[3] = currentBounds[2];
504         } else {
505             reversedBounds[2] = currentBounds[2];
506             reversedBounds[3] = currentBounds[3];
507         }
508
509         if (axes.getAxes()[2].getReverse()) {
510             reversedBounds[4] = currentBounds[5];
511             reversedBounds[5] = currentBounds[4];
512         } else {
513             reversedBounds[4] = currentBounds[4];
514             reversedBounds[5] = currentBounds[5];
515         }
516
517         /*
518          * Interval values are set to 1 when bounds are equal to avoid divides by 0
519          * in the object to box coordinates conversion function.
520          */
521         if (reversedBounds[1] == reversedBounds[0]) {
522             reversedBoundsIntervals[0] = 1.0;
523         } else {
524             reversedBoundsIntervals[0] = reversedBounds[1] - reversedBounds[0];
525         }
526
527         if (reversedBounds[3] == reversedBounds[2]) {
528             reversedBoundsIntervals[1] = 1.0;
529         } else {
530             reversedBoundsIntervals[1] = reversedBounds[3] - reversedBounds[2];
531         }
532
533         if (reversedBounds[5] == reversedBounds[4]) {
534             reversedBoundsIntervals[2] = 1.0;
535         } else {
536             reversedBoundsIntervals[2] = reversedBounds[5] - reversedBounds[4];
537         }
538
539     }
540
541     /**
542      * Computes the culling modes respectively corresponding to front and back faces
543      * of the given Axes' child objects as a function of its {X,Y,Z} reverse properties.
544      * It must be called by draw prior to rendering any child object.
545      * @param axes the given {@see Axes}.
546      */
547     private void computeFaceCullingModes(Axes axes) {
548         if (axes.getAxes()[0].getReverse() ^ axes.getAxes()[1].getReverse() ^ axes.getAxes()[2].getReverse()) {
549             /* Front: CW */
550             this.frontFaceCullingMode = FaceCullingMode.CW;
551             this.backFaceCullingMode = FaceCullingMode.CCW;
552         } else {
553             /* Front: CCW */
554             this.frontFaceCullingMode = FaceCullingMode.CCW;
555             this.backFaceCullingMode = FaceCullingMode.CW;
556         }
557     }
558
559     /**
560      * Converts a point from object coordinates to box coordinates (used when drawing axis rulers).
561      * @param point the point in object coordinates.
562      * @return the point in box coordinates.
563      */
564     public Vector3d getBoxCoordinates(Vector3d point) {
565         double[] dataCoordinates = new double[3];
566
567         dataCoordinates[0] = 1 - 2.0 * (point.getX() - reversedBounds[0]) / reversedBoundsIntervals[0];
568         dataCoordinates[1] = 1 - 2.0 * (point.getY() - reversedBounds[2]) / reversedBoundsIntervals[1];
569         dataCoordinates[2] = 1 - 2.0 * (point.getZ() - reversedBounds[4]) / reversedBoundsIntervals[2];
570
571         return new Vector3d(dataCoordinates);
572     }
573
574     /**
575      * Converts a point from box coordinates (used when drawing axis rulers) to object coordinates.
576      * @param point the point in box coordinates.
577      * @return the point in object coordinates.
578      */
579     public Vector3d getObjectCoordinates(Vector3d point) {
580         double[] objectCoordinates = new double[3];
581
582         objectCoordinates[0] = 0.5 * (1.0 - point.getX()) * (reversedBounds[1] - reversedBounds[0]) + reversedBounds[0];
583         objectCoordinates[1] = 0.5 * (1.0 - point.getY()) * (reversedBounds[3] - reversedBounds[2]) + reversedBounds[2];
584         objectCoordinates[2] = 0.5 * (1.0 - point.getZ()) * (reversedBounds[5] - reversedBounds[4]) + reversedBounds[4];
585
586         return new Vector3d(objectCoordinates);
587     }
588
589     /**
590      * Computes and return the object to window coordinate projection corresponding to the given Axes object.
591      * @param axes the given Axes.
592      * @param drawingTools the drawing tools.
593      * @param canvasDimension the current canvas dimension.
594      * @param use2dView specifies whether the default 2d view rotation angles must be used (true) or the given Axes' ones (false).
595      * @return the projection
596      */
597     private Transformation computeProjection(Axes axes, DrawingTools drawingTools, Dimension canvasDimension, boolean use2dView) {
598         Transformation projection;
599
600         try {
601             /* Compute the zone projection. */
602             Transformation zoneProjection = computeZoneProjection(axes);
603
604             /* Compute the box transformation. */
605             Transformation transformation = computeBoxTransformation(axes, canvasDimension, use2dView);
606
607             /* Compute the data scale and translate transformation. */
608             Transformation dataTransformation = computeDataTransformation(axes);
609
610             /* Compute the object to window coordinates projection. */
611             projection = zoneProjection.rightTimes(transformation);
612             projection = projection.rightTimes(dataTransformation);
613
614             Transformation windowTrans = drawingTools.getTransformationManager().getWindowTransformation().getInverseTransformation();
615             projection = windowTrans.rightTimes(projection);
616         } catch (DegenerateMatrixException e) {
617             return TransformationFactory.getIdentity();
618         }
619
620         return projection;
621     }
622
623     /**
624      * Returns the current projection from object to window coordinates.
625      * @return the projection.
626      */
627     public Transformation getProjection() {
628         return currentProjection;
629     }
630
631     /**
632      * Returns the current data scale and translate transformation.
633      * @return the data transformation.
634      */
635     public Transformation getDataTransformation() {
636         return currentDataTransformation;
637     }
638
639     /**
640      * Adds the projection from object to window coordinates corresponding to a given Axes object
641      * to the projection map.
642      * @param axesId the identifier of the given Axes.
643      * @param projection the corresponding projection.
644      */
645     public synchronized void addProjection(String axesId, Transformation projection) {
646         projectionMap.put(axesId, projection);
647     }
648
649     /**
650      * Returns the projection from object to window coordinates corresponding
651      * to a given Axes object.
652      * @param id the identifier of the given Axes.
653      * @return the projection.
654      */
655     public Transformation getProjection(String id) {
656         return projectionMap.get(id);
657     }
658
659     /**
660      * Removes the object to window coordinate projection corresponding to a given Axes from
661      * the projection map.
662      * @param axesId the identifier of the given Axes.
663      */
664     public void removeProjection(String axesId) {
665         projectionMap.remove(axesId);
666     }
667
668     /**
669      * Adds the projection from object (in 2d view mode) to window coordinates corresponding to a given Axes object
670      * to the projection map.
671      * @param axesId the identifier of the given Axes.
672      * @param projection the corresponding projection.
673      */
674     public synchronized void addProjection2dView(String axesId, Transformation projection) {
675         projection2dViewMap.put(axesId, projection);
676     }
677
678     /**
679      * Returns the projection from object (in 2d view mode) to window coordinates corresponding
680      * to a given Axes object.
681      * @param id the identifier of the given Axes.
682      * @return the projection.
683      */
684     public Transformation getProjection2dView(String id) {
685         return projection2dViewMap.get(id);
686     }
687
688     public Transformation getSceneProjection(String id) {
689         return sceneProjectionMap.get(id);
690     }
691
692     /**
693      * Removes the object (in 2d view mode) to window coordinate projection corresponding to a given Axes from
694      * the projection map.
695      * @param axesId the identifier of the given Axes.
696      */
697     public void removeProjection2dView(String axesId) {
698         projection2dViewMap.remove(axesId);
699     }
700
701     /**
702      * Updates both the projection from object to window coordinates and the related
703      * object (in 2d view mode) to window coordinates projection for the given Axes object.
704      * @param axes the given Axes.
705      */
706     public static void updateAxesTransformation(Axes axes) {
707         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
708         AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
709         Integer[] size = currentVisitor.getFigure().getAxesSize();
710         Dimension canvasDimension = new Dimension(size[0], size[1]);
711
712         Transformation transformation = axesDrawer.getProjection(axes.getIdentifier());
713
714         /* The projection must be updated */
715         if (transformation == null) {
716             Transformation projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, false);
717
718             axesDrawer.addProjection(axes.getIdentifier(), projection);
719         }
720
721         Transformation transformation2dView = axesDrawer.getProjection2dView(axes.getIdentifier());
722
723         /* The projection must be updated */
724         if (transformation2dView == null) {
725             Transformation projection2dView = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, true);
726
727             axesDrawer.addProjection2dView(axes.getIdentifier(), projection2dView);
728         }
729     }
730
731     /**
732      * Computes and returns the coordinates of a point projected onto the default 2d view plane.
733      * To compute them, the point is projected using the object to window coordinate projection, then
734      * unprojected using the object to window coordinate projection corresponding to the default 2d view
735      * (which uses the default camera rotation angles).
736      * To do: optimize by using the already computed 3d view projection.
737      * @param axes the given Axes.
738      * @param coordinates the object (x,y,z) coordinates to project onto the 2d view plane (3-element array).
739      * @returns the 2d view coordinates (3-element array).
740      */
741     public static double[] compute2dViewCoordinates(Axes axes, double[] coordinates) {
742         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
743         AxesDrawer axesDrawer;
744         Transformation projection;
745         Transformation projection2d;
746
747         Vector3d point = new Vector3d(coordinates);
748
749         if (currentVisitor != null) {
750             Integer[] size = currentVisitor.getFigure().getAxesSize();
751             Dimension canvasDimension = new Dimension(size[0], size[1]);
752
753             axesDrawer = currentVisitor.getAxesDrawer();
754
755             projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, false);
756             projection2d = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, true);
757
758             point = projection.project(point);
759             point = projection2d.unproject(point);
760         }
761
762         return new double[] {point.getX(), point.getY(), point.getZ()};
763     }
764
765     /**
766      * Computes and returns the pixel coordinates from a point's coordinates expressed in the default
767      * 2d view coordinate frame, using the given Axes. The returned pixel coordinates are expressed
768      * in the AWT's 2d coordinate frame.
769      * @param axes the given Axes.
770      * @param coordinates the 2d view coordinates (3-element array: x, y, z).
771      * @returns the pixel coordinates (2-element array: x, y).
772      */
773     public static double[] computePixelFrom2dViewCoordinates(Axes axes, double[] coordinates) {
774         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
775         AxesDrawer axesDrawer;
776         double[] coords2dView = new double[] {0.0, 0.0, 0.0};
777
778         if (currentVisitor != null) {
779             Integer[] size = currentVisitor.getFigure().getAxesSize();
780             double height = (double) size[1];
781
782             axesDrawer = currentVisitor.getAxesDrawer();
783
784             Transformation projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
785
786             Vector3d point = new Vector3d(coordinates);
787             point = projection2d.project(point);
788
789             /* Convert the window coordinates to pixel coordinates, only y changes due to the differing y-axis convention */
790             coords2dView[0] = point.getX();
791             coords2dView[1] = height - point.getY();
792         }
793
794         return coords2dView;
795     }
796
797     /**
798      * Computes and returns the coordinates of a point projected onto the default 2d view plane
799      * from its pixel coordinates, using the given Axes. Pixel coordinates are expressed in
800      * the AWT's 2d coordinate frame.
801      * The returned point's z component is set to 0, as we only have x and y as an input.
802      * @param axes the given Axes.
803      * @param coordinates the pixel coordinates (2-element array: x, y).
804      * @return coordinates the 2d view coordinates (3-element array: x, y, z).
805      */
806     public static double[] compute2dViewFromPixelCoordinates(Axes axes, double[] coordinates) {
807         DrawerVisitor currentVisitor;
808         AxesDrawer axesDrawer;
809
810         double[] coords2dView = new double[] {0.0, 0.0, 0.0};
811
812         currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
813
814         if (currentVisitor != null) {
815             Integer[] size = currentVisitor.getFigure().getAxesSize();
816             double height = (double) size[1];
817
818             axesDrawer = currentVisitor.getAxesDrawer();
819
820             /* Convert the pixel coordinates to window coordinates, only y changes due to the differing y-axis convention */
821             Vector3d point = new Vector3d(coordinates[0], height - coordinates[1], 0.0);
822
823             Transformation projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
824             point = projection2d.unproject(point);
825             coords2dView = point.getData();
826         }
827
828         return coords2dView;
829     }
830
831     /**
832      * Un-project the given point from AWT coordinate to given axes coordinate.
833      * @param axes returned coordinate are relative to this axes.
834      * @param point un-projected point.
835      * @return The un-projected point.
836      */
837     public static Vector3d unProject(Axes axes, Vector3d point) {
838         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
839
840         if (currentVisitor != null) {
841             AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
842             double height = currentVisitor.getCanvas().getHeight() - 1;
843
844             Transformation projection2d = axesDrawer.getProjection(axes.getIdentifier());
845             return projection2d.unproject(new Vector3d(point.getX(), height - point.getY(), point.getZ()));
846         } else {
847             return new Vector3d(0, 0, 0);
848         }
849     }
850
851     /**
852      * Computes and returns the viewing area corresponding to the given Axes object.
853      * The viewing area is described by the (x, y) coordinates of the Axes box's upper-left corner
854      * and the Axes box's dimensions (width and height), all values are in pixel.
855      * The 2d coordinate frame in which the area is expressed uses the AWT convention:
856      * upper-left window corner at (0, 0), y-axis pointing downwards).
857      * @param axes the given Axes.
858      * @return the Axes' viewing area (4-element array: x, y, width, height).
859      */
860     public static double[] getViewingArea(Axes axes) {
861         DrawerVisitor currentVisitor;
862
863         double[] viewingArea = new double[] {0.0, 0.0, 0.0, 0.0};
864
865         currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
866
867         if (currentVisitor != null) {
868             Integer[] size = currentVisitor.getFigure().getAxesSize();
869             double width = (double) size[0];
870             double height = (double) size[1];
871             double upperLeftY;
872             AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
873             Rectangle2D axesZone = axesDrawer.computeZone(axes);
874
875             /* Compute the upper-left point's y coordinate */
876             upperLeftY = axesZone.getY() + axesZone.getHeight() * 2.0;
877
878             /* Convert from normalized coordinates to 2D pixel coordinates */
879             viewingArea[0] = (axesZone.getX() + 1.0) * 0.5 * width;
880             viewingArea[1] = (1.0 - upperLeftY) * 0.5 * height;
881             viewingArea[2] = axesZone.getWidth() * width;
882             viewingArea[3] = axesZone.getHeight() * height;
883         }
884
885         return viewingArea;
886     }
887
888     /**
889      * Returns the culling mode corresponding to front faces.
890      * @return the front face culling mode.
891      */
892     public FaceCullingMode getFrontFaceCullingMode() {
893         return this.frontFaceCullingMode;
894     }
895
896     /**
897      * Returns the culling mode corresponding to back faces.
898      * @return the back face culling mode.
899      */
900     public FaceCullingMode getBackFaceCullingMode() {
901         return this.backFaceCullingMode;
902     }
903
904     /**
905      * Enables clipping for the given {@link ClippableProperty}, which describes
906      * the clipping state of a clippable graphic object.
907      * Depending on the object's clip state property, clipping can be either
908      * disabled (OFF), performed against the parent Axes' box planes (CLIPGRF),
909      * or performed against the planes defined by the object's clip box.
910      * To do: find a better way to compute the clipping planes' offsets as the current one
911      * may lead to problems when the interval between the min and max bounds is too small.
912      * @param parentAxes the clipped object's parent Axes.
913      * @param clipProperty the clipping property of a clippable object.
914      */
915     public void enableClipping(Axes parentAxes, ClippableProperty clipProperty) {
916         DrawingTools drawingTools = visitor.getDrawingTools();
917
918         if (clipProperty.getClipState() != ClipStateType.OFF) {
919             int numPlanes = 0;
920             Vector4d[] equations = null;
921
922             /* Stores the (xmin,xmax) ,(ymin,ymax) and (zmin,zmax) clipping bounds */
923             double[] clipBounds = new double[6];
924
925             /* The offsets used for the x, y and z planes in order to avoid strict clipping */
926             double[] offsets = new double[3];
927
928             if (clipProperty.getClipState() == ClipStateType.CLIPGRF) {
929                 /*
930                  * All the clipping planes are set as clipping is performed
931                  * against the Axes box planes.
932                  */
933                 numPlanes = 6;
934                 Double[] bounds = parentAxes.getDisplayedBounds();
935
936                 for (int i = 0; i < numPlanes; i++) {
937                     clipBounds[i] = bounds[i];
938                 }
939
940                 offsets[0] = CLIPPING_EPSILON * (bounds[1] - bounds[0]);
941                 offsets[1] = CLIPPING_EPSILON * (bounds[3] - bounds[2]);
942                 offsets[2] = CLIPPING_EPSILON * (bounds[5] - bounds[4]);
943             } else if (clipProperty.getClipState() == ClipStateType.ON) {
944                 /*
945                  * The clip box property defines values only for the x and y axes,
946                  * we therefore set only the x and y clipping planes.
947                  */
948                 numPlanes = 4;
949                 Double[] clipBox = clipProperty.getClipBox();
950
951                 /* The clip box stores the upper-left point coordinates. */
952                 clipBounds[0] = clipBox[0];
953                 clipBounds[1] = clipBox[0] + clipBox[2];
954                 clipBounds[2] = clipBox[1] - clipBox[3];
955                 clipBounds[3] = clipBox[1];
956
957                 Double[] bounds = parentAxes.getMaximalDisplayedBounds();
958
959                 /*
960                  * The logarithmic scale must be applied to clip bounds values.
961                  * If any of them are invalid, we set them to the displayed
962                  * left bounds (xmin or ymin).
963                  */
964                 if (parentAxes.getXAxisLogFlag()) {
965                     if (clipBounds[0] <= 0.0) {
966                         clipBounds[0] = bounds[0];
967                     } else {
968                         clipBounds[0] = Math.log10(clipBounds[0]);
969                     }
970
971                     if (clipBounds[1] <= 0.0) {
972                         clipBounds[1] = bounds[0];
973                     } else {
974                         clipBounds[1] = Math.log10(clipBounds[1]);
975                     }
976                 }
977
978                 if (parentAxes.getYAxisLogFlag()) {
979                     if (clipBounds[2] <= 0.0) {
980                         clipBounds[2] = bounds[2];
981                     } else {
982                         clipBounds[2] = Math.log10(clipBounds[2]);
983                     }
984
985                     if (clipBounds[3] <= 0.0) {
986                         clipBounds[3] = bounds[2];
987                     } else {
988                         clipBounds[3] = Math.log10(clipBounds[3]);
989                     }
990                 }
991
992                 offsets[0] = CLIPPING_EPSILON * (clipBounds[1] - clipBounds[0]);
993                 offsets[1] = CLIPPING_EPSILON * (clipBounds[3] - clipBounds[2]);
994             }
995
996             equations = new Vector4d[numPlanes];
997
998             equations[0] = new Vector4d(+1, 0, 0, -clipBounds[0] + offsets[0]);
999             equations[1] = new Vector4d(-1, 0, 0, +clipBounds[1] + offsets[0]);
1000             equations[2] = new Vector4d(0, +1, 0, -clipBounds[2] + offsets[1]);
1001             equations[3] = new Vector4d(0, -1, 0, +clipBounds[3] + offsets[1]);
1002
1003             /* If clipping is performed against the Axes box, the z plane equations must be initialized. */
1004             if (numPlanes == 6) {
1005                 equations[4] = new Vector4d(0, 0, +1, -clipBounds[4] + offsets[2]);
1006                 equations[5] = new Vector4d(0, 0, -1, +clipBounds[5] + offsets[2]);
1007             }
1008
1009             Transformation currentTransformation = drawingTools.getTransformationManager().getTransformation();
1010             for (int i = 0 ; i < numPlanes; i++) {
1011                 ClippingPlane plane = drawingTools.getClippingManager().getClippingPlane(i);
1012                 plane.setTransformation(currentTransformation);
1013                 plane.setEquation(equations[i]);
1014                 plane.setEnable(true);
1015             }
1016         }
1017     }
1018
1019     /**
1020      * Disables clipping for the given {@link ClippableProperty}.
1021      * @param clipProperty the clip property for which clipping is disabled.
1022      */
1023     public void disableClipping(ClippableProperty clipProperty) {
1024         int numPlanes = 0;
1025
1026         if (clipProperty.getClipState() == ClipStateType.CLIPGRF) {
1027             numPlanes = 6;
1028         } else if (clipProperty.getClipState() == ClipStateType.ON) {
1029             numPlanes = 4;
1030         }
1031
1032         for (int i = 0 ; i < numPlanes; i++) {
1033             ClippingPlane plane = visitor.getDrawingTools().getClippingManager().getClippingPlane(i);
1034             plane.setEnable(false);
1035         }
1036
1037         visitor.getDrawingTools().getClippingManager().disableClipping();
1038     }
1039
1040     /**
1041      * Returns the x-axis label positioner.
1042      * @return the x-axis label positioner.
1043      */
1044     public AxisLabelPositioner getXAxisLabelPositioner(Axes axes) {
1045         AxisLabelPositioner positioner = this.xAxisLabelPositioner.get(axes.getIdentifier());
1046         if (positioner == null) {
1047             positioner = new AxisLabelPositioner();
1048             this.xAxisLabelPositioner.put(axes.getIdentifier(), positioner);
1049         }
1050
1051         return positioner;
1052     }
1053
1054     /**
1055      * Returns the y-axis label positioner.
1056      * @return the y-axis label positioner.
1057      */
1058     public AxisLabelPositioner getYAxisLabelPositioner(Axes axes) {
1059         AxisLabelPositioner positioner = this.yAxisLabelPositioner.get(axes.getIdentifier());
1060         if (positioner == null) {
1061             positioner = new YAxisLabelPositioner();
1062             this.yAxisLabelPositioner.put(axes.getIdentifier(), positioner);
1063         }
1064
1065         return positioner;
1066     }
1067
1068     /**
1069      * Returns the z-axis label positioner.
1070      * @return the z-axis label positioner.
1071      */
1072     public AxisLabelPositioner getZAxisLabelPositioner(Axes axes) {
1073         AxisLabelPositioner positioner = this.zAxisLabelPositioner.get(axes.getIdentifier());
1074         if (positioner == null) {
1075             positioner = new AxisLabelPositioner();
1076             this.zAxisLabelPositioner.put(axes.getIdentifier(), positioner);
1077         }
1078
1079         return positioner;
1080     }
1081
1082     /**
1083      * Returns the title positioner.
1084      * @return the title positioner.
1085      */
1086     public LabelPositioner getTitlePositioner(Axes axes) {
1087         TitlePositioner positioner = this.titlePositioner.get(axes.getIdentifier());
1088         if (positioner == null) {
1089             positioner = new TitlePositioner();
1090             this.titlePositioner.put(axes.getIdentifier(), positioner);
1091         }
1092
1093         return positioner;
1094     }
1095
1096     public void disposeAll() {
1097         this.rulerDrawer.disposeAll();
1098         this.projectionMap.clear();
1099         this.projection2dViewMap.clear();
1100         this.sceneProjectionMap.clear();
1101         this.xAxisLabelPositioner.clear();
1102         this.yAxisLabelPositioner.clear();
1103         this.zAxisLabelPositioner.clear();
1104         this.titlePositioner.clear();
1105     }
1106
1107     public void update(String id, int property) {
1108         if (this.rulerDrawer.update(id, property)) {
1109             GraphicObject object = GraphicController.getController().getObjectFromId(id);
1110             if (object instanceof Axes) {
1111                 computeRulers((Axes) object);
1112             }
1113         }
1114     }
1115
1116     public void dispose(String id) {
1117         this.rulerDrawer.dispose(id);
1118         projectionMap.remove(id);
1119         projection2dViewMap.remove(id);
1120         sceneProjectionMap.remove(id);
1121         this.xAxisLabelPositioner.remove(id);
1122         this.yAxisLabelPositioner.remove(id);
1123         this.zAxisLabelPositioner.remove(id);
1124         this.titlePositioner.remove(id);
1125     }
1126 }