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
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.1-en.txt
13 package org.scilab.modules.renderer.JoGLView.axes;
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.graphicController.GraphicController;
30 import org.scilab.modules.graphic_objects.graphicObject.GraphicObject;
31 import org.scilab.modules.graphic_objects.graphicObject.ClippableProperty;
32 import org.scilab.modules.graphic_objects.graphicObject.ClippableProperty.ClipStateType;
33 import org.scilab.modules.graphic_objects.figure.ColorMap;
34 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
35 import org.scilab.modules.renderer.JoGLView.axes.ruler.AxesRulerDrawer;
36 import org.scilab.modules.renderer.JoGLView.label.AxisLabelPositioner;
37 import org.scilab.modules.renderer.JoGLView.label.LabelManager;
38 import org.scilab.modules.renderer.JoGLView.label.LabelPositioner;
39 import org.scilab.modules.renderer.JoGLView.label.TitlePositioner;
40 import org.scilab.modules.renderer.JoGLView.label.YAxisLabelPositioner;
41 import org.scilab.modules.renderer.JoGLView.util.ColorFactory;
42 import org.scilab.modules.renderer.JoGLView.util.ScaleUtils;
44 import java.awt.Dimension;
45 import java.awt.geom.Rectangle2D;
46 import java.util.HashMap;
51 * AxesDrawer are used by {@see DrawerVisitor} to draw {@see Axes}.
53 * @author Pierre Lando
55 public class AxesDrawer {
56 private static final double DEFAULT_THETA = 270.0;
57 private static final Line.LineType HIDDEN_BORDER_PATTERN = Line.LineType.DASH;
59 /** An epsilon value used to move the clipping planes in order to prevent strict clipping. */
60 private static final double CLIPPING_EPSILON = 1e-5;
62 private final DrawerVisitor visitor;
63 private final Geometries geometries;
65 private final AxesRulerDrawer rulerDrawer;
67 /** The front face culling mode. */
68 private FaceCullingMode frontFaceCullingMode;
70 /** The back face culling mode. */
71 private FaceCullingMode backFaceCullingMode;
73 /** The label manager. */
74 private final LabelManager labelManager;
76 /** The x-axis label positioner. */
77 private final Map<String, AxisLabelPositioner> xAxisLabelPositioner = new HashMap<String, AxisLabelPositioner>();
79 /** The y-axis label positioner. */
80 private final Map<String, AxisLabelPositioner> yAxisLabelPositioner = new HashMap<String, AxisLabelPositioner>();
82 /** The z-axis label positioner. */
83 private final Map<String, AxisLabelPositioner> zAxisLabelPositioner = new HashMap<String, AxisLabelPositioner>();
85 /** The title positioner. */
86 private final Map<String, TitlePositioner> titlePositioner = new HashMap<String, TitlePositioner>();
89 * The current reversed bounds. Used by the functions converting
90 * between object and box coordinates.
92 private double[] reversedBounds;
94 /** The current reversed bounds intervals. */
95 private double[] reversedBoundsIntervals;
97 /** The current projection (from object to window coordinates) used when drawing objects. */
98 private Transformation currentProjection;
100 /** The current data scale and translate transformation. */
101 private Transformation currentDataTransformation;
103 /** The set of object to window coordinate projections associated to all the Axes drawn by this drawer. */
104 private final Map<String, Transformation> projectionMap = new HashMap<String, Transformation>();
106 /** The set of object (in 2d view mode) to window coordinate projections associated to all the Axes drawn by this drawer. */
107 private final Map<String, Transformation> projection2dViewMap = new HashMap<String, Transformation>();
109 /** This is a __MAP__ */
110 private final Map<String, Transformation> sceneProjectionMap = new HashMap<String, Transformation>();
113 * Default constructor.
114 * @param visitor the parent {@see DrawerVisitor}.
116 public AxesDrawer(DrawerVisitor visitor) {
117 this.visitor = visitor;
118 this.labelManager = visitor.getLabelManager();
119 this.geometries = new Geometries(visitor.getCanvas());
120 this.rulerDrawer = new AxesRulerDrawer(visitor.getCanvas());
122 reversedBounds = new double[6];
123 reversedBoundsIntervals = new double[3];
126 public Transformation getCurrentProjection(Axes axes) throws DegenerateMatrixException {
127 DrawingTools drawingTools = visitor.getDrawingTools();
128 Integer[] size = visitor.getFigure().getAxesSize();
129 Transformation zoneProjection = computeZoneProjection(axes);
130 Transformation transformation = computeBoxTransformation(axes, new Dimension(size[0], size[1]), false);
131 Transformation dataTransformation = computeDataTransformation(axes);
132 Transformation windowTrans = drawingTools.getTransformationManager().getWindowTransformation().getInverseTransformation();
133 Transformation current = zoneProjection.rightTimes(transformation);
134 current = current.rightTimes(dataTransformation);
136 return windowTrans.rightTimes(current);
140 * Compute the graduations on the axes
141 * @param axes the axes
143 public void computeRulers(Axes axes) {
144 DrawingTools drawingTools = visitor.getDrawingTools();
145 ColorMap colorMap = visitor.getColorMap();
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, new Dimension(size[0], size[1]), false);
153 Transformation canvasTrans = windowTrans.rightTimes(zoneProjection).rightTimes(transformation);
155 rulerDrawer.computeRulers(axes, this, colorMap, drawingTools, transformation, canvasTrans);
156 } catch (DegenerateMatrixException e) {
162 * Draw the given {@see Axes}.
163 * @param axes {@see Axes} to draw.
164 * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
166 public void draw(Axes axes) throws SciRendererException {
167 DrawingTools drawingTools = visitor.getDrawingTools();
168 Integer[] size = visitor.getFigure().getAxesSize();
169 Dimension canvasDimension = new Dimension(size[0], size[1]);
170 ColorMap colorMap = visitor.getColorMap();
171 TransformationStack modelViewStack = drawingTools.getTransformationManager().getModelViewStack();
172 TransformationStack projectionStack = drawingTools.getTransformationManager().getProjectionStack();
175 Transformation zoneProjection = computeZoneProjection(axes);
176 projectionStack.push(zoneProjection);
178 // Set box projection.
179 Transformation transformation = computeBoxTransformation(axes, canvasDimension, false);
180 modelViewStack.pushRightMultiply(transformation);
182 /* Compute the data scale and translate transformation. */
183 Transformation dataTransformation = computeDataTransformation(axes);
185 currentDataTransformation = dataTransformation;
187 /* Compute the object to window coordinates projection. */
188 currentProjection = zoneProjection.rightTimes(transformation);
189 currentProjection = currentProjection.rightTimes(dataTransformation);
191 sceneProjectionMap.put(axes.getIdentifier(), currentProjection);
193 Transformation windowTrans = drawingTools.getTransformationManager().getInverseWindowTransformation();
194 currentProjection = windowTrans.rightTimes(currentProjection);
196 /* Update the projection maps with the resulting projections. */
197 addProjection(axes.getIdentifier(), currentProjection);
199 /* 2d view projection, to do: optimize computation */
200 if ((axes.getRotationAngles()[0] != 0 || axes.getRotationAngles()[1] != DEFAULT_THETA)) {
201 Transformation transformation2dView = computeBoxTransformation(axes, canvasDimension, true);
202 currentProjection = zoneProjection.rightTimes(transformation2dView);
203 currentProjection = currentProjection.rightTimes(dataTransformation);
204 currentProjection = windowTrans.rightTimes(currentProjection);
207 addProjection2dView(axes.getIdentifier(), currentProjection);
210 * Draw the axes background.
212 drawBackground(axes, drawingTools, colorMap);
215 * Mirror the transformation such that the corner with the maximum Z value was (-1, -1, -1).
218 Transformation cubeOrientation = computeCubeMirroring(transformation);
219 modelViewStack.pushRightMultiply(cubeOrientation);
220 drawBox(axes, drawingTools, colorMap);
221 modelViewStack.pop();
223 // Ruler are drawn in box coordinate.
224 rulerDrawer.drawRuler(axes, this, colorMap, drawingTools);
226 /* Compute reversed bounds. */
227 computeReversedBounds(axes);
230 * Scale and translate for data fitting.
233 modelViewStack.pushRightMultiply(dataTransformation);
235 /* Compute the front and back culling modes */
236 computeFaceCullingModes(axes);
238 visitor.askAcceptVisitor(axes.getChildren());
239 modelViewStack.pop();
241 // Reset transformation stacks.
242 modelViewStack.pop();
243 projectionStack.pop();
247 * Draw the axes background.
248 * @param axes the {@see Axes}
249 * @param drawingTools the {@see DrawingTools} to use.
250 * @param colorMap the current {@see ColorMap}
251 * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
253 private void drawBackground(Axes axes, DrawingTools drawingTools, ColorMap colorMap) throws SciRendererException {
254 if (axes.getFilled()) {
255 Appearance appearance = new Appearance();
256 appearance.setFillColor(ColorFactory.createColor(colorMap, axes.getBackground()));
257 drawingTools.draw(geometries.getCubeGeometry(), appearance);
258 drawingTools.clearDepthBuffer();
263 * Draw the box of the given {@see Axes}
264 * @param axes the given {@see Axes}.
265 * @param drawingTools the {@see DrawingTools} to use.
266 * @param colorMap the current {@see ColorMap}.
267 * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
269 private void drawBox(Axes axes, DrawingTools drawingTools, ColorMap colorMap) throws SciRendererException {
270 Box.BoxType boxed = axes.getBox().getBox();
271 if (boxed != Box.BoxType.OFF) {
272 Appearance appearance = new Appearance();
275 * Draw hidden part of box.
277 if (!visitor.is2DView()) {
278 appearance.setLineColor(ColorFactory.createColor(colorMap, axes.getHiddenAxisColor()));
279 appearance.setLineWidth(axes.getLineThickness().floatValue());
280 appearance.setLinePattern(HIDDEN_BORDER_PATTERN.asPattern());
281 drawingTools.draw(geometries.getHiddenBoxBorderGeometry(), appearance);
284 if (boxed != Box.BoxType.HIDDEN_AXES) {
289 appearance.setLineColor(ColorFactory.createColor(colorMap, axes.getLineColor()));
290 appearance.setLineWidth(axes.getLineThickness().floatValue());
291 appearance.setLinePattern(axes.getLine().getLineStyle().asPattern());
292 drawingTools.draw(geometries.getBoxBorderGeometry(), appearance);
295 if (boxed != Box.BoxType.BACK_HALF) {
297 * Draw front part of box.
299 drawingTools.draw(geometries.getFrontBoxBorderGeometry(), appearance);
306 * Compute the mirroring matrix needed to have the [-1; -1; -1] point projected with the maximal Z value.
307 * @param transformation the current transformation.
308 * @return the mirroring matrix needed to have the [-1; -1; -1] point projected with the maximal Z value.
310 private Transformation computeCubeMirroring(Transformation transformation) {
311 double[] matrix = transformation.getMatrix();
313 return TransformationFactory.getScaleTransformation(
314 matrix[2] < 0 ? 1 : -1,
315 matrix[6] < 0 ? 1 : -1,
316 matrix[10] < 0 ? 1 : -1
318 } catch (DegenerateMatrixException e) {
319 // Should never happen.
320 return TransformationFactory.getIdentity();
325 * Compute zone where the axes is draw. In normalized window coordinate.
326 * @param axes the given {@see axes}.
327 * @return the zone where the axes is draw.
329 private Rectangle2D computeZone(Axes axes) {
330 Double[] axesBounds = axes.getAxesBounds();
331 Double[] margins = axes.getMargins();
334 double x = (axesBounds[0] + axesBounds[2] * margins[0]) * 2 - 1;
335 double y = (1.0 - axesBounds[1] - axesBounds[3] * (1.0 - margins[3])) * 2 - 1;
336 double w = (1 - margins[0] - margins[1]) * axesBounds[2];
337 double h = (1 - margins[2] - margins[3]) * axesBounds[3];
339 if (axes.getIsoview()) {
340 double minSize = Math.min(w, h);
347 return new Rectangle2D.Double(x, y, w, h);
351 * Compute the projection for the given axes.
352 * @param axes the given axes.
353 * @return the projection matrix.
354 * @throws DegenerateMatrixException if axes represent a nul area.
356 private Transformation computeZoneProjection(Axes axes) throws DegenerateMatrixException {
357 Rectangle2D zone = computeZone(axes);
358 Transformation zoneTranslation = TransformationFactory.getTranslateTransformation(zone.getMaxX(), zone.getMaxY(), 0);
360 // We scale by 0.5 in Z to allow ruler to be drawn.
361 Transformation zoneScale = TransformationFactory.getScaleTransformation(zone.getWidth(), zone.getHeight(), .5);
363 return zoneTranslation.rightTimes(zoneScale);
367 * Compute data transformation for the given {@see Axes}.
369 * The data transformation is applied to data to fit in axes box.
371 * @param axes the given {@see Axes}
372 * @return data transformation.
373 * @throws DegenerateMatrixException if data bounds are not corrects.
375 private Transformation computeDataTransformation(Axes axes) throws DegenerateMatrixException {
376 // Reverse data if needed.
377 Transformation transformation = TransformationFactory.getScaleTransformation(
378 axes.getAxes()[0].getReverse() ? 1 : -1,
379 axes.getAxes()[1].getReverse() ? 1 : -1,
380 axes.getAxes()[2].getReverse() ? 1 : -1
383 if (axes.getZoomEnabled()) {
384 Double[] bounds = axes.getCorrectedBounds();
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])
392 transformation = transformation.rightTimes(scaleTransformation);
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
401 transformation = transformation.rightTimes(translateTransformation);
403 return transformation;
406 return transformation;
411 * Compute box transformation for the given axes.
413 * The box transformation is applied to the axes box to fit in the canvas.
415 * @param axes the given {@see Axes}.
416 * @param canvasDimension the current canvas {@see Canvas}.
417 * @param use2dView specifies whether the default 2d view rotation angles must be used (true) or the given Axes' ones (false).
418 * @return box transformation for the given axes.
419 * @throws DegenerateMatrixException if data bounds are incorrect or canvas with or length are zero.
421 private Transformation computeBoxTransformation(Axes axes, Dimension canvasDimension, boolean use2dView) throws DegenerateMatrixException {
422 Double[] bounds = axes.getDisplayedBounds();
429 Transformation transformation;
431 // Set zone aspect ratio.
432 Rectangle2D zone = computeZone(axes);
433 double axesRatio = zone.getWidth() / zone.getHeight();
434 transformation = TransformationFactory.getPreferredAspectRatioTransformation(canvasDimension, axesRatio);
438 theta = 2 * DEFAULT_THETA;
440 double alpha = -axes.getRotationAngles()[0];
441 theta = DEFAULT_THETA + axes.getRotationAngles()[1];
443 Transformation alphaRotation = TransformationFactory.getRotationTransformation(alpha, 1.0, 0.0, 0.0);
444 transformation = transformation.rightTimes(alphaRotation);
448 Transformation thetaRotation = TransformationFactory.getRotationTransformation(theta, 0.0, 0.0, 1.0);
449 transformation = transformation.rightTimes(thetaRotation);
451 // If there is no cube scaling, we must take into account the distribution of data.
452 if (!axes.getCubeScaling()) {
453 tmpX = (bounds[1] - bounds[0]);
454 tmpY = (bounds[3] - bounds[2]);
455 tmpZ = (bounds[5] - bounds[4]);
458 * Here, we should divide the values by their maximum.
459 * But the next operation will automatically.
461 if (tmpX != 1 || tmpY != 1 || tmpZ != 1) {
462 Transformation cubeScale = TransformationFactory.getScaleTransformation(tmpX, tmpY, tmpZ);
463 transformation = transformation.rightTimes(cubeScale);
467 // Compute bounds of projected data.
468 double[] matrix = transformation.getMatrix();
469 tmpX = 1 / (Math.abs(matrix[0]) + Math.abs(matrix[4]) + Math.abs(matrix[8]));
470 tmpY = 1 / (Math.abs(matrix[1]) + Math.abs(matrix[5]) + Math.abs(matrix[9]));
471 tmpZ = 1 / (Math.abs(matrix[2]) + Math.abs(matrix[6]) + Math.abs(matrix[10]));
473 // Scale projected data to fit in the cube.
474 Transformation isoScale;
475 if (axes.getIsoview()) {
476 double w = zone.getWidth();
477 double h = zone.getHeight();
478 double minScale = Math.min(tmpX * w, tmpY * h);
479 isoScale = TransformationFactory.getScaleTransformation(minScale / w, minScale / h, tmpZ);
481 isoScale = TransformationFactory.getScaleTransformation(tmpX, tmpY, tmpZ);
483 transformation = transformation.leftTimes(isoScale);
485 return transformation;
490 * Computes and sets the reversed bounds of the given Axes.
491 * @param axes the given {@see Axes}.
493 private void computeReversedBounds(Axes axes) {
494 Double[] currentBounds = axes.getCorrectedBounds();
497 if (axes.getAxes()[0].getReverse()) {
498 reversedBounds[0] = currentBounds[1];
499 reversedBounds[1] = currentBounds[0];
501 reversedBounds[0] = currentBounds[0];
502 reversedBounds[1] = currentBounds[1];
505 if (axes.getAxes()[1].getReverse()) {
506 reversedBounds[2] = currentBounds[3];
507 reversedBounds[3] = currentBounds[2];
509 reversedBounds[2] = currentBounds[2];
510 reversedBounds[3] = currentBounds[3];
513 if (axes.getAxes()[2].getReverse()) {
514 reversedBounds[4] = currentBounds[5];
515 reversedBounds[5] = currentBounds[4];
517 reversedBounds[4] = currentBounds[4];
518 reversedBounds[5] = currentBounds[5];
522 * Interval values are set to 1 when bounds are equal to avoid divides by 0
523 * in the object to box coordinates conversion function.
525 if (reversedBounds[1] == reversedBounds[0]) {
526 reversedBoundsIntervals[0] = 1.0;
528 reversedBoundsIntervals[0] = reversedBounds[1] - reversedBounds[0];
531 if (reversedBounds[3] == reversedBounds[2]) {
532 reversedBoundsIntervals[1] = 1.0;
534 reversedBoundsIntervals[1] = reversedBounds[3] - reversedBounds[2];
537 if (reversedBounds[5] == reversedBounds[4]) {
538 reversedBoundsIntervals[2] = 1.0;
540 reversedBoundsIntervals[2] = reversedBounds[5] - reversedBounds[4];
546 * Computes the culling modes respectively corresponding to front and back faces
547 * of the given Axes' child objects as a function of its {X,Y,Z} reverse properties.
548 * It must be called by draw prior to rendering any child object.
549 * @param axes the given {@see Axes}.
551 private void computeFaceCullingModes(Axes axes) {
552 if (axes.getAxes()[0].getReverse() ^ axes.getAxes()[1].getReverse() ^ axes.getAxes()[2].getReverse()) {
554 this.frontFaceCullingMode = FaceCullingMode.CW;
555 this.backFaceCullingMode = FaceCullingMode.CCW;
558 this.frontFaceCullingMode = FaceCullingMode.CCW;
559 this.backFaceCullingMode = FaceCullingMode.CW;
564 * Converts a point from object coordinates to box coordinates (used when drawing axis rulers).
565 * @param point the point in object coordinates.
566 * @return the point in box coordinates.
568 public Vector3d getBoxCoordinates(Vector3d point) {
569 double[] dataCoordinates = new double[3];
571 dataCoordinates[0] = 1 - 2.0 * (point.getX() - reversedBounds[0]) / reversedBoundsIntervals[0];
572 dataCoordinates[1] = 1 - 2.0 * (point.getY() - reversedBounds[2]) / reversedBoundsIntervals[1];
573 dataCoordinates[2] = 1 - 2.0 * (point.getZ() - reversedBounds[4]) / reversedBoundsIntervals[2];
575 return new Vector3d(dataCoordinates);
579 * Converts a point from box coordinates (used when drawing axis rulers) to object coordinates.
580 * @param point the point in box coordinates.
581 * @return the point in object coordinates.
583 public Vector3d getObjectCoordinates(Vector3d point) {
584 double[] objectCoordinates = new double[3];
586 objectCoordinates[0] = 0.5 * (1.0 - point.getX()) * (reversedBounds[1] - reversedBounds[0]) + reversedBounds[0];
587 objectCoordinates[1] = 0.5 * (1.0 - point.getY()) * (reversedBounds[3] - reversedBounds[2]) + reversedBounds[2];
588 objectCoordinates[2] = 0.5 * (1.0 - point.getZ()) * (reversedBounds[5] - reversedBounds[4]) + reversedBounds[4];
590 return new Vector3d(objectCoordinates);
594 * Computes and return the object to window coordinate projection corresponding to the given Axes object.
595 * @param axes the given Axes.
596 * @param drawingTools the drawing tools.
597 * @param canvasDimension the current canvas dimension.
598 * @param use2dView specifies whether the default 2d view rotation angles must be used (true) or the given Axes' ones (false).
599 * @return the projection
601 private Transformation computeProjection(Axes axes, DrawingTools drawingTools, Dimension canvasDimension, boolean use2dView) {
602 Transformation projection;
605 /* Compute the zone projection. */
606 Transformation zoneProjection = computeZoneProjection(axes);
608 /* Compute the box transformation. */
609 Transformation transformation = computeBoxTransformation(axes, canvasDimension, use2dView);
611 /* Compute the data scale and translate transformation. */
612 Transformation dataTransformation = computeDataTransformation(axes);
614 /* Compute the object to window coordinates projection. */
615 projection = zoneProjection.rightTimes(transformation);
616 projection = projection.rightTimes(dataTransformation);
618 Transformation windowTrans = drawingTools.getTransformationManager().getWindowTransformation().getInverseTransformation();
619 projection = windowTrans.rightTimes(projection);
620 } catch (DegenerateMatrixException e) {
621 return TransformationFactory.getIdentity();
628 * Returns the current projection from object to window coordinates.
629 * @return the projection.
631 public Transformation getProjection() {
632 return currentProjection;
636 * Returns the current data scale and translate transformation.
637 * @return the data transformation.
639 public Transformation getDataTransformation() {
640 return currentDataTransformation;
644 * Adds the projection from object to window coordinates corresponding to a given Axes object
645 * to the projection map.
646 * @param axesId the identifier of the given Axes.
647 * @param projection the corresponding projection.
649 public synchronized void addProjection(String axesId, Transformation projection) {
650 projectionMap.put(axesId, projection);
654 * Returns the projection from object to window coordinates corresponding
655 * to a given Axes object.
656 * @param id the identifier of the given Axes.
657 * @return the projection.
659 public Transformation getProjection(String id) {
660 return projectionMap.get(id);
664 * Removes the object to window coordinate projection corresponding to a given Axes from
665 * the projection map.
666 * @param axesId the identifier of the given Axes.
668 public void removeProjection(String axesId) {
669 projectionMap.remove(axesId);
673 * Adds the projection from object (in 2d view mode) to window coordinates corresponding to a given Axes object
674 * to the projection map.
675 * @param axesId the identifier of the given Axes.
676 * @param projection the corresponding projection.
678 public synchronized void addProjection2dView(String axesId, Transformation projection) {
679 projection2dViewMap.put(axesId, projection);
683 * Returns the projection from object (in 2d view mode) to window coordinates corresponding
684 * to a given Axes object.
685 * @param id the identifier of the given Axes.
686 * @return the projection.
688 public Transformation getProjection2dView(String id) {
689 return projection2dViewMap.get(id);
692 public Transformation getSceneProjection(String id) {
693 return sceneProjectionMap.get(id);
697 * Removes the object (in 2d view mode) to window coordinate projection corresponding to a given Axes from
698 * the projection map.
699 * @param axesId the identifier of the given Axes.
701 public void removeProjection2dView(String axesId) {
702 projection2dViewMap.remove(axesId);
706 * Updates both the projection from object to window coordinates and the related
707 * object (in 2d view mode) to window coordinates projection for the given Axes object.
708 * @param axes the given Axes.
710 public static void updateAxesTransformation(Axes axes) {
711 DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
712 AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
713 Integer[] size = currentVisitor.getFigure().getAxesSize();
714 Dimension canvasDimension = new Dimension(size[0], size[1]);
716 Transformation transformation = axesDrawer.getProjection(axes.getIdentifier());
718 /* The projection must be updated */
719 if (transformation == null) {
720 Transformation projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, false);
722 axesDrawer.addProjection(axes.getIdentifier(), projection);
725 Transformation transformation2dView = axesDrawer.getProjection2dView(axes.getIdentifier());
727 /* The projection must be updated */
728 if (transformation2dView == null) {
729 Transformation projection2dView = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, true);
731 axesDrawer.addProjection2dView(axes.getIdentifier(), projection2dView);
736 * Computes and returns the coordinates of a point projected onto the default 2d view plane.
737 * To compute them, the point is projected using the object to window coordinate projection, then
738 * unprojected using the object to window coordinate projection corresponding to the default 2d view
739 * (which uses the default camera rotation angles).
740 * To do: optimize by using the already computed 3d view projection.
741 * @param axes the given Axes.
742 * @param coordinates the object (x,y,z) coordinates to project onto the 2d view plane (3-element array).
743 * @returns the 2d view coordinates (3-element array).
745 public static double[] compute2dViewCoordinates(Axes axes, double[] coordinates) {
748 DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
749 AxesDrawer axesDrawer;
750 Transformation projection;
751 Transformation projection2d;
752 double[] coords = coordinates;
754 if (currentVisitor != null) {
755 Integer[] size = currentVisitor.getFigure().getAxesSize();
756 Dimension canvasDimension = new Dimension(size[0], size[1]);
757 double[][] factors = axes.getScaleTranslateFactors();
759 axesDrawer = currentVisitor.getAxesDrawer();
760 coords[0] = coords[0] * factors[0][0] + factors[1][0];
761 coords[1] = coords[1] * factors[0][1] + factors[1][1];
762 coords[2] = coords[2] * factors[0][2] + factors[1][2];
764 projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, false);
765 projection2d = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, true);
766 Vector3d point = new Vector3d(coords);
767 point = projection.project(point);
768 point = projection2d.unproject(point);
770 coords = point.getData();
771 coords[0] = (coords[0] - factors[1][0]) / factors[0][0];
772 coords[1] = (coords[1] - factors[1][1]) / factors[0][1];
773 coords[2] = (coords[2] - factors[1][2]) / factors[0][2];
780 * Computes and returns the pixel coordinates from a point's coordinates expressed in the default
781 * 2d view coordinate frame, using the given Axes. The returned pixel coordinates are expressed
782 * in the AWT's 2d coordinate frame.
783 * @param axes the given Axes.
784 * @param coordinates the 2d view coordinates (3-element array: x, y, z).
785 * @returns the pixel coordinates (2-element array: x, y).
787 public static double[] computePixelFrom2dViewCoordinates(Axes axes, double[] coordinates) {
790 DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
791 AxesDrawer axesDrawer;
792 double[] coords2dView = new double[] {0.0, 0.0, 0.0};
794 if (currentVisitor != null) {
795 Integer[] size = currentVisitor.getFigure().getAxesSize();
796 double height = (double) size[1];
797 double[][] factors = axes.getScaleTranslateFactors();
799 axesDrawer = currentVisitor.getAxesDrawer();
800 coords2dView[0] = coordinates[0] * factors[0][0] + factors[1][0];
801 coords2dView[1] = coordinates[1] * factors[0][1] + factors[1][1];
802 coords2dView[2] = coordinates[2] * factors[0][2] + factors[1][2];
804 Transformation projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
805 if (projection2d == null) {
806 updateAxesTransformation(axes);
807 projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
810 Vector3d point = new Vector3d(coords2dView);
811 point = projection2d.project(point);
813 /* Convert the window coordinates to pixel coordinates, only y changes due to the differing y-axis convention */
814 coords2dView[0] = point.getX();
815 coords2dView[1] = height - point.getY();
823 * Computes and returns the pixel coordinates from a point's coordinates expressed in the current
824 * 3d view coordinate frame, using the given Axes. The returned pixel coordinates are expressed
825 * in the AWT's 2d coordinate frame.
826 * @param axes the given Axes.
827 * @param coordinates the 3d view coordinates (3-element array: x, y, z).
828 * @returns the pixel coordinates (2-element array: x, y).
830 public static double[] computePixelFrom3dCoordinates(Axes axes, double[] coordinates) {
831 DrawerVisitor currentVisitor;
832 AxesDrawer axesDrawer;
833 Transformation projection;
834 Transformation projection2d;
837 currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
838 boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
840 Vector3d point = new Vector3d(coordinates);
841 point = ScaleUtils.applyLogScale(point, logFlags);
843 if (currentVisitor != null) {
844 axesDrawer = currentVisitor.getAxesDrawer();
845 Integer[] size = currentVisitor.getFigure().getAxesSize();
846 Dimension canvasDimension = new Dimension(size[0], size[1]);
847 height = (double) size[1];
849 projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, false);
851 point = projection.project(point);
854 return new double[] {point.getX(), height - point.getY(), point.getZ()};
858 * Computes and returns the coordinates of a point onto the 3d view plane.
859 * To compute them, the point is projected using the object to window coordinate projection, then
860 * unprojected using the object to window coordinate projection corresponding to the 3d view
861 * @param axes the given Axes.
862 * @param coordinates the object (x,y,z) coordinates to project onto the 2d view plane (3-element array).
863 * @returns the 3d view coordinates (3-element array).
865 public static double[] compute3dViewCoordinates(Axes axes, double[] coordinates) {
866 DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
867 AxesDrawer axesDrawer;
868 Transformation projection;
869 Transformation projection2d;
871 Vector3d point = new Vector3d(coordinates);
873 if (currentVisitor != null) {
874 Integer[] size = currentVisitor.getFigure().getAxesSize();
875 Dimension canvasDimension = new Dimension(size[0], size[1]);
877 axesDrawer = currentVisitor.getAxesDrawer();
879 projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, false);
880 projection2d = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), canvasDimension, true);
882 point = projection2d.project(point);
883 point = projection.unproject(point);
886 return new double[] {point.getX(), point.getY(), point.getZ()};
890 * Computes and returns the coordinates of a point projected onto the default 2d view plane
891 * from its pixel coordinates, using the given Axes. Pixel coordinates are expressed in
892 * the AWT's 2d coordinate frame.
893 * The returned point's z component is set to 0, as we only have x and y as an input.
894 * @param axes the given Axes.
895 * @param coordinates the pixel coordinates (2-element array: x, y).
896 * @return coordinates the 2d view coordinates (3-element array: x, y, z).
898 public static double[] compute2dViewFromPixelCoordinates(Axes axes, double[] coordinates) {
899 // used by xgetmouse and by xchange
901 DrawerVisitor currentVisitor;
902 AxesDrawer axesDrawer;
904 double[] coords2dView = new double[] {0.0, 0.0, 0.0};
906 currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
908 if (currentVisitor != null) {
909 Integer[] size = currentVisitor.getFigure().getAxesSize();
910 double height = (double) size[1];
911 double[][] factors = axes.getScaleTranslateFactors();
913 axesDrawer = currentVisitor.getAxesDrawer();
915 /* Convert the pixel coordinates to window coordinates, only y changes due to the differing y-axis convention */
916 Vector3d point = new Vector3d(coordinates[0], height - coordinates[1], 0.0);
918 Transformation projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
919 if (projection2d == null) {
920 updateAxesTransformation(axes);
921 projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
924 point = projection2d.unproject(point);
925 coords2dView = point.getData();
927 coords2dView[0] = (coords2dView[0] - factors[1][0]) / factors[0][0];
928 coords2dView[1] = (coords2dView[1] - factors[1][1]) / factors[0][1];
929 coords2dView[2] = (coords2dView[2] - factors[1][2]) / factors[0][2];
936 * Un-project the given point from AWT coordinate to given axes coordinate.
937 * @param axes returned coordinate are relative to this axes.
938 * @param point un-projected point.
939 * @return The un-projected point.
941 public static Vector3d unProject(Axes axes, Vector3d point) {
942 DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
944 if (currentVisitor != null) {
945 AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
946 double height = currentVisitor.getCanvas().getHeight() - 1;
948 Transformation projection2d = axesDrawer.getProjection(axes.getIdentifier());
949 return projection2d.unproject(new Vector3d(point.getX(), height - point.getY(), point.getZ()));
951 return new Vector3d(0, 0, 0);
956 * Computes and returns the viewing area corresponding to the given Axes object.
957 * The viewing area is described by the (x, y) coordinates of the Axes box's upper-left corner
958 * and the Axes box's dimensions (width and height), all values are in pixel.
959 * The 2d coordinate frame in which the area is expressed uses the AWT convention:
960 * upper-left window corner at (0, 0), y-axis pointing downwards).
961 * @param axes the given Axes.
962 * @return the Axes' viewing area (4-element array: x, y, width, height).
964 public static double[] getViewingArea(Axes axes) {
965 DrawerVisitor currentVisitor;
967 double[] viewingArea = new double[] {0.0, 0.0, 0.0, 0.0};
969 currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
971 if (currentVisitor != null) {
972 Integer[] size = currentVisitor.getFigure().getAxesSize();
973 double width = (double) size[0];
974 double height = (double) size[1];
976 AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
977 Rectangle2D axesZone = axesDrawer.computeZone(axes);
979 /* Compute the upper-left point's y coordinate */
980 upperLeftY = axesZone.getY() + axesZone.getHeight() * 2.0;
982 /* Convert from normalized coordinates to 2D pixel coordinates */
983 viewingArea[0] = (axesZone.getX() + 1.0) * 0.5 * width;
984 viewingArea[1] = (1.0 - upperLeftY) * 0.5 * height;
985 viewingArea[2] = axesZone.getWidth() * width;
986 viewingArea[3] = axesZone.getHeight() * height;
993 * Returns the culling mode corresponding to front faces.
994 * @return the front face culling mode.
996 public FaceCullingMode getFrontFaceCullingMode() {
997 return this.frontFaceCullingMode;
1001 * Returns the culling mode corresponding to back faces.
1002 * @return the back face culling mode.
1004 public FaceCullingMode getBackFaceCullingMode() {
1005 return this.backFaceCullingMode;
1009 * Enables clipping for the given {@link ClippableProperty}, which describes
1010 * the clipping state of a clippable graphic object.
1011 * Depending on the object's clip state property, clipping can be either
1012 * disabled (OFF), performed against the parent Axes' box planes (CLIPGRF),
1013 * or performed against the planes defined by the object's clip box.
1014 * To do: find a better way to compute the clipping planes' offsets as the current one
1015 * may lead to problems when the interval between the min and max bounds is too small.
1016 * @param parentAxes the clipped object's parent Axes.
1017 * @param clipProperty the clipping property of a clippable object.
1019 public void enableClipping(Axes parentAxes, ClippableProperty clipProperty) {
1020 DrawingTools drawingTools = visitor.getDrawingTools();
1022 if (clipProperty.getClipState() != ClipStateType.OFF) {
1024 Vector4d[] equations = null;
1026 /* Stores the (xmin,xmax) ,(ymin,ymax) and (zmin,zmax) clipping bounds */
1027 double[] clipBounds = new double[6];
1029 /* The offsets used for the x, y and z planes in order to avoid strict clipping */
1030 double[] offsets = new double[3];
1032 if (clipProperty.getClipState() == ClipStateType.CLIPGRF) {
1034 * All the clipping planes are set as clipping is performed
1035 * against the Axes box planes.
1038 Double[] bounds = parentAxes.getCorrectedBounds();
1040 for (int i = 0; i < numPlanes; i++) {
1041 clipBounds[i] = bounds[i];
1044 offsets[0] = CLIPPING_EPSILON * (bounds[1] - bounds[0]);
1045 offsets[1] = CLIPPING_EPSILON * (bounds[3] - bounds[2]);
1046 offsets[2] = CLIPPING_EPSILON * (bounds[5] - bounds[4]);
1047 } else if (clipProperty.getClipState() == ClipStateType.ON) {
1049 * The clip box property defines values only for the x and y axes,
1050 * we therefore set only the x and y clipping planes.
1053 Double[] clipBox = clipProperty.getClipBox();
1055 /* The clip box stores the upper-left point coordinates. */
1056 clipBounds[0] = clipBox[0];
1057 clipBounds[1] = clipBox[0] + clipBox[2];
1058 clipBounds[2] = clipBox[1] - clipBox[3];
1059 clipBounds[3] = clipBox[1];
1061 double[][] factors = parentAxes.getScaleTranslateFactors();
1062 Double[] bounds = parentAxes.getMaximalDisplayedBounds();
1065 * The logarithmic scale must be applied to clip bounds values.
1066 * If any of them are invalid, we set them to the displayed
1067 * left bounds (xmin or ymin).
1069 if (parentAxes.getXAxisLogFlag()) {
1070 if (clipBounds[0] <= 0.0) {
1071 clipBounds[0] = bounds[0];
1073 clipBounds[0] = Math.log10(clipBounds[0]);
1076 if (clipBounds[1] <= 0.0) {
1077 clipBounds[1] = bounds[0];
1079 clipBounds[1] = Math.log10(clipBounds[1]);
1083 if (parentAxes.getYAxisLogFlag()) {
1084 if (clipBounds[2] <= 0.0) {
1085 clipBounds[2] = bounds[2];
1087 clipBounds[2] = Math.log10(clipBounds[2]);
1090 if (clipBounds[3] <= 0.0) {
1091 clipBounds[3] = bounds[2];
1093 clipBounds[3] = Math.log10(clipBounds[3]);
1097 clipBounds[0] = clipBounds[0] * factors[0][0] + factors[1][0];
1098 clipBounds[1] = clipBounds[1] * factors[0][0] + factors[1][0];
1099 clipBounds[2] = clipBounds[2] * factors[0][1] + factors[1][1];
1100 clipBounds[3] = clipBounds[3] * factors[0][1] + factors[1][1];
1102 offsets[0] = CLIPPING_EPSILON * (clipBounds[1] - clipBounds[0]);
1103 offsets[1] = CLIPPING_EPSILON * (clipBounds[3] - clipBounds[2]);
1106 equations = new Vector4d[numPlanes];
1108 equations[0] = new Vector4d(+1, 0, 0, -clipBounds[0] + offsets[0]);
1109 equations[1] = new Vector4d(-1, 0, 0, +clipBounds[1] + offsets[0]);
1110 equations[2] = new Vector4d(0, +1, 0, -clipBounds[2] + offsets[1]);
1111 equations[3] = new Vector4d(0, -1, 0, +clipBounds[3] + offsets[1]);
1113 /* If clipping is performed against the Axes box, the z plane equations must be initialized. */
1114 if (numPlanes == 6) {
1115 equations[4] = new Vector4d(0, 0, +1, -clipBounds[4] + offsets[2]);
1116 equations[5] = new Vector4d(0, 0, -1, +clipBounds[5] + offsets[2]);
1119 Transformation currentTransformation = drawingTools.getTransformationManager().getTransformation();
1121 for (int i = 0 ; i < numPlanes; i++) {
1122 ClippingPlane plane = drawingTools.getClippingManager().getClippingPlane(i);
1123 plane.setTransformation(currentTransformation);
1124 plane.setEquation(equations[i]);
1125 plane.setEnable(true);
1131 * Disables clipping for the given {@link ClippableProperty}.
1132 * @param clipProperty the clip property for which clipping is disabled.
1134 public void disableClipping(ClippableProperty clipProperty) {
1137 if (clipProperty.getClipState() == ClipStateType.CLIPGRF) {
1139 } else if (clipProperty.getClipState() == ClipStateType.ON) {
1143 for (int i = 0 ; i < numPlanes; i++) {
1144 ClippingPlane plane = visitor.getDrawingTools().getClippingManager().getClippingPlane(i);
1145 plane.setEnable(false);
1148 visitor.getDrawingTools().getClippingManager().disableClipping();
1152 * Returns the x-axis label positioner.
1153 * @return the x-axis label positioner.
1155 public AxisLabelPositioner getXAxisLabelPositioner(Axes axes) {
1156 AxisLabelPositioner positioner = this.xAxisLabelPositioner.get(axes.getIdentifier());
1157 if (positioner == null) {
1158 positioner = new AxisLabelPositioner();
1159 this.xAxisLabelPositioner.put(axes.getIdentifier(), positioner);
1166 * Returns the y-axis label positioner.
1167 * @return the y-axis label positioner.
1169 public AxisLabelPositioner getYAxisLabelPositioner(Axes axes) {
1170 AxisLabelPositioner positioner = this.yAxisLabelPositioner.get(axes.getIdentifier());
1171 if (positioner == null) {
1172 positioner = new YAxisLabelPositioner();
1173 this.yAxisLabelPositioner.put(axes.getIdentifier(), positioner);
1180 * Returns the z-axis label positioner.
1181 * @return the z-axis label positioner.
1183 public AxisLabelPositioner getZAxisLabelPositioner(Axes axes) {
1184 AxisLabelPositioner positioner = this.zAxisLabelPositioner.get(axes.getIdentifier());
1185 if (positioner == null) {
1186 positioner = new AxisLabelPositioner();
1187 this.zAxisLabelPositioner.put(axes.getIdentifier(), positioner);
1194 * Returns the title positioner.
1195 * @return the title positioner.
1197 public LabelPositioner getTitlePositioner(Axes axes) {
1198 TitlePositioner positioner = this.titlePositioner.get(axes.getIdentifier());
1199 if (positioner == null) {
1200 positioner = new TitlePositioner();
1201 this.titlePositioner.put(axes.getIdentifier(), positioner);
1207 public void disposeAll() {
1208 this.rulerDrawer.disposeAll();
1209 this.projectionMap.clear();
1210 this.projection2dViewMap.clear();
1211 this.sceneProjectionMap.clear();
1212 this.xAxisLabelPositioner.clear();
1213 this.yAxisLabelPositioner.clear();
1214 this.zAxisLabelPositioner.clear();
1215 this.titlePositioner.clear();
1218 public void update(String id, int property) {
1219 if (this.rulerDrawer.update(id, property)) {
1220 GraphicObject object = GraphicController.getController().getObjectFromId(id);
1221 if (object instanceof Axes) {
1222 computeRulers((Axes) object);
1227 public void dispose(String id) {
1228 this.rulerDrawer.dispose(id);
1229 projectionMap.remove(id);
1230 projection2dViewMap.remove(id);
1231 sceneProjectionMap.remove(id);
1232 this.xAxisLabelPositioner.remove(id);
1233 this.yAxisLabelPositioner.remove(id);
1234 this.zAxisLabelPositioner.remove(id);
1235 this.titlePositioner.remove(id);