bb7cbb4287e41a34011f9ae01d4e2793e5bd0aa0
[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.1-en.txt
11  */
12
13 package org.scilab.modules.renderer.JoGLView.axes;
14
15 import org.scilab.forge.scirenderer.DrawingTools;
16 import org.scilab.forge.scirenderer.SciRendererException;
17 import org.scilab.forge.scirenderer.clipping.ClippingPlane;
18 import org.scilab.forge.scirenderer.shapes.appearance.Appearance;
19 import org.scilab.forge.scirenderer.shapes.geometry.Geometry.FaceCullingMode;
20 import org.scilab.forge.scirenderer.tranformations.DegenerateMatrixException;
21 import org.scilab.forge.scirenderer.tranformations.Transformation;
22 import org.scilab.forge.scirenderer.tranformations.TransformationFactory;
23 import org.scilab.forge.scirenderer.tranformations.TransformationStack;
24 import org.scilab.forge.scirenderer.tranformations.Vector3d;
25 import org.scilab.forge.scirenderer.tranformations.Vector4d;
26 import org.scilab.modules.graphic_objects.axes.Axes;
27 import org.scilab.modules.graphic_objects.axes.AxisProperty;
28 import org.scilab.modules.graphic_objects.axes.Box;
29 import org.scilab.modules.graphic_objects.axes.Camera.ViewType;
30 import org.scilab.modules.graphic_objects.contouredObject.Line;
31 import org.scilab.modules.graphic_objects.graphicController.GraphicController;
32 import org.scilab.modules.graphic_objects.graphicObject.GraphicObject;
33 import org.scilab.modules.graphic_objects.graphicObject.ClippableProperty;
34 import org.scilab.modules.graphic_objects.graphicObject.ClippableProperty.ClipStateType;
35 import org.scilab.modules.graphic_objects.figure.ColorMap;
36 import org.scilab.modules.graphic_objects.figure.Figure;
37 import org.scilab.modules.graphic_objects.legend.Legend;
38 import org.scilab.modules.graphic_objects.legend.Legend.LegendLocation;
39 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
40 import org.scilab.modules.renderer.JoGLView.axes.ruler.AxesRulerDrawer;
41 import org.scilab.modules.renderer.JoGLView.label.AxisLabelPositioner;
42 import org.scilab.modules.renderer.JoGLView.label.LabelManager;
43 import org.scilab.modules.renderer.JoGLView.label.TitlePositioner;
44 import org.scilab.modules.renderer.JoGLView.label.YAxisLabelPositioner;
45 import org.scilab.modules.renderer.JoGLView.legend.LegendDrawer;
46 import org.scilab.modules.renderer.JoGLView.util.ColorFactory;
47 import org.scilab.modules.renderer.JoGLView.util.ScaleUtils;
48
49 import java.awt.Dimension;
50 import java.awt.geom.Rectangle2D;
51 import java.util.HashMap;
52 import java.util.Map;
53
54 /**
55  *
56  * AxesDrawer are used by {@see DrawerVisitor} to draw {@see Axes}.
57  *
58  * @author Pierre Lando
59  */
60 public class AxesDrawer {
61     private static final double DEFAULT_THETA = 270.0;
62     private static final Line.LineType HIDDEN_BORDER_PATTERN = Line.LineType.DASH;
63
64     /** An epsilon value used to move the clipping planes in order to prevent strict clipping. */
65     private static final double CLIPPING_EPSILON = 1e-5;
66
67     private final DrawerVisitor visitor;
68     private final Geometries geometries;
69
70     private final AxesRulerDrawer rulerDrawer;
71
72     /** The front face culling mode. */
73     private FaceCullingMode frontFaceCullingMode;
74
75     /** The back face culling mode. */
76     private FaceCullingMode backFaceCullingMode;
77
78     /** The label manager. */
79     private final LabelManager labelManager;
80
81     /** The x-axis label positioner. */
82     private final Map<Integer, AxisLabelPositioner> xAxisLabelPositioner = new HashMap<Integer, AxisLabelPositioner>();
83
84     /** The y-axis label positioner. */
85     private final Map<Integer, AxisLabelPositioner> yAxisLabelPositioner = new HashMap<Integer, AxisLabelPositioner>();
86
87     /** The z-axis label positioner. */
88     private final Map<Integer, AxisLabelPositioner> zAxisLabelPositioner = new HashMap<Integer, AxisLabelPositioner>();
89
90     /** The title positioner. */
91     private final Map<Integer, TitlePositioner> titlePositioner = new HashMap<Integer, TitlePositioner>();
92
93     /**
94      * The current reversed bounds. Used by the functions converting
95      * between object and box coordinates.
96      */
97     private double[] reversedBounds;
98
99     /** The current reversed bounds intervals. */
100     private double[] reversedBoundsIntervals;
101
102     /** The current projection (from object to window coordinates) used when drawing objects. */
103     private Transformation currentProjection;
104
105     /**  The current data scale and translate transformation. */
106     private Transformation currentDataTransformation;
107
108     /** The set of object to window coordinate projections associated to all the Axes drawn by this drawer. */
109     private final Map<Integer, Transformation> projectionMap = new HashMap<Integer, Transformation>();
110
111     /** The set of object (in 2d view mode) to window coordinate projections associated to all the Axes drawn by this drawer. */
112     private final Map<Integer, Transformation> projection2dViewMap = new HashMap<Integer, Transformation>();
113
114     /** This is a __MAP__ */
115     private final Map<Integer, Transformation> sceneProjectionMap = new HashMap<Integer, Transformation>();
116
117     /**
118      * Default constructor.
119      * @param visitor the parent {@see DrawerVisitor}.
120      */
121     public AxesDrawer(DrawerVisitor visitor) {
122         this.visitor = visitor;
123         this.labelManager = visitor.getLabelManager();
124         this.geometries = new Geometries(visitor.getCanvas());
125         this.rulerDrawer = new AxesRulerDrawer(visitor.getCanvas());
126
127         reversedBounds = new double[6];
128         reversedBoundsIntervals = new double[3];
129     }
130
131     /**
132      * @return the axis label manager
133      */
134     public LabelManager getLabelManager() {
135         return labelManager;
136     }
137
138     public Transformation getCurrentProjection(Axes axes) throws DegenerateMatrixException {
139         DrawingTools drawingTools = visitor.getDrawingTools();
140         Transformation zoneProjection = computeZoneProjection(axes);
141         Transformation transformation = computeBoxTransformation(axes, visitor.getCanvas().getDimension(), false);
142         Transformation dataTransformation = computeDataTransformation(axes);
143         Transformation windowTrans;
144         if (drawingTools == null) {
145             windowTrans = TransformationFactory.getIdentity();
146         } else {
147             windowTrans = drawingTools.getTransformationManager().getWindowTransformation().getInverseTransformation();
148         }
149         Transformation current = zoneProjection.rightTimes(transformation);
150         current = current.rightTimes(dataTransformation);
151
152         return windowTrans.rightTimes(current);
153     }
154
155     /**
156      * Compute the graduations on the axes
157      * @param axes the axes
158      */
159     public void computeRulers(Axes axes) {
160         Figure figure = (Figure) GraphicController.getController().getObjectFromId(axes.getParentFigure());
161
162         //figure may be null during xml loading
163         if (figure == null) {
164             return;
165         }
166
167         final ColorMap colorMap = figure.getColorMap();
168         try {
169             Dimension dims = visitor.getCanvas().getDimension();
170             double w = dims.getWidth() / 2.0;
171             double h = dims.getHeight() / 2.0;
172
173             Transformation windowTrans = TransformationFactory.getAffineTransformation(new Vector3d(w, h, 1), new Vector3d(w, h, 0));
174             Transformation zoneProjection = computeZoneProjection(axes);
175             Transformation transformation = computeBoxTransformation(axes, dims, false);
176             Transformation canvasTrans = windowTrans.rightTimes(zoneProjection).rightTimes(transformation);
177
178             rulerDrawer.computeRulers(axes, this, colorMap, transformation, canvasTrans);
179         } catch (DegenerateMatrixException e) {
180
181         }
182     }
183
184     public void computeMargins(Axes axes) {
185         if (axes.getAutoMargins() && axes.getViewAsEnum() == ViewType.VIEW_2D) {
186             ColorMap colorMap = visitor.getColorMap();
187             Dimension[] marginLabels = labelManager.getLabelsSize(colorMap, axes, this);
188             Integer[] size = {visitor.getCanvas().getWidth(), visitor.getCanvas().getHeight()};
189             // [x_left, y_up, w, h]
190             Double[] axesBounds = axes.getAxesBounds();
191             // [l, r, t, b]
192             Double[] margins = axes.getMargins();
193             // m is a copy of margins
194             Double[] mt = new Double[] { 0., 0., 0., 0. };
195             Double[] ml = new Double[] { 0., 0., 0., 0. };
196             Double[] ma = new Double[] { 0., 0., 0., 0. };
197             Double[] m = new Double[] { 0., 0., 0., 0. };
198             AxisProperty.AxisLocation xloc = axes.getXAxis().getAxisLocation();
199             AxisProperty.AxisLocation yloc = axes.getYAxis().getAxisLocation();
200             final double DEFAULT_MARGIN = 0.125;
201
202             // We compute the adapted margins for axes titles.
203             if (marginLabels[0].height != 0 || marginLabels[2].height != 0 || marginLabels[1].width != 0) {
204                 if (marginLabels[2].height != 0) {
205                     final double th = (marginLabels[2].height + 2 + TitlePositioner.TITLEOFFSET) / (size[1] * axesBounds[3]);
206                     mt[2] = th;
207                 }
208
209                 if (marginLabels[0].height != 0 && (xloc == AxisProperty.AxisLocation.BOTTOM || xloc == AxisProperty.AxisLocation.TOP)) {
210                     final double xh = (marginLabels[0].height + 2) / (size[1] * axesBounds[3]);
211                     if (xloc == AxisProperty.AxisLocation.BOTTOM) {
212                         mt[3] = xh;
213                     } else {
214                         mt[2] += xh;
215                     }
216                 }
217
218                 if (marginLabels[1].width != 0 && (yloc == AxisProperty.AxisLocation.LEFT || yloc == AxisProperty.AxisLocation.RIGHT)) {
219                     final double yh = (marginLabels[1].width + 2) / (size[0] * axesBounds[2]);
220                     if (yloc == AxisProperty.AxisLocation.LEFT) {
221                         mt[0] = yh;
222                     } else {
223                         mt[1] = yh;
224                     }
225                 }
226             }
227
228             //computeRulers(axes);
229             final double xratio = rulerDrawer.getRulerDrawer(axes, 0).getDistanceRatio();
230             final double yratio = rulerDrawer.getRulerDrawer(axes, 1).getDistanceRatio();
231
232             if (xloc == AxisProperty.AxisLocation.BOTTOM) {
233                 ma[3] = (1 - margins[2] - margins[3]) * xratio / 2.;
234             } else if (xloc == AxisProperty.AxisLocation.TOP) {
235                 ma[2] = (1 - margins[2] - margins[3]) * xratio / 2.;
236             }
237
238             if (yloc == AxisProperty.AxisLocation.LEFT) {
239                 ma[0] = (1 - margins[0] - margins[1]) * yratio / 2.;
240             } else if (yloc == AxisProperty.AxisLocation.RIGHT) {
241                 ma[1] = (1 - margins[0] - margins[1]) * yratio / 2.;
242             }
243
244             // Get the legend if any (only one ???)
245             if (axes.getChildren() != null) {
246                 for (Integer i : axes.getChildren()) {
247                     GraphicObject child = GraphicController.getController().getObjectFromId(i);
248                     if (child instanceof Legend) {
249                         Legend legend = (Legend) child;
250                         Dimension legDims = visitor.getLegendDrawer().computeDimensions(axes, legend);
251                         if (legDims != null) {
252                             LegendLocation legLoc = legend.getLegendLocationAsEnum();
253                             double C;
254                             /*
255                              * Legends dimension are linearly dependent of margins... so we need to solve an equation
256                              * to find a good value for margins.
257                              * For example:
258                              *  legend.w = texture.w + 3/8 * line.w + line.w
259                              *  where line.w = LINE_WIDTH * ab[2] * (1 - m[0] - m[1]) * size[0];
260                              *  the minimal value for m[1] is the solution of the equation (where unknown is m[1]):
261                              *   legend.w = ab[2] * m[1] * size[0].
262                              */
263                             switch (legLoc) {
264                                 case OUT_UPPER_RIGHT:
265                                 case OUT_LOWER_RIGHT:
266                                     // 1/8 of LINE_WIDTH is xOffset
267                                     // see legendDims[0] = ... in LegendDrawer::draw
268                                     // we add 2*xoffset to have a little space around the box
269                                     C = legend.getLineWidth() + LegendDrawer.LINE_WIDTH * (3. / 8. + 2. / 8.);
270                                     m[0] = Math.max(ma[0] + mt[0], DEFAULT_MARGIN);
271                                     m[1] = Math.max(((legDims.width + 2) / (axesBounds[2] * size[0]) + C * (1 - m[0])) / (1 + C) + ma[1] + mt[1], DEFAULT_MARGIN);
272                                     break;
273                                 case OUT_UPPER_LEFT:
274                                 case OUT_LOWER_LEFT:
275                                     C = legend.getLineWidth() + LegendDrawer.LINE_WIDTH * (3. / 8. + 2. / 8.);
276                                     m[1] = Math.max(ma[1] + mt[1], DEFAULT_MARGIN);
277                                     m[0] = Math.max(((legDims.width + 2) / (axesBounds[2] * size[0]) + C * (1 - m[1])) / (1 + C) + ma[0] + mt[0], DEFAULT_MARGIN);
278                                     break;
279                                 case UPPER_CAPTION:
280                                     C = LegendDrawer.Y_OFFSET * (3. + 2.);
281                                     m[3] = Math.max(ma[3] + mt[3], DEFAULT_MARGIN);
282                                     m[2] = Math.max(Math.max(((legDims.height + 2) / (axesBounds[3] * size[1]) + C * (1 - m[3])) / (1 + C), mt[2]) + ma[2], DEFAULT_MARGIN);
283                                     break;
284                                 case LOWER_CAPTION:
285                                     C = LegendDrawer.Y_OFFSET * (3. + 2.);
286                                     m[2] = Math.max(ma[2] + mt[2], DEFAULT_MARGIN);
287                                     m[3] = Math.max(Math.max(((legDims.height + 2) / (axesBounds[3] * size[1]) + C * (1 - m[2])) / (1 + C), mt[3]) + ma[3], DEFAULT_MARGIN);
288                                     break;
289                                 default:
290                             }
291                         }
292                         break;
293                     }
294                 }
295             }
296
297             for (int i = 0; i < m.length; i++) {
298                 if (m[i] == 0) {
299                     m[i] = Math.max(ma[i] + mt[i], DEFAULT_MARGIN);
300                 }
301             }
302
303             if (!m[0].equals(margins[0]) || !m[1].equals(margins[1]) || !m[2].equals(margins[2]) || !m[3].equals(margins[3])) {
304                 axes.setMargins(m);
305                 //computeRulers(axes);
306             }
307         }
308     }
309
310     /**
311      * Draw the given {@see Axes}.
312      * @param axes {@see Axes} to draw.
313      * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
314      */
315     public void draw(Axes axes) throws SciRendererException {
316         DrawingTools drawingTools = visitor.getDrawingTools();
317         //Integer[] size = visitor.getFigure().getAxesSize();
318         //Dimension canvasDimension = new Dimension(size[0], size[1]);
319         Dimension canvasDimension = visitor.getCanvas().getDimension();
320         ColorMap colorMap = visitor.getColorMap();
321         TransformationStack modelViewStack = drawingTools.getTransformationManager().getModelViewStack();
322         TransformationStack projectionStack = drawingTools.getTransformationManager().getProjectionStack();
323
324         // Set axes zone.
325         Transformation zoneProjection = computeZoneProjection(axes);
326         projectionStack.push(zoneProjection);
327
328         // Set box projection.
329         Transformation transformation = computeBoxTransformation(axes, canvasDimension, false);
330         modelViewStack.pushRightMultiply(transformation);
331
332         /* Compute the data scale and translate transformation. */
333         Transformation dataTransformation = computeDataTransformation(axes);
334
335         currentDataTransformation = dataTransformation;
336
337         /* Compute the object to window coordinates projection. */
338         currentProjection = zoneProjection.rightTimes(transformation);
339         currentProjection = currentProjection.rightTimes(dataTransformation);
340
341         sceneProjectionMap.put(axes.getIdentifier(), currentProjection);
342
343         Transformation windowTrans = drawingTools.getTransformationManager().getInverseWindowTransformation();
344         currentProjection = windowTrans.rightTimes(currentProjection);
345
346         /* Update the projection maps with the resulting projections. */
347         addProjection(axes.getIdentifier(), currentProjection);
348
349         /* 2d view projection, to do: optimize computation */
350         if ((axes.getRotationAngles()[0] != 0 || axes.getRotationAngles()[1] != DEFAULT_THETA)) {
351             Transformation transformation2dView = computeBoxTransformation(axes, canvasDimension, true);
352             currentProjection = zoneProjection.rightTimes(transformation2dView);
353             currentProjection = currentProjection.rightTimes(dataTransformation);
354             currentProjection = windowTrans.rightTimes(currentProjection);
355         }
356
357         addProjection2dView(axes.getIdentifier(), currentProjection);
358
359         /**
360          * Draw the axes background.
361          */
362         drawBackground(axes, drawingTools, colorMap);
363
364         /**
365          * Mirror the transformation such that the corner with the maximum Z value was (-1, -1, -1).
366          * And draw the box.
367          */
368         Transformation cubeOrientation = computeCubeMirroring(transformation);
369         modelViewStack.pushRightMultiply(cubeOrientation);
370         drawBox(axes, drawingTools, colorMap);
371         modelViewStack.pop();
372
373         // Ruler are drawn in box coordinate.
374         rulerDrawer.drawRuler(axes, this, colorMap, drawingTools);
375
376         /* Compute reversed bounds. */
377         computeReversedBounds(axes);
378
379         /**
380          * Scale and translate for data fitting.
381          * And draw data.
382          */
383         modelViewStack.pushRightMultiply(dataTransformation);
384
385         /* Compute the front and back culling modes */
386         computeFaceCullingModes(axes);
387
388         visitor.askAcceptVisitor(axes.getChildren());
389         modelViewStack.pop();
390
391         // Reset transformation stacks.
392         modelViewStack.pop();
393         projectionStack.pop();
394     }
395
396     /**
397      * Draw the axes background.
398      * @param axes the {@see Axes}
399      * @param drawingTools the {@see DrawingTools} to use.
400      * @param colorMap the current {@see ColorMap}
401      * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
402      */
403     private void drawBackground(Axes axes, DrawingTools drawingTools, ColorMap colorMap) throws SciRendererException {
404         if (axes.getFilled()) {
405             Appearance appearance = new Appearance();
406             appearance.setFillColor(ColorFactory.createColor(colorMap, axes.getBackground()));
407             drawingTools.draw(geometries.getCubeGeometry(), appearance);
408             drawingTools.clearDepthBuffer();
409         }
410     }
411
412     /**
413      * Draw the box of the given {@see Axes}
414      * @param axes the given {@see Axes}.
415      * @param drawingTools the {@see DrawingTools} to use.
416      * @param colorMap the current {@see ColorMap}.
417      * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
418      */
419     private void drawBox(Axes axes, DrawingTools drawingTools, ColorMap colorMap) throws SciRendererException {
420         Box.BoxType boxed = axes.getBox().getBox();
421         if (boxed != Box.BoxType.OFF) {
422             Appearance appearance = new Appearance();
423
424             /**
425              * Draw hidden part of box.
426              */
427             if (axes.getViewAsEnum() != ViewType.VIEW_2D) {
428                 appearance.setLineColor(ColorFactory.createColor(colorMap, axes.getHiddenAxisColor()));
429                 appearance.setLineWidth(axes.getLineThickness().floatValue());
430                 appearance.setLinePattern(HIDDEN_BORDER_PATTERN.asPattern());
431                 drawingTools.draw(geometries.getHiddenBoxBorderGeometry(), appearance);
432             }
433
434             if (boxed != Box.BoxType.HIDDEN_AXES) {
435
436                 /**
437                  * Draw box border.
438                  */
439                 appearance.setLineColor(ColorFactory.createColor(colorMap, axes.getLineColor()));
440                 appearance.setLineWidth(axes.getLineThickness().floatValue());
441                 appearance.setLinePattern(axes.getLine().getLineStyle().asPattern());
442                 drawingTools.draw(geometries.getBoxBorderGeometry(), appearance);
443
444
445                 if (boxed != Box.BoxType.BACK_HALF) {
446                     /**
447                      * Draw front part of box.
448                      */
449                     drawingTools.draw(geometries.getFrontBoxBorderGeometry(), appearance);
450                 }
451             }
452         }
453     }
454
455     /**
456      * Compute the mirroring matrix needed to have the [-1; -1; -1] point projected with the maximal Z value.
457      * @param transformation the current transformation.
458      * @return the mirroring matrix needed to have the [-1; -1; -1] point projected with the maximal Z value.
459      */
460     private Transformation computeCubeMirroring(Transformation transformation) {
461         double[] matrix = transformation.getMatrix();
462         try {
463             return TransformationFactory.getScaleTransformation(
464                        matrix[2] < 0 ? 1 : -1,
465                        matrix[6] < 0 ? 1 : -1,
466                        matrix[10] < 0 ? 1 : -1
467                    );
468         } catch (DegenerateMatrixException e) {
469             // Should never happen.
470             return TransformationFactory.getIdentity();
471         }
472     }
473
474     /**
475      * Compute zone where the axes is draw. In normalized window coordinate.
476      * @param axes the given {@see axes}.
477      * @return the zone where the axes is draw.
478      */
479     private Rectangle2D computeZone(Axes axes) {
480         Double[] axesBounds = axes.getAxesBounds();
481         Double[] margins = axes.getMargins();
482
483         // TODO :  zoom box.
484         double x = (axesBounds[0] + axesBounds[2] * margins[0]) * 2 - 1;
485         double y = (1.0 - axesBounds[1] - axesBounds[3] * (1.0 - margins[3])) * 2 - 1;
486         double w = (1 - margins[0] - margins[1]) * axesBounds[2];
487         double h = (1 - margins[2] - margins[3]) * axesBounds[3];
488
489         if (axes.getIsoview()) {
490             double minSize = Math.min(w, h);
491             y += (h - minSize);
492             h = minSize;
493             x += (w - minSize);
494             w = minSize;
495         }
496
497         return new Rectangle2D.Double(x, y, w, h);
498     }
499
500     /**
501      * Compute the projection for the given axes.
502      * @param axes the given axes.
503      * @return the projection matrix.
504      * @throws DegenerateMatrixException if axes represent a nul area.
505      */
506     private Transformation computeZoneProjection(Axes axes) throws DegenerateMatrixException {
507         Rectangle2D zone = computeZone(axes);
508         Transformation zoneTranslation = TransformationFactory.getTranslateTransformation(zone.getMaxX(), zone.getMaxY(), 0);
509
510         // We scale by 0.5 in Z to allow ruler to be drawn.
511         Transformation zoneScale = TransformationFactory.getScaleTransformation(zone.getWidth(), zone.getHeight(), .5);
512
513         return zoneTranslation.rightTimes(zoneScale);
514     }
515
516     /**
517      * Compute data transformation for the given {@see Axes}.
518      *
519      * The data transformation is applied to data to fit in axes box.
520      *
521      * @param axes the given {@see Axes}
522      * @return data transformation.
523      * @throws DegenerateMatrixException if data bounds are not corrects.
524      */
525     private Transformation computeDataTransformation(Axes axes) throws DegenerateMatrixException {
526         // Reverse data if needed.
527         Transformation transformation = TransformationFactory.getScaleTransformation(
528                                             axes.getAxes()[0].getReverse() ? 1 : -1,
529                                             axes.getAxes()[1].getReverse() ? 1 : -1,
530                                             axes.getAxes()[2].getReverse() ? 1 : -1
531                                         );
532
533         if (axes.getZoomEnabled()) {
534             Double[] bounds = axes.getCorrectedBounds();
535
536             // Scale data.
537             Transformation scaleTransformation = TransformationFactory.getScaleTransformation(
538                     2.0 / (bounds[1] - bounds[0]),
539                     2.0 / (bounds[3] - bounds[2]),
540                     2.0 / (bounds[5] - bounds[4])
541                                                  );
542             transformation = transformation.rightTimes(scaleTransformation);
543
544
545             // Translate data.
546             Transformation translateTransformation = TransformationFactory.getTranslateTransformation(
547                         -(bounds[0] + bounds[1]) / 2.0,
548                         -(bounds[2] + bounds[3]) / 2.0,
549                         -(bounds[4] + bounds[5]) / 2.0
550                     );
551             transformation = transformation.rightTimes(translateTransformation);
552
553             return transformation;
554         }
555
556         return transformation;
557     }
558
559
560     /**
561      * Compute box transformation for the given axes.
562      *
563      * The box transformation is applied to the axes box to fit in the canvas.
564      *
565      * @param axes the given {@see Axes}.
566      * @param canvasDimension the current canvas {@see Canvas}.
567      * @param use2dView specifies whether the default 2d view rotation angles must be used (true) or the given Axes' ones (false).
568      * @return box transformation for the given axes.
569      * @throws DegenerateMatrixException if data bounds are incorrect or canvas with or length are zero.
570      */
571     private Transformation computeBoxTransformation(Axes axes, Dimension canvasDimension, boolean use2dView) throws DegenerateMatrixException {
572         Double[] bounds = axes.getDisplayedBounds();
573         double theta;
574
575         double tmpX;
576         double tmpY;
577         double tmpZ;
578
579         Transformation transformation;
580
581         // Set zone aspect ratio.
582         Rectangle2D zone = computeZone(axes);
583         double axesRatio = zone.getWidth() / zone.getHeight();
584         transformation = TransformationFactory.getPreferredAspectRatioTransformation(canvasDimension, axesRatio);
585
586         // Rotate.
587         if (use2dView) {
588             theta = 2 * DEFAULT_THETA;
589         } else {
590             double alpha = -axes.getRotationAngles()[0];
591             theta = DEFAULT_THETA + axes.getRotationAngles()[1];
592             if (alpha != 0) {
593                 Transformation alphaRotation = TransformationFactory.getRotationTransformation(alpha, 1.0, 0.0, 0.0);
594                 transformation = transformation.rightTimes(alphaRotation);
595             }
596         }
597
598         Transformation thetaRotation = TransformationFactory.getRotationTransformation(theta, 0.0, 0.0, 1.0);
599         transformation = transformation.rightTimes(thetaRotation);
600
601         // If there is no cube scaling, we must take into account the distribution of data.
602         if (!axes.getCubeScaling()) {
603             tmpX = (bounds[1] - bounds[0]);
604             tmpY = (bounds[3] - bounds[2]);
605             tmpZ = (bounds[5] - bounds[4]);
606
607             /**
608              * Here, we should divide the values by their maximum.
609              * But the next operation will automatically.
610              */
611             if (tmpX != 1 || tmpY != 1 || tmpZ != 1) {
612                 Transformation cubeScale = TransformationFactory.getScaleTransformation(tmpX, tmpY, tmpZ);
613                 transformation = transformation.rightTimes(cubeScale);
614             }
615         }
616
617         // Compute bounds of projected data.
618         double[] matrix = transformation.getMatrix();
619         tmpX = 1 / (Math.abs(matrix[0]) + Math.abs(matrix[4]) + Math.abs(matrix[8]));
620         tmpY = 1 / (Math.abs(matrix[1]) + Math.abs(matrix[5]) + Math.abs(matrix[9]));
621         tmpZ = 1 / (Math.abs(matrix[2]) + Math.abs(matrix[6]) + Math.abs(matrix[10]));
622
623         // Scale projected data to fit in the cube.
624         Transformation isoScale;
625         if (axes.getIsoview()) {
626             double w = zone.getWidth();
627             double h = zone.getHeight();
628             double minScale = Math.min(tmpX * w, tmpY * h);
629             isoScale = TransformationFactory.getScaleTransformation(minScale / w, minScale / h, tmpZ);
630         } else {
631             isoScale = TransformationFactory.getScaleTransformation(tmpX, tmpY, tmpZ);
632         }
633         transformation = transformation.leftTimes(isoScale);
634
635         return transformation;
636     }
637
638
639     /**
640      * Computes and sets the reversed bounds of the given Axes.
641      * @param axes the given {@see Axes}.
642      */
643     private void computeReversedBounds(Axes axes) {
644         Double[] currentBounds = axes.getCorrectedBounds();
645
646         /* Reverse */
647         if (axes.getAxes()[0].getReverse()) {
648             reversedBounds[0] = currentBounds[1];
649             reversedBounds[1] = currentBounds[0];
650         } else {
651             reversedBounds[0] = currentBounds[0];
652             reversedBounds[1] = currentBounds[1];
653         }
654
655         if (axes.getAxes()[1].getReverse()) {
656             reversedBounds[2] = currentBounds[3];
657             reversedBounds[3] = currentBounds[2];
658         } else {
659             reversedBounds[2] = currentBounds[2];
660             reversedBounds[3] = currentBounds[3];
661         }
662
663         if (axes.getAxes()[2].getReverse()) {
664             reversedBounds[4] = currentBounds[5];
665             reversedBounds[5] = currentBounds[4];
666         } else {
667             reversedBounds[4] = currentBounds[4];
668             reversedBounds[5] = currentBounds[5];
669         }
670
671         /*
672          * Interval values are set to 1 when bounds are equal to avoid divides by 0
673          * in the object to box coordinates conversion function.
674          */
675         if (reversedBounds[1] == reversedBounds[0]) {
676             reversedBoundsIntervals[0] = 1.0;
677         } else {
678             reversedBoundsIntervals[0] = reversedBounds[1] - reversedBounds[0];
679         }
680
681         if (reversedBounds[3] == reversedBounds[2]) {
682             reversedBoundsIntervals[1] = 1.0;
683         } else {
684             reversedBoundsIntervals[1] = reversedBounds[3] - reversedBounds[2];
685         }
686
687         if (reversedBounds[5] == reversedBounds[4]) {
688             reversedBoundsIntervals[2] = 1.0;
689         } else {
690             reversedBoundsIntervals[2] = reversedBounds[5] - reversedBounds[4];
691         }
692
693     }
694
695     /**
696      * Computes the culling modes respectively corresponding to front and back faces
697      * of the given Axes' child objects as a function of its {X,Y,Z} reverse properties.
698      * It must be called by draw prior to rendering any child object.
699      * @param axes the given {@see Axes}.
700      */
701     private void computeFaceCullingModes(Axes axes) {
702         if (axes.getAxes()[0].getReverse() ^ axes.getAxes()[1].getReverse() ^ axes.getAxes()[2].getReverse()) {
703             /* Front: CW */
704             this.frontFaceCullingMode = FaceCullingMode.CW;
705             this.backFaceCullingMode = FaceCullingMode.CCW;
706         } else {
707             /* Front: CCW */
708             this.frontFaceCullingMode = FaceCullingMode.CCW;
709             this.backFaceCullingMode = FaceCullingMode.CW;
710         }
711     }
712
713     /**
714      * Converts a point from object coordinates to box coordinates (used when drawing axis rulers).
715      * @param point the point in object coordinates.
716      * @return the point in box coordinates.
717      */
718     public Vector3d getBoxCoordinates(Vector3d point) {
719         double[] dataCoordinates = new double[3];
720
721         dataCoordinates[0] = 1 - 2.0 * (point.getX() - reversedBounds[0]) / reversedBoundsIntervals[0];
722         dataCoordinates[1] = 1 - 2.0 * (point.getY() - reversedBounds[2]) / reversedBoundsIntervals[1];
723         dataCoordinates[2] = 1 - 2.0 * (point.getZ() - reversedBounds[4]) / reversedBoundsIntervals[2];
724
725         return new Vector3d(dataCoordinates);
726     }
727
728     /**
729      * Converts a point from box coordinates (used when drawing axis rulers) to object coordinates.
730      * @param point the point in box coordinates.
731      * @return the point in object coordinates.
732      */
733     public Vector3d getObjectCoordinates(Vector3d point) {
734         double[] objectCoordinates = new double[3];
735
736         objectCoordinates[0] = 0.5 * (1.0 - point.getX()) * (reversedBounds[1] - reversedBounds[0]) + reversedBounds[0];
737         objectCoordinates[1] = 0.5 * (1.0 - point.getY()) * (reversedBounds[3] - reversedBounds[2]) + reversedBounds[2];
738         objectCoordinates[2] = 0.5 * (1.0 - point.getZ()) * (reversedBounds[5] - reversedBounds[4]) + reversedBounds[4];
739
740         return new Vector3d(objectCoordinates);
741     }
742
743     /**
744      * Computes and return the object to window coordinate projection corresponding to the given Axes object.
745      * @param axes the given Axes.
746      * @param drawingTools the drawing tools.
747      * @param canvasDimension the current canvas dimension.
748      * @param use2dView specifies whether the default 2d view rotation angles must be used (true) or the given Axes' ones (false).
749      * @return the projection
750      */
751     private Transformation computeProjection(Axes axes, DrawingTools drawingTools, Dimension canvasDimension, boolean use2dView) {
752         Transformation projection;
753
754         if (drawingTools == null) {
755             return TransformationFactory.getIdentity();
756         }
757
758         try {
759             /* Compute the zone projection. */
760             Transformation zoneProjection = computeZoneProjection(axes);
761
762             /* Compute the box transformation. */
763             Transformation transformation = computeBoxTransformation(axes, canvasDimension, use2dView);
764
765             /* Compute the data scale and translate transformation. */
766             Transformation dataTransformation = computeDataTransformation(axes);
767
768             /* Compute the object to window coordinates projection. */
769             projection = zoneProjection.rightTimes(transformation);
770             projection = projection.rightTimes(dataTransformation);
771
772             Transformation windowTrans = drawingTools.getTransformationManager().getWindowTransformation().getInverseTransformation();
773             projection = windowTrans.rightTimes(projection);
774         } catch (DegenerateMatrixException e) {
775             return TransformationFactory.getIdentity();
776         }
777
778         return projection;
779     }
780
781     /**
782      * Returns the current projection from object to window coordinates.
783      * @return the projection.
784      */
785     public Transformation getProjection() {
786         return currentProjection;
787     }
788
789     /**
790      * Returns the current data scale and translate transformation.
791      * @return the data transformation.
792      */
793     public Transformation getDataTransformation() {
794         return currentDataTransformation;
795     }
796
797     /**
798      * Adds the projection from object to window coordinates corresponding to a given Axes object
799      * to the projection map.
800      * @param axesId the identifier of the given Axes.
801      * @param projection the corresponding projection.
802      */
803     public synchronized void addProjection(Integer axesId, Transformation projection) {
804         projectionMap.put(axesId, projection);
805     }
806
807     /**
808      * Returns the projection from object to window coordinates corresponding
809      * to a given Axes object.
810      * @param id the identifier of the given Axes.
811      * @return the projection.
812      */
813     public Transformation getProjection(Integer id) {
814         return projectionMap.get(id);
815     }
816
817     /**
818      * Removes the object to window coordinate projection corresponding to a given Axes from
819      * the projection map.
820      * @param axesId the identifier of the given Axes.
821      */
822     public void removeProjection(Integer axesId) {
823         projectionMap.remove(axesId);
824     }
825
826     /**
827      * Adds the projection from object (in 2d view mode) to window coordinates corresponding to a given Axes object
828      * to the projection map.
829      * @param axesId the identifier of the given Axes.
830      * @param projection the corresponding projection.
831      */
832     public synchronized void addProjection2dView(Integer axesId, Transformation projection) {
833         projection2dViewMap.put(axesId, projection);
834     }
835
836     /**
837      * Returns the projection from object (in 2d view mode) to window coordinates corresponding
838      * to a given Axes object.
839      * @param id the identifier of the given Axes.
840      * @return the projection.
841      */
842     public Transformation getProjection2dView(Integer id) {
843         return projection2dViewMap.get(id);
844     }
845
846     public Transformation getSceneProjection(Integer id) {
847         return sceneProjectionMap.get(id);
848     }
849
850     /**
851      * Removes the object (in 2d view mode) to window coordinate projection corresponding to a given Axes from
852      * the projection map.
853      * @param axesId the identifier of the given Axes.
854      */
855     public void removeProjection2dView(Integer axesId) {
856         projection2dViewMap.remove(axesId);
857     }
858
859     /**
860      * Updates both the projection from object to window coordinates and the related
861      * object (in 2d view mode) to window coordinates projection for the given Axes object.
862      * @param axes the given Axes.
863      */
864     public static void updateAxesTransformation(Axes axes) {
865         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
866         AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
867         Dimension canvasDimension = currentVisitor.getCanvas().getDimension();
868
869         Transformation transformation = axesDrawer.getProjection(axes.getIdentifier());
870
871         /* The projection must be updated */
872         if (transformation == null) {
873             Transformation projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, false);
874
875             axesDrawer.addProjection(axes.getIdentifier(), projection);
876         }
877
878         Transformation transformation2dView = axesDrawer.getProjection2dView(axes.getIdentifier());
879
880         /* The projection must be updated */
881         if (transformation2dView == null) {
882             Transformation projection2dView = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, true);
883
884             axesDrawer.addProjection2dView(axes.getIdentifier(), projection2dView);
885         }
886     }
887
888     /**
889      * Computes and returns the coordinates of a point projected onto the default 2d view plane.
890      * To compute them, the point is projected using the object to window coordinate projection, then
891      * unprojected using the object to window coordinate projection corresponding to the default 2d view
892      * (which uses the default camera rotation angles).
893      * To do: optimize by using the already computed 3d view projection.
894      * @param axes the given Axes.
895      * @param coordinates the object (x,y,z) coordinates to project onto the 2d view plane (3-element array).
896      * @returns the 2d view coordinates (3-element array).
897      */
898     public static double[] compute2dViewCoordinates(Axes axes, double[] coordinates) {
899         // used in geom3d
900
901         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
902         AxesDrawer axesDrawer;
903         Transformation projection;
904         Transformation projection2d;
905         double[] coords = coordinates;
906
907         if (currentVisitor != null) {
908             boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
909             Dimension canvasDimension = currentVisitor.getCanvas().getDimension();
910             double[][] factors = axes.getScaleTranslateFactors();
911             ScaleUtils.applyLogScale(coords, logFlags);
912
913             axesDrawer = currentVisitor.getAxesDrawer();
914             coords[0] = coords[0] * factors[0][0] + factors[1][0];
915             coords[1] = coords[1] * factors[0][1] + factors[1][1];
916             coords[2] = coords[2] * factors[0][2] + factors[1][2];
917
918             projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, false);
919             projection2d = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, true);
920             Vector3d point = new Vector3d(coords);
921             point = projection.project(point);
922             point = projection2d.unproject(point);
923
924             coords = point.getData();
925             coords[0] = (coords[0] - factors[1][0]) / factors[0][0];
926             coords[1] = (coords[1] - factors[1][1]) / factors[0][1];
927             coords[2] = (coords[2] - factors[1][2]) / factors[0][2];
928
929             ScaleUtils.applyInverseLogScale(coords, logFlags);
930         }
931
932         return coords;
933     }
934
935     /**
936      * Computes and returns the pixel coordinates from a point's coordinates expressed in the default
937      * 2d view coordinate frame, using the given Axes. The returned pixel coordinates are expressed
938      * in the AWT's 2d coordinate frame.
939      * @param axes the given Axes.
940      * @param coordinates the 2d view coordinates (3-element array: x, y, z).
941      * @returns the pixel coordinates (2-element array: x, y).
942      */
943     public static double[] computePixelFrom2dViewCoordinates(Axes axes, double[] coordinates) {
944         // used by xchange
945
946         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
947         AxesDrawer axesDrawer;
948         double[] coords2dView = new double[] {0.0, 0.0, 0.0};
949
950         if (currentVisitor != null) {
951             boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
952             double[][] factors = axes.getScaleTranslateFactors();
953             ScaleUtils.applyLogScale(coordinates, logFlags);
954
955             axesDrawer = currentVisitor.getAxesDrawer();
956             coords2dView[0] = coordinates[0] * factors[0][0] + factors[1][0];
957             coords2dView[1] = coordinates[1] * factors[0][1] + factors[1][1];
958             coords2dView[2] = coordinates[2] * factors[0][2] + factors[1][2];
959
960             Transformation projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
961             if (projection2d == null) {
962                 updateAxesTransformation(axes);
963                 projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
964             }
965
966             Vector3d point = new Vector3d(coords2dView);
967             point = projection2d.project(point);
968
969             /* Convert the window coordinates to pixel coordinates, only y changes due to the differing y-axis convention */
970             coords2dView[0] = point.getX();
971             coords2dView[1] = currentVisitor.getCanvas().getHeight() - point.getY();
972             coords2dView[2] = 0;
973         }
974
975         return coords2dView;
976     }
977
978     /**
979      * Computes and returns the pixel coordinates from a point's coordinates expressed in the current
980      * 3d view coordinate frame, using the given Axes. The returned pixel coordinates are expressed
981      * in the AWT's 2d coordinate frame.
982      * @param axes the given Axes.
983      * @param coordinates the 3d view coordinates (3-element array: x, y, z).
984      * @returns the pixel coordinates (2-element array: x, y).
985      */
986     public static double[] computePixelFrom3dCoordinates(Axes axes, double[] coordinates) {
987         DrawerVisitor currentVisitor;
988         AxesDrawer axesDrawer;
989         Transformation projection;
990         Transformation projection2d;
991
992         currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
993         boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
994         double[][] factors = axes.getScaleTranslateFactors();
995
996         Vector3d point = new Vector3d(coordinates);
997         point = ScaleUtils.applyLogScale(point, logFlags);
998         double[] coords = point.getData();
999
1000         if (currentVisitor != null) {
1001             axesDrawer = currentVisitor.getAxesDrawer();
1002
1003             coords[0] = coords[0] * factors[0][0] + factors[1][0];
1004             coords[1] = coords[1] * factors[0][1] + factors[1][1];
1005             coords[2] = coords[2] * factors[0][2] + factors[1][2];
1006
1007             projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), currentVisitor.getCanvas().getDimension(), false);
1008
1009             point = new Vector3d(coords);
1010             point = projection.project(point);
1011         }
1012
1013         return new double[] {point.getX(), currentVisitor.getCanvas().getHeight() - point.getY(), point.getZ()};
1014     }
1015
1016     /**
1017      * Computes and returns the pixel coordinates from a point's coordinates expressed in the current
1018      * 3d view coordinate frame, using the given Axes. The returned pixel coordinates are expressed
1019      * in the AWT's 2d coordinate frame.
1020      * @param axes the given Axes.
1021      * @param coordinates the 3d view coordinates (3-element array: x, y, z).
1022      * @returns the pixel coordinates (2-element array: x, y).
1023      */
1024     public static double[][] computePixelFrom3dCoordinates(Axes axes, double[] coordsX, double[] coordsY, double[] coordsZ) {
1025         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
1026
1027         if (currentVisitor != null) {
1028             AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
1029             Dimension canvasDimension = currentVisitor.getCanvas().getDimension();
1030             double height = canvasDimension.getHeight();
1031             boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
1032             double[][] factors = axes.getScaleTranslateFactors();
1033             double[] coords = new double[3];
1034             double[][] ret = new double[coordsX.length][2];
1035             Transformation projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, false);
1036
1037             for (int i = 0; i < coordsX.length; i++) {
1038                 coords[0] = coordsX[i];
1039                 coords[1] = coordsY[i];
1040                 coords[2] = coordsZ[i];
1041                 ScaleUtils.applyLogScale(coords, logFlags);
1042
1043                 coords[0] = coords[0] * factors[0][0] + factors[1][0];
1044                 coords[1] = coords[1] * factors[0][1] + factors[1][1];
1045                 coords[2] = coords[2] * factors[0][2] + factors[1][2];
1046
1047                 Vector3d point = new Vector3d(coords);
1048                 point = projection.project(point);
1049                 ret[i][0] = point.getX();
1050                 ret[i][1] = height - point.getY();
1051             }
1052
1053             return ret;
1054         }
1055
1056         return null;
1057     }
1058
1059     /**
1060      * Computes and returns the coordinates of a point onto the 3d view plane.
1061      * To compute them, the point is projected using the object to window coordinate projection, then
1062      * unprojected using the object to window coordinate projection corresponding to the 3d view
1063      * @param axes the given Axes.
1064      * @param coordinates the object (x,y,z) coordinates to project onto the 2d view plane (3-element array).
1065      * @returns the 3d view coordinates (3-element array).
1066      */
1067     public static double[] compute3dViewCoordinates(Axes axes, double[] coordinates) {
1068         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
1069         AxesDrawer axesDrawer;
1070         Transformation projection;
1071         Transformation projection2d;
1072         double[] coords = coordinates;
1073
1074         if (currentVisitor != null) {
1075             if (axes.getViewAsEnum() == ViewType.VIEW_2D) {
1076                 // No need to projet/unproject since the product is identity
1077                 return new double[] {coords[0], coords[1], coords[2]};
1078             }
1079
1080             boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
1081             Dimension canvasDimension = currentVisitor.getCanvas().getDimension();
1082             double[][] factors = axes.getScaleTranslateFactors();
1083             ScaleUtils.applyLogScale(coords, logFlags);
1084
1085             axesDrawer = currentVisitor.getAxesDrawer();
1086             coords[0] = coords[0] * factors[0][0] + factors[1][0];
1087             coords[1] = coords[1] * factors[0][1] + factors[1][1];
1088             coords[2] = coords[2] * factors[0][2] + factors[1][2];
1089
1090             projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, false);
1091             projection2d = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, true);
1092
1093             Vector3d point = new Vector3d(coords);
1094             point = projection2d.project(point);
1095             point = projection.unproject(point);
1096
1097             coords = point.getData();
1098             coords[0] = (coords[0] - factors[1][0]) / factors[0][0];
1099             coords[1] = (coords[1] - factors[1][1]) / factors[0][1];
1100             coords[2] = (coords[2] - factors[1][2]) / factors[0][2];
1101
1102             ScaleUtils.applyInverseLogScale(coords, logFlags);
1103         }
1104
1105         return coords;
1106     }
1107
1108     /**
1109      * Computes and returns the coordinates of a point projected onto the default 2d view plane
1110      * from its pixel coordinates, using the given Axes. Pixel coordinates are expressed in
1111      * the AWT's 2d coordinate frame.
1112      * The returned point's z component is set to 0, as we only have x and y as an input.
1113      * @param axes the given Axes.
1114      * @param coordinates the pixel coordinates (2-element array: x, y).
1115      * @return coordinates the 2d view coordinates (3-element array: x, y, z).
1116      */
1117     public static double[] compute2dViewFromPixelCoordinates(Axes axes, double[] coordinates) {
1118         // used by xgetmouse and by xchange
1119
1120         DrawerVisitor currentVisitor;
1121         AxesDrawer axesDrawer;
1122         double[] coords2dView = new double[] {0.0, 0.0, 0.0};
1123
1124         currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
1125
1126         if (currentVisitor != null) {
1127             boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
1128             double[][] factors = axes.getScaleTranslateFactors();
1129
1130             axesDrawer = currentVisitor.getAxesDrawer();
1131
1132             /* Convert the pixel coordinates to window coordinates, only y changes due to the differing y-axis convention */
1133             Vector3d point = new Vector3d(coordinates[0], currentVisitor.getCanvas().getHeight() - coordinates[1], 0.0);
1134
1135             Transformation projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
1136             if (projection2d == null) {
1137                 updateAxesTransformation(axes);
1138                 projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
1139             }
1140
1141             point = projection2d.unproject(point);
1142             coords2dView = point.getData();
1143
1144             coords2dView[0] = (coords2dView[0] - factors[1][0]) / factors[0][0];
1145             coords2dView[1] = (coords2dView[1] - factors[1][1]) / factors[0][1];
1146             coords2dView[2] = (coords2dView[2] - factors[1][2]) / factors[0][2];
1147
1148             ScaleUtils.applyInverseLogScale(coords2dView, logFlags);
1149         }
1150
1151         return coords2dView;
1152     }
1153
1154     /**
1155      * Un-project the given point from AWT coordinate to given axes coordinate.
1156      * @param axes returned coordinate are relative to this axes.
1157      * @param point un-projected point.
1158      * @return The un-projected point.
1159      */
1160     public static Vector3d unProject(Axes axes, Vector3d point) {
1161         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
1162
1163         if (currentVisitor != null) {
1164             AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
1165             double height = currentVisitor.getCanvas().getHeight() - 1;
1166
1167             Transformation projection2d = axesDrawer.getProjection(axes.getIdentifier());
1168             return projection2d.unproject(new Vector3d(point.getX(), height - point.getY(), point.getZ()));
1169         } else {
1170             return new Vector3d(0, 0, 0);
1171         }
1172     }
1173
1174     /**
1175      * Computes and returns the viewing area corresponding to the given Axes object.
1176      * The viewing area is described by the (x, y) coordinates of the Axes box's upper-left corner
1177      * and the Axes box's dimensions (width and height), all values are in pixel.
1178      * The 2d coordinate frame in which the area is expressed uses the AWT convention:
1179      * upper-left window corner at (0, 0), y-axis pointing downwards).
1180      * @param axes the given Axes.
1181      * @return the Axes' viewing area (4-element array: x, y, width, height).
1182      */
1183     public static double[] getViewingArea(Axes axes) {
1184         DrawerVisitor currentVisitor;
1185
1186         double[] viewingArea = new double[] {0.0, 0.0, 0.0, 0.0};
1187
1188         currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
1189
1190         if (currentVisitor != null) {
1191             double width = currentVisitor.getCanvas().getDimension().getWidth();
1192             double height = currentVisitor.getCanvas().getDimension().getHeight();
1193             double upperLeftY;
1194             AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
1195             Rectangle2D axesZone = axesDrawer.computeZone(axes);
1196
1197             /* Compute the upper-left point's y coordinate */
1198             upperLeftY = axesZone.getY() + axesZone.getHeight() * 2.0;
1199
1200             /* Convert from normalized coordinates to 2D pixel coordinates */
1201             viewingArea[0] = (axesZone.getX() + 1.0) * 0.5 * width;
1202             viewingArea[1] = (1.0 - upperLeftY) * 0.5 * height;
1203             viewingArea[2] = axesZone.getWidth() * width;
1204             viewingArea[3] = axesZone.getHeight() * height;
1205         }
1206
1207         return viewingArea;
1208     }
1209
1210     /**
1211      * Returns the culling mode corresponding to front faces.
1212      * @return the front face culling mode.
1213      */
1214     public FaceCullingMode getFrontFaceCullingMode() {
1215         return this.frontFaceCullingMode;
1216     }
1217
1218     /**
1219      * Returns the culling mode corresponding to back faces.
1220      * @return the back face culling mode.
1221      */
1222     public FaceCullingMode getBackFaceCullingMode() {
1223         return this.backFaceCullingMode;
1224     }
1225
1226     /**
1227      * Enables clipping for the given {@link ClippableProperty}, which describes
1228      * the clipping state of a clippable graphic object.
1229      * Depending on the object's clip state property, clipping can be either
1230      * disabled (OFF), performed against the parent Axes' box planes (CLIPGRF),
1231      * or performed against the planes defined by the object's clip box.
1232      * To do: find a better way to compute the clipping planes' offsets as the current one
1233      * may lead to problems when the interval between the min and max bounds is too small.
1234      * @param parentAxes the clipped object's parent Axes.
1235      * @param clipProperty the clipping property of a clippable object.
1236      */
1237     public void enableClipping(Axes parentAxes, ClippableProperty clipProperty) {
1238         DrawingTools drawingTools = visitor.getDrawingTools();
1239
1240         if (clipProperty.getClipState() != ClipStateType.OFF) {
1241             int numPlanes = 0;
1242             Vector4d[] equations = null;
1243
1244             /* Stores the (xmin,xmax) ,(ymin,ymax) and (zmin,zmax) clipping bounds */
1245             double[] clipBounds = new double[6];
1246
1247             /* The offsets used for the x, y and z planes in order to avoid strict clipping */
1248             double[] offsets = new double[3];
1249
1250             if (clipProperty.getClipState() == ClipStateType.CLIPGRF) {
1251                 /*
1252                  * All the clipping planes are set as clipping is performed
1253                  * against the Axes box planes.
1254                  */
1255                 numPlanes = 6;
1256                 Double[] bounds = parentAxes.getCorrectedBounds();
1257
1258                 for (int i = 0; i < numPlanes; i++) {
1259                     clipBounds[i] = bounds[i];
1260                 }
1261
1262                 offsets[0] = CLIPPING_EPSILON * (bounds[1] - bounds[0]);
1263                 offsets[1] = CLIPPING_EPSILON * (bounds[3] - bounds[2]);
1264                 offsets[2] = CLIPPING_EPSILON * (bounds[5] - bounds[4]);
1265             } else if (clipProperty.getClipState() == ClipStateType.ON) {
1266                 /*
1267                  * The clip box property defines values only for the x and y axes,
1268                  * we therefore set only the x and y clipping planes.
1269                  */
1270                 numPlanes = 4;
1271                 Double[] clipBox = clipProperty.getClipBox();
1272
1273                 /* The clip box stores the upper-left point coordinates. */
1274                 clipBounds[0] = clipBox[0];
1275                 clipBounds[1] = clipBox[0] + clipBox[2];
1276                 clipBounds[2] = clipBox[1] - clipBox[3];
1277                 clipBounds[3] = clipBox[1];
1278
1279                 double[][] factors = parentAxes.getScaleTranslateFactors();
1280                 Double[] bounds = parentAxes.getMaximalDisplayedBounds();
1281
1282                 /*
1283                  * The logarithmic scale must be applied to clip bounds values.
1284                  * If any of them are invalid, we set them to the displayed
1285                  * left bounds (xmin or ymin).
1286                  */
1287                 if (parentAxes.getXAxisLogFlag()) {
1288                     if (clipBounds[0] <= 0.0) {
1289                         clipBounds[0] = bounds[0];
1290                     } else {
1291                         clipBounds[0] = Math.log10(clipBounds[0]);
1292                     }
1293
1294                     if (clipBounds[1] <= 0.0) {
1295                         clipBounds[1] = bounds[0];
1296                     } else {
1297                         clipBounds[1] = Math.log10(clipBounds[1]);
1298                     }
1299                 }
1300
1301                 if (parentAxes.getYAxisLogFlag()) {
1302                     if (clipBounds[2] <= 0.0) {
1303                         clipBounds[2] = bounds[2];
1304                     } else {
1305                         clipBounds[2] = Math.log10(clipBounds[2]);
1306                     }
1307
1308                     if (clipBounds[3] <= 0.0) {
1309                         clipBounds[3] = bounds[2];
1310                     } else {
1311                         clipBounds[3] = Math.log10(clipBounds[3]);
1312                     }
1313                 }
1314
1315                 clipBounds[0] = clipBounds[0] * factors[0][0] + factors[1][0];
1316                 clipBounds[1] = clipBounds[1] * factors[0][0] + factors[1][0];
1317                 clipBounds[2] = clipBounds[2] * factors[0][1] + factors[1][1];
1318                 clipBounds[3] = clipBounds[3] * factors[0][1] + factors[1][1];
1319
1320                 offsets[0] = CLIPPING_EPSILON * (clipBounds[1] - clipBounds[0]);
1321                 offsets[1] = CLIPPING_EPSILON * (clipBounds[3] - clipBounds[2]);
1322             }
1323
1324             equations = new Vector4d[numPlanes];
1325
1326             equations[0] = new Vector4d(+1, 0, 0, -clipBounds[0] + offsets[0]);
1327             equations[1] = new Vector4d(-1, 0, 0, +clipBounds[1] + offsets[0]);
1328             equations[2] = new Vector4d(0, +1, 0, -clipBounds[2] + offsets[1]);
1329             equations[3] = new Vector4d(0, -1, 0, +clipBounds[3] + offsets[1]);
1330
1331             /* If clipping is performed against the Axes box, the z plane equations must be initialized. */
1332             if (numPlanes == 6) {
1333                 equations[4] = new Vector4d(0, 0, +1, -clipBounds[4] + offsets[2]);
1334                 equations[5] = new Vector4d(0, 0, -1, +clipBounds[5] + offsets[2]);
1335             }
1336
1337             Transformation currentTransformation = drawingTools.getTransformationManager().getTransformation();
1338
1339             for (int i = 0 ; i < numPlanes; i++) {
1340                 ClippingPlane plane = drawingTools.getClippingManager().getClippingPlane(i);
1341                 plane.setTransformation(currentTransformation);
1342                 plane.setEquation(equations[i]);
1343                 plane.setEnable(true);
1344             }
1345         }
1346     }
1347
1348     /**
1349      * Disables clipping for the given {@link ClippableProperty}.
1350      * @param clipProperty the clip property for which clipping is disabled.
1351      */
1352     public void disableClipping(ClippableProperty clipProperty) {
1353         int numPlanes = 0;
1354
1355         if (clipProperty.getClipState() == ClipStateType.CLIPGRF) {
1356             numPlanes = 6;
1357         } else if (clipProperty.getClipState() == ClipStateType.ON) {
1358             numPlanes = 4;
1359         }
1360
1361         for (int i = 0 ; i < numPlanes; i++) {
1362             ClippingPlane plane = visitor.getDrawingTools().getClippingManager().getClippingPlane(i);
1363             plane.setEnable(false);
1364         }
1365
1366         visitor.getDrawingTools().getClippingManager().disableClipping();
1367     }
1368
1369     /**
1370      * Returns the x-axis label positioner.
1371      * @return the x-axis label positioner.
1372      */
1373     public AxisLabelPositioner getXAxisLabelPositioner(Axes axes) {
1374         AxisLabelPositioner positioner = this.xAxisLabelPositioner.get(axes.getIdentifier());
1375         if (positioner == null) {
1376             positioner = new AxisLabelPositioner();
1377             this.xAxisLabelPositioner.put(axes.getIdentifier(), positioner);
1378         }
1379
1380         return positioner;
1381     }
1382
1383     /**
1384      * Returns the y-axis label positioner.
1385      * @return the y-axis label positioner.
1386      */
1387     public AxisLabelPositioner getYAxisLabelPositioner(Axes axes) {
1388         AxisLabelPositioner positioner = this.yAxisLabelPositioner.get(axes.getIdentifier());
1389         if (positioner == null) {
1390             positioner = new YAxisLabelPositioner();
1391             this.yAxisLabelPositioner.put(axes.getIdentifier(), positioner);
1392         }
1393
1394         return positioner;
1395     }
1396
1397     /**
1398      * Returns the z-axis label positioner.
1399      * @return the z-axis label positioner.
1400      */
1401     public AxisLabelPositioner getZAxisLabelPositioner(Axes axes) {
1402         AxisLabelPositioner positioner = this.zAxisLabelPositioner.get(axes.getIdentifier());
1403         if (positioner == null) {
1404             positioner = new AxisLabelPositioner();
1405             this.zAxisLabelPositioner.put(axes.getIdentifier(), positioner);
1406         }
1407
1408         return positioner;
1409     }
1410
1411     /**
1412      * Returns the title positioner.
1413      * @return the title positioner.
1414      */
1415     public TitlePositioner getTitlePositioner(Axes axes) {
1416         TitlePositioner positioner = this.titlePositioner.get(axes.getIdentifier());
1417         if (positioner == null) {
1418             positioner = new TitlePositioner();
1419             this.titlePositioner.put(axes.getIdentifier(), positioner);
1420         }
1421
1422         return positioner;
1423     }
1424
1425     public void disposeAll() {
1426         this.rulerDrawer.disposeAll();
1427         this.projectionMap.clear();
1428         this.projection2dViewMap.clear();
1429         this.sceneProjectionMap.clear();
1430         this.xAxisLabelPositioner.clear();
1431         this.yAxisLabelPositioner.clear();
1432         this.zAxisLabelPositioner.clear();
1433         this.titlePositioner.clear();
1434     }
1435
1436     public void update(Integer id, int property) {
1437         if (this.rulerDrawer.update(id, property)) {
1438             GraphicObject object = GraphicController.getController().getObjectFromId(id);
1439             if (object instanceof Axes) {
1440                 computeRulers((Axes) object);
1441             }
1442         }
1443     }
1444
1445     public void dispose(Integer id) {
1446         this.rulerDrawer.dispose(id);
1447         projectionMap.remove(id);
1448         projection2dViewMap.remove(id);
1449         sceneProjectionMap.remove(id);
1450         this.xAxisLabelPositioner.remove(id);
1451         this.yAxisLabelPositioner.remove(id);
1452         this.zAxisLabelPositioner.remove(id);
1453         this.titlePositioner.remove(id);
1454     }
1455 }