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