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