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