Wrong Zoombox calculation for isoview.
[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             appearance.setLineColor(ColorFactory.createColor(colorMap, axes.getHiddenAxisColor()));
243             appearance.setLineWidth(axes.getLineThickness().floatValue());
244             appearance.setLinePattern(HIDDEN_BORDER_PATTERN.asPattern());
245             drawingTools.draw(geometries.getHiddenBoxBorderGeometry(), appearance);
246
247
248             if (boxed != Box.BoxType.HIDDEN_AXES) {
249
250                 /**
251                  * Draw box border.
252                  */
253                 appearance.setLineColor(ColorFactory.createColor(colorMap, axes.getLineColor()));
254                 appearance.setLineWidth(axes.getLineThickness().floatValue());
255                 appearance.setLinePattern(axes.getLine().getLineStyle().asPattern());
256                 drawingTools.draw(geometries.getBoxBorderGeometry(), appearance);
257
258
259                 if (boxed != Box.BoxType.BACK_HALF) {
260                     /**
261                      * Draw front part of box.
262                      */
263                     drawingTools.draw(geometries.getFrontBoxBorderGeometry(), appearance);
264                 }
265             }
266         }
267     }
268
269
270     /**
271      * Compute the mirroring matrix needed to have the [-1; -1; -1] point projected with the maximal Z value.
272      * @param transformation the current transformation.
273      * @return the mirroring matrix needed to have the [-1; -1; -1] point projected with the maximal Z value.
274      */
275     private Transformation computeCubeMirroring(Transformation transformation) {
276         double[] matrix = transformation.getMatrix();
277         try {
278             return TransformationFactory.getScaleTransformation(
279                        matrix[2] < 0 ? 1 : -1,
280                        matrix[6] < 0 ? 1 : -1,
281                        matrix[10] < 0 ? 1 : -1
282                    );
283         } catch (DegenerateMatrixException e) {
284             // Should never happen.
285             return TransformationFactory.getIdentity();
286         }
287     }
288
289
290     /**
291      * Compute zone where the axes is draw. In normalised window coordinate.
292      * @param axes the given {@see axes}.
293      * @return the zone where the axes is draw.
294      */
295     private Rectangle2D computeZone(Axes axes) {
296         Double[] axesBounds = axes.getAxesBounds();
297         Double[] margins = axes.getMargins();
298
299         // TODO :  zoom box.
300         double x = (axesBounds[0] + axesBounds[2] * margins[0]) * 2 - 1;  
301         double y = (1.0 - axesBounds[1] - axesBounds[3] * (1.0 - margins[3])) * 2 - 1;
302         double w = (1 - margins[0] - margins[1]) * axesBounds[2];
303         double h = (1 - margins[2] - margins[3]) * axesBounds[3];
304
305         if (axes.getIsoview()) {
306             double minSize = Math.min(w, h);
307             y += (h - minSize);
308             h = minSize;
309             x += (w - minSize);
310             w = minSize;
311         }
312
313         return new Rectangle2D.Double(x, y, w, h);
314     }
315
316
317     /**
318      * Compute the projection for the given axes.
319      * @param axes the given axes.
320      * @return the projection matrix.
321      * @throws DegenerateMatrixException if axes represent a nul area.
322      */
323     private Transformation computeZoneProjection(Axes axes) throws DegenerateMatrixException {
324         Rectangle2D zone = computeZone(axes);
325         Transformation zoneTranslation = TransformationFactory.getTranslateTransformation(zone.getMaxX(), zone.getMaxY(), 0);
326
327         // We scale by 0.5 in Z to allow ruler to be drawn.
328         Transformation zoneScale = TransformationFactory.getScaleTransformation(zone.getWidth(), zone.getHeight(), .5);
329
330         return zoneTranslation.rightTimes(zoneScale);
331     }
332
333     /**
334      * Compute data transformation for the given {@see Axes}.
335      *
336      * The data transformation is applied to data to fit in axes box.
337      *
338      * @param axes the given {@see Axes}
339      * @return data transformation.
340      * @throws DegenerateMatrixException if data bounds are not corrects.
341      */
342     private Transformation computeDataTransformation(Axes axes) throws DegenerateMatrixException {
343         Double[] bounds = axes.getDisplayedBounds();
344
345         // Reverse data if needed.
346         Transformation transformation = TransformationFactory.getScaleTransformation(
347                                             axes.getAxes()[0].getReverse() ? 1 : -1,
348                                             axes.getAxes()[1].getReverse() ? 1 : -1,
349                                             axes.getAxes()[2].getReverse() ? 1 : -1
350                                         );
351
352         // Scale data.
353         Transformation scaleTransformation = TransformationFactory.getScaleTransformation(
354                 2.0 / (bounds[1] - bounds[0]),
355                 2.0 / (bounds[3] - bounds[2]),
356                 2.0 / (bounds[5] - bounds[4])
357                                              );
358         transformation = transformation.rightTimes(scaleTransformation);
359
360
361         // Translate data.
362         Transformation translateTransformation = TransformationFactory.getTranslateTransformation(
363                     -(bounds[0] + bounds[1]) / 2.0,
364                     -(bounds[2] + bounds[3]) / 2.0,
365                     -(bounds[4] + bounds[5]) / 2.0
366                 );
367         transformation = transformation.rightTimes(translateTransformation);
368         return transformation;
369     }
370
371
372     /**
373      * Compute box transformation for the given axes.
374      *
375      * The box transformation is applied to the axes box to fit in the canvas.
376      *
377      * @param axes the given {@see Axes}.
378      * @param canvas the current {@see Canvas}.
379      * @param use2dView specifies whether the default 2d view rotation angles must be used (true) or the given Axes' ones (false).
380      * @return box transformation for the given axes.
381      * @throws DegenerateMatrixException if data bounds are incorrect or canvas with or length are zero.
382      */
383     private Transformation computeBoxTransformation(Axes axes, Canvas canvas, boolean use2dView) throws DegenerateMatrixException {
384         Double[] bounds = axes.getDisplayedBounds();
385
386
387         double alpha;
388         double theta;
389
390         double tmpX;
391         double tmpY;
392         double tmpZ;
393
394         Transformation transformation;
395
396         // Set zone aspect ratio.
397         Rectangle2D zone = computeZone(axes);
398         double axesRatio = zone.getWidth() / zone.getHeight();
399         transformation = TransformationFactory.getPreferredAspectRatioTransformation(canvas.getDimension(), axesRatio);
400
401         // Rotate.
402         if (use2dView) {
403             alpha = 0.0;
404             theta = 2 * DEFAULT_THETA;
405         } else {
406             alpha = -axes.getRotationAngles()[0];
407             theta = DEFAULT_THETA + axes.getRotationAngles()[1];
408         }
409
410         Transformation alphaRotation = TransformationFactory.getRotationTransformation(alpha, 1.0, 0.0, 0.0);
411         transformation = transformation.rightTimes(alphaRotation);
412         Transformation thetaRotation = TransformationFactory.getRotationTransformation(theta, 0.0, 0.0, 1.0);
413         transformation = transformation.rightTimes(thetaRotation);
414
415         // If there is no cube scaling, we must take into account the distribution of data.
416         if (!axes.getCubeScaling()) {
417             tmpX = (bounds[1] - bounds[0]);
418             tmpY = (bounds[3] - bounds[2]);
419             tmpZ = (bounds[5] - bounds[4]);
420
421             /**
422              * Here, we should divide the values by their maximum.
423              * But the next operation will automatically.
424              */
425             Transformation cubeScale = TransformationFactory.getScaleTransformation(tmpX, tmpY, tmpZ);
426             transformation = transformation.rightTimes(cubeScale);
427         }
428
429         // Compute bounds of projected data.
430         double[] matrix = transformation.getMatrix();
431         tmpX = 1 / (Math.abs(matrix[0]) + Math.abs(matrix[4]) + Math.abs(matrix[8]));
432         tmpY = 1 / (Math.abs(matrix[1]) + Math.abs(matrix[5]) + Math.abs(matrix[9]));
433         tmpZ = 1 / (Math.abs(matrix[2]) + Math.abs(matrix[6]) + Math.abs(matrix[10]));
434
435         // Scale projected data to fit in the cube.
436         Transformation isoScale;
437         if (axes.getIsoview()) {
438             Double[] axesBounds = axes.getAxesBounds();
439             double minScale = Math.min(tmpX * axesBounds[2], tmpY * axesBounds[3]);
440             isoScale = TransformationFactory.getScaleTransformation(minScale / axesBounds[2], minScale / axesBounds[3], tmpZ);
441         } else {
442             isoScale = TransformationFactory.getScaleTransformation(tmpX, tmpY, tmpZ);
443         }
444         transformation = transformation.leftTimes(isoScale);
445
446         return transformation;
447     }
448
449
450     /**
451      * Computes and sets the reversed bounds of the given Axes.
452      * @param axes the given {@see Axes}.
453      */
454     private void computeReversedBounds(Axes axes) {
455         Double[] currentBounds = axes.getDisplayedBounds();
456
457         /* Reverse */
458         if (axes.getAxes()[0].getReverse()) {
459             reversedBounds[0] = currentBounds[1];
460             reversedBounds[1] = currentBounds[0];
461         } else {
462             reversedBounds[0] = currentBounds[0];
463             reversedBounds[1] = currentBounds[1];
464         }
465
466         if (axes.getAxes()[1].getReverse()) {
467             reversedBounds[2] = currentBounds[3];
468             reversedBounds[3] = currentBounds[2];
469         } else {
470             reversedBounds[2] = currentBounds[2];
471             reversedBounds[3] = currentBounds[3];
472         }
473
474         if (axes.getAxes()[2].getReverse()) {
475             reversedBounds[4] = currentBounds[5];
476             reversedBounds[5] = currentBounds[4];
477         } else {
478             reversedBounds[4] = currentBounds[4];
479             reversedBounds[5] = currentBounds[5];
480         }
481
482         /*
483          * Interval values are set to 1 when bounds are equal to avoid divides by 0
484          * in the object to box coordinates conversion function.
485          */
486         if (reversedBounds[1] == reversedBounds[0]) {
487             reversedBoundsIntervals[0] = 1.0;
488         } else {
489             reversedBoundsIntervals[0] = reversedBounds[1] - reversedBounds[0];
490         }
491
492         if (reversedBounds[3] == reversedBounds[2]) {
493             reversedBoundsIntervals[1] = 1.0;
494         } else {
495             reversedBoundsIntervals[1] = reversedBounds[3] - reversedBounds[2];
496         }
497
498         if (reversedBounds[5] == reversedBounds[4]) {
499             reversedBoundsIntervals[2] = 1.0;
500         } else {
501             reversedBoundsIntervals[2] = reversedBounds[5] - reversedBounds[4];
502         }
503
504     }
505
506     /**
507      * Computes the culling modes respectively corresponding to front and back faces
508      * of the given Axes' child objects as a function of its {X,Y,Z} reverse properties.
509      * It must be called by draw prior to rendering any child object.
510      * @param axes the given {@see Axes}.
511      */
512     private void computeFaceCullingModes(Axes axes) {
513         if (axes.getAxes()[0].getReverse() ^ axes.getAxes()[1].getReverse() ^ axes.getAxes()[2].getReverse()) {
514             /* Front: CW */
515             this.frontFaceCullingMode = FaceCullingMode.CW;
516             this.backFaceCullingMode = FaceCullingMode.CCW;
517         } else {
518             /* Front: CCW */
519             this.frontFaceCullingMode = FaceCullingMode.CCW;
520             this.backFaceCullingMode = FaceCullingMode.CW;
521         }
522     }
523
524     /**
525      * Converts a point from object coordinates to box coordinates (used when drawing axis rulers).
526      * @param point the point in object coordinates.
527      * @return the point in box coordinates.
528      */
529     public Vector3d getBoxCoordinates(Vector3d point) {
530         double[] dataCoordinates = new double[3];
531
532         dataCoordinates[0] = 1 - 2.0 * (point.getX() - reversedBounds[0]) / reversedBoundsIntervals[0];
533         dataCoordinates[1] = 1 - 2.0 * (point.getY() - reversedBounds[2]) / reversedBoundsIntervals[1];
534         dataCoordinates[2] = 1 - 2.0 * (point.getZ() - reversedBounds[4]) / reversedBoundsIntervals[2];
535
536         return new Vector3d(dataCoordinates);
537     }
538
539     /**
540      * Converts a point from box coordinates (used when drawing axis rulers) to object coordinates.
541      * @param point the point in box coordinates.
542      * @return the point in object coordinates.
543      */
544     public Vector3d getObjectCoordinates(Vector3d point) {
545         double[] objectCoordinates = new double[3];
546
547         objectCoordinates[0] = 0.5 * (1.0 - point.getX()) * (reversedBounds[1] - reversedBounds[0]) + reversedBounds[0];
548         objectCoordinates[1] = 0.5 * (1.0 - point.getY()) * (reversedBounds[3] - reversedBounds[2]) + reversedBounds[2];
549         objectCoordinates[2] = 0.5 * (1.0 - point.getZ()) * (reversedBounds[5] - reversedBounds[4]) + reversedBounds[4];
550
551         return new Vector3d(objectCoordinates);
552     }
553
554     /**
555      * Computes and return the object to window coordinate projection corresponding to the given Axes object.
556      * @param axes the given Axes.
557      * @param drawingTools the drawing tools.
558      * @param canvas the current canvas.
559      * @param use2dView specifies whether the default 2d view rotation angles must be used (true) or the given Axes' ones (false).
560      * @return the projection
561      */
562     private Transformation computeProjection(Axes axes, DrawingTools drawingTools, Canvas canvas, boolean use2dView) {
563         Transformation projection;
564
565         try {
566             /* Compute the zone projection. */
567             Transformation zoneProjection = computeZoneProjection(axes);
568
569             /* Compute the box transformation. */
570             Transformation transformation = computeBoxTransformation(axes, canvas, use2dView);
571
572             /* Compute the data scale and translate transformation. */
573             Transformation dataTransformation = computeDataTransformation(axes);
574
575             /* Compute the object to window coordinates projection. */
576             projection = zoneProjection.rightTimes(transformation);
577             projection = projection.rightTimes(dataTransformation);
578
579             Transformation windowTrans = drawingTools.getTransformationManager().getWindowTransformation().getInverseTransformation();
580             projection = windowTrans.rightTimes(projection);
581         } catch (DegenerateMatrixException e) {
582             return TransformationFactory.getIdentity();
583         }
584
585         return projection;
586     }
587
588     /**
589      * Returns the current projection from object to window coordinates.
590      * @return the projection.
591      */
592     public Transformation getProjection() {
593         return currentProjection;
594     }
595
596     /**
597      * Returns the current data scale and translate transformation.
598      * @return the data transformation.
599      */
600     public Transformation getDataTransformation() {
601         return currentDataTransformation;
602     }
603
604     /**
605      * Adds the projection from object to window coordinates corresponding to a given Axes object
606      * to the projection map.
607      * @param axesId the identifier of the given Axes.
608      * @param projection the corresponding projection.
609      */
610     public synchronized void addProjection(String axesId, Transformation projection) {
611         projectionMap.put(axesId, projection);
612     }
613
614     /**
615      * Returns the projection from object to window coordinates corresponding
616      * to a given Axes object.
617      * @param id the identifier of the given Axes.
618      * @return the projection.
619      */
620     public Transformation getProjection(String id) {
621         return projectionMap.get(id);
622     }
623
624     /**
625      * Removes the object to window coordinate projection corresponding to a given Axes from
626      * the projection map.
627      * @param axesId the identifier of the given Axes.
628      */
629     public void removeProjection(String axesId) {
630         projectionMap.remove(axesId);
631     }
632
633     /**
634      * Adds the projection from object (in 2d view mode) to window coordinates corresponding to a given Axes object
635      * to the projection map.
636      * @param axesId the identifier of the given Axes.
637      * @param projection the corresponding projection.
638      */
639     public synchronized void addProjection2dView(String axesId, Transformation projection) {
640         projection2dViewMap.put(axesId, projection);
641     }
642
643     /**
644      * Returns the projection from object (in 2d view mode) to window coordinates corresponding
645      * to a given Axes object.
646      * @param id the identifier of the given Axes.
647      * @return the projection.
648      */
649     public Transformation getProjection2dView(String id) {
650         return projection2dViewMap.get(id);
651     }
652
653     public Transformation getSceneProjection(String id) {
654         return sceneProjectionMap.get(id);
655     }
656
657     /**
658      * Removes the object (in 2d view mode) to window coordinate projection corresponding to a given Axes from
659      * the projection map.
660      * @param axesId the identifier of the given Axes.
661      */
662     public void removeProjection2dView(String axesId) {
663         projection2dViewMap.remove(axesId);
664     }
665
666     /**
667      * Updates both the projection from object to window coordinates and the related
668      * object (in 2d view mode) to window coordinates projection for the given Axes object.
669      * @param axes the given Axes.
670      */
671     public static void updateAxesTransformation(Axes axes) {
672         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
673         AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
674
675         Transformation transformation = axesDrawer.getProjection(axes.getIdentifier());
676
677         /* The projection must be updated */
678         if (transformation == null) {
679             Transformation projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), currentVisitor.getCanvas(), false);
680
681             axesDrawer.addProjection(axes.getIdentifier(), projection);
682         }
683
684         Transformation transformation2dView = axesDrawer.getProjection2dView(axes.getIdentifier());
685
686         /* The projection must be updated */
687         if (transformation2dView == null) {
688             Transformation projection2dView = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), currentVisitor.getCanvas(), true);
689
690             axesDrawer.addProjection2dView(axes.getIdentifier(), projection2dView);
691         }
692     }
693
694     /**
695      * Computes and returns the coordinates of a point projected onto the default 2d view plane.
696      * To compute them, the point is projected using the object to window coordinate projection, then
697      * unprojected using the object to window coordinate projection corresponding to the default 2d view
698      * (which uses the default camera rotation angles).
699      * To do: optimize by using the already computed 3d view projection.
700      * @param axes the given Axes.
701      * @param coordinates the object (x,y,z) coordinates to project onto the 2d view plane (3-element array).
702      * @returns the 2d view coordinates (3-element array).
703      */
704     public static double[] compute2dViewCoordinates(Axes axes, double[] coordinates) {
705         DrawerVisitor currentVisitor;
706         AxesDrawer axesDrawer;
707         Transformation projection;
708         Transformation projection2d;
709
710         currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
711
712         Vector3d point = new Vector3d(coordinates);
713
714         if (currentVisitor != null) {
715             axesDrawer = currentVisitor.getAxesDrawer();
716
717             projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), currentVisitor.getCanvas(), false);
718             projection2d = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), currentVisitor.getCanvas(), true);
719
720             point = projection.project(point);
721             point = projection2d.unproject(point);
722         }
723
724         return new double[] {point.getX(), point.getY(), point.getZ()};
725     }
726
727     /**
728      * Computes and returns the pixel coordinates from a point's coordinates expressed in the default
729      * 2d view coordinate frame, using the given Axes. The returned pixel coordinates are expressed
730      * in the AWT's 2d coordinate frame.
731      * @param axes the given Axes.
732      * @param coordinates the 2d view coordinates (3-element array: x, y, z).
733      * @returns the pixel coordinates (2-element array: x, y).
734      */
735     public static double[] computePixelFrom2dViewCoordinates(Axes axes, double[] coordinates) {
736         DrawerVisitor currentVisitor;
737         AxesDrawer axesDrawer;
738
739         double[] coords2dView = new double[] {0.0, 0.0, 0.0};
740
741         currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
742
743         if (currentVisitor != null) {
744             double height;
745
746             axesDrawer = currentVisitor.getAxesDrawer();
747             height = currentVisitor.getCanvas().getHeight();
748
749             Transformation projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
750
751             Vector3d point = new Vector3d(coordinates);
752             point = projection2d.project(point);
753
754             /* Convert the window coordinates to pixel coordinates, only y changes due to the differing y-axis convention */
755             coords2dView[0] = point.getX();
756             coords2dView[1] = height - point.getY();
757         }
758
759         return coords2dView;
760     }
761
762     /**
763      * Computes and returns the coordinates of a point projected onto the default 2d view plane
764      * from its pixel coordinates, using the given Axes. Pixel coordinates are expressed in
765      * the AWT's 2d coordinate frame.
766      * The returned point's z component is set to 0, as we only have x and y as an input.
767      * @param axes the given Axes.
768      * @param coordinates the pixel coordinates (2-element array: x, y).
769      * @return coordinates the 2d view coordinates (3-element array: x, y, z).
770      */
771     public static double[] compute2dViewFromPixelCoordinates(Axes axes, double[] coordinates) {
772         DrawerVisitor currentVisitor;
773         AxesDrawer axesDrawer;
774
775         double[] coords2dView = new double[] {0.0, 0.0, 0.0};
776
777         currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
778
779         if (currentVisitor != null) {
780             double height;
781
782             axesDrawer = currentVisitor.getAxesDrawer();
783             height = currentVisitor.getCanvas().getHeight();
784
785             /* Convert the pixel coordinates to window coordinates, only y changes due to the differing y-axis convention */
786             Vector3d point = new Vector3d(coordinates[0], height - coordinates[1], 0.0);
787
788             Transformation projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
789             point = projection2d.unproject(point);
790             coords2dView = point.getData();
791         }
792
793         return coords2dView;
794     }
795
796     /**
797      * Un-project the given point from AWT coordinate to given axes coordinate.
798      * @param axes returned coordinate are relative to this axes.
799      * @param point un-projected point.
800      * @return The un-projected point.
801      */
802     public static Vector3d unProject(Axes axes, Vector3d point) {
803         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
804
805         if (currentVisitor != null) {
806             AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
807             double height = currentVisitor.getCanvas().getHeight() - 1;
808
809             Transformation projection2d = axesDrawer.getProjection(axes.getIdentifier());
810             return projection2d.unproject(new Vector3d(point.getX(), height - point.getY(), point.getZ()));
811         } else {
812             return new Vector3d(0, 0, 0);
813         }
814     }
815
816     /**
817      * Computes and returns the viewing area corresponding to the given Axes object.
818      * The viewing area is described by the (x, y) coordinates of the Axes box's upper-left corner
819      * and the Axes box's dimensions (width and height), all values are in pixel.
820      * The 2d coordinate frame in which the area is expressed uses the AWT convention:
821      * upper-left window corner at (0, 0), y-axis pointing downwards).
822      * @param axes the given Axes.
823      * @return the Axes' viewing area (4-element array: x, y, width, height).
824      */
825     public static double[] getViewingArea(Axes axes) {
826         DrawerVisitor currentVisitor;
827
828         double[] viewingArea = new double[] {0.0, 0.0, 0.0, 0.0};
829
830         currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
831
832         if (currentVisitor != null) {
833             double width;
834             double height;
835             double upperLeftY;
836
837             AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
838
839             width = (double) currentVisitor.getCanvas().getWidth();
840             height = (double) currentVisitor.getCanvas().getHeight();
841
842             Rectangle2D axesZone = axesDrawer.computeZone(axes);
843
844             /* Compute the upper-left point's y coordinate */
845             upperLeftY = axesZone.getY() + axesZone.getHeight() * 2.0;
846
847             /* Convert from normalized coordinates to 2D pixel coordinates */
848             viewingArea[0] = (axesZone.getX() + 1.0) * 0.5 * width;
849             viewingArea[1] = (1.0 - upperLeftY) * 0.5 * height;
850             viewingArea[2] = axesZone.getWidth() * width;
851             viewingArea[3] = axesZone.getHeight() * height;
852         }
853
854         return viewingArea;
855     }
856
857     /**
858      * Returns the culling mode corresponding to front faces.
859      * @return the front face culling mode.
860      */
861     public FaceCullingMode getFrontFaceCullingMode() {
862         return this.frontFaceCullingMode;
863     }
864
865     /**
866      * Returns the culling mode corresponding to back faces.
867      * @return the back face culling mode.
868      */
869     public FaceCullingMode getBackFaceCullingMode() {
870         return this.backFaceCullingMode;
871     }
872
873     /**
874      * Enables clipping for the given {@link ClippableProperty}, which describes
875      * the clipping state of a clippable graphic object.
876      * Depending on the object's clip state property, clipping can be either
877      * disabled (OFF), performed against the parent Axes' box planes (CLIPGRF),
878      * or performed against the planes defined by the object's clip box.
879      * To do: find a better way to compute the clipping planes' offsets as the current one
880      * may lead to problems when the interval between the min and max bounds is too small.
881      * @param parentAxes the clipped object's parent Axes.
882      * @param clipProperty the clipping property of a clippable object.
883      */
884     public void enableClipping(Axes parentAxes, ClippableProperty clipProperty) {
885         DrawingTools drawingTools = visitor.getDrawingTools();
886
887         if (clipProperty.getClipState() != ClipStateType.OFF) {
888             int numPlanes = 0;
889             Vector4d[] equations = null;
890
891             /* Stores the (xmin,xmax) ,(ymin,ymax) and (zmin,zmax) clipping bounds */
892             double[] clipBounds = new double[6];
893
894             /* The offsets used for the x, y and z planes in order to avoid strict clipping */
895             double[] offsets = new double[3];
896
897             if (clipProperty.getClipState() == ClipStateType.CLIPGRF) {
898                 /*
899                  * All the clipping planes are set as clipping is performed
900                  * against the Axes box planes.
901                  */
902                 numPlanes = 6;
903                 Double[] bounds = parentAxes.getDisplayedBounds();
904
905                 for (int i = 0; i < numPlanes; i++) {
906                     clipBounds[i] = bounds[i];
907                 }
908
909                 offsets[0] = CLIPPING_EPSILON * (bounds[1] - bounds[0]);
910                 offsets[1] = CLIPPING_EPSILON * (bounds[3] - bounds[2]);
911                 offsets[2] = CLIPPING_EPSILON * (bounds[5] - bounds[4]);
912             } else if (clipProperty.getClipState() == ClipStateType.ON) {
913                 /*
914                  * The clip box property defines values only for the x and y axes,
915                  * we therefore set only the x and y clipping planes.
916                  */
917                 numPlanes = 4;
918                 Double[] clipBox = clipProperty.getClipBox();
919
920                 /* The clip box stores the upper-left point coordinates. */
921                 clipBounds[0] = clipBox[0];
922                 clipBounds[1] = clipBox[0] + clipBox[2];
923                 clipBounds[2] = clipBox[1] - clipBox[3];
924                 clipBounds[3] = clipBox[1];
925
926                 Double[] bounds = parentAxes.getMaximalDisplayedBounds();
927
928                 /*
929                  * The logarithmic scale must be applied to clip bounds values.
930                  * If any of them are invalid, we set them to the displayed
931                  * left bounds (xmin or ymin).
932                  */
933                 if (parentAxes.getXAxisLogFlag()) {
934                     if (clipBounds[0] <= 0.0) {
935                         clipBounds[0] = bounds[0];
936                     } else {
937                         clipBounds[0] = Math.log10(clipBounds[0]);
938                     }
939
940                     if (clipBounds[1] <= 0.0) {
941                         clipBounds[1] = bounds[0];
942                     } else {
943                         clipBounds[1] = Math.log10(clipBounds[1]);
944                     }
945                 }
946
947                 if (parentAxes.getYAxisLogFlag()) {
948                     if (clipBounds[2] <= 0.0) {
949                         clipBounds[2] = bounds[2];
950                     } else {
951                         clipBounds[2] = Math.log10(clipBounds[2]);
952                     }
953
954                     if (clipBounds[3] <= 0.0) {
955                         clipBounds[3] = bounds[2];
956                     } else {
957                         clipBounds[3] = Math.log10(clipBounds[3]);
958                     }
959                 }
960
961                 offsets[0] = CLIPPING_EPSILON * (clipBounds[1] - clipBounds[0]);
962                 offsets[1] = CLIPPING_EPSILON * (clipBounds[3] - clipBounds[2]);
963             }
964
965             equations = new Vector4d[numPlanes];
966
967             equations[0] = new Vector4d(+1, 0, 0, -clipBounds[0] + offsets[0]);
968             equations[1] = new Vector4d(-1, 0, 0, +clipBounds[1] + offsets[0]);
969             equations[2] = new Vector4d(0, +1, 0, -clipBounds[2] + offsets[1]);
970             equations[3] = new Vector4d(0, -1, 0, +clipBounds[3] + offsets[1]);
971
972             /* If clipping is performed against the Axes box, the z plane equations must be initialized. */
973             if (numPlanes == 6) {
974                 equations[4] = new Vector4d(0, 0, +1, -clipBounds[4] + offsets[2]);
975                 equations[5] = new Vector4d(0, 0, -1, +clipBounds[5] + offsets[2]);
976             }
977
978             Transformation currentTransformation = drawingTools.getTransformationManager().getTransformation();
979             for (int i = 0 ; i < numPlanes; i++) {
980                 ClippingPlane plane = drawingTools.getClippingManager().getClippingPlane(i);
981                 plane.setTransformation(currentTransformation);
982                 plane.setEquation(equations[i]);
983                 plane.setEnable(true);
984             }
985         }
986     }
987
988     /**
989      * Disables clipping for the given {@link ClippableProperty}.
990      * @param clipProperty the clip property for which clipping is disabled.
991      */
992     public void disableClipping(ClippableProperty clipProperty) {
993         int numPlanes = 0;
994
995         if (clipProperty.getClipState() == ClipStateType.CLIPGRF) {
996             numPlanes = 6;
997         } else if (clipProperty.getClipState() == ClipStateType.ON) {
998             numPlanes = 4;
999         }
1000
1001         for (int i = 0 ; i < numPlanes; i++) {
1002             ClippingPlane plane = visitor.getDrawingTools().getClippingManager().getClippingPlane(i);
1003             plane.setEnable(false);
1004         }
1005
1006         visitor.getDrawingTools().getClippingManager().disableClipping();
1007     }
1008
1009     /**
1010      * Returns the x-axis label positioner.
1011      * @return the x-axis label positioner.
1012      */
1013     public AxisLabelPositioner getXAxisLabelPositioner() {
1014         return this.xAxisLabelPositioner;
1015     }
1016
1017     /**
1018      * Returns the y-axis label positioner.
1019      * @return the y-axis label positioner.
1020      */
1021     public AxisLabelPositioner getYAxisLabelPositioner() {
1022         return this.yAxisLabelPositioner;
1023     }
1024
1025     /**
1026      * Returns the z-axis label positioner.
1027      * @return the z-axis label positioner.
1028      */
1029     public AxisLabelPositioner getZAxisLabelPositioner() {
1030         return this.zAxisLabelPositioner;
1031     }
1032
1033     /**
1034      * Returns the title positioner.
1035      * @return the title positioner.
1036      */
1037     public LabelPositioner getTitlePositioner() {
1038         return this.titlePositioner;
1039     }
1040
1041     public void disposeAll() {
1042         this.rulerDrawer.disposeAll();
1043         this.projectionMap.clear();
1044     }
1045
1046     public void update(String id, String property) {
1047         this.rulerDrawer.update(id, property);
1048     }
1049
1050     public void dispose(String id) {
1051         this.rulerDrawer.dispose(id);
1052     }
1053 }