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