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