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