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