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