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