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