2 * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3 * Copyright (C) 2009-2010 - DIGITEO - Pierre Lando
5 * This file must be used under the terms of the CeCILL.
6 * This source file is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at
9 * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
12 package org.scilab.modules.renderer.JoGLView.axes;
14 import org.scilab.forge.scirenderer.Canvas;
15 import org.scilab.forge.scirenderer.DrawingTools;
16 import org.scilab.forge.scirenderer.SciRendererException;
17 import org.scilab.forge.scirenderer.clipping.ClippingPlane;
18 import org.scilab.forge.scirenderer.shapes.appearance.Appearance;
19 import org.scilab.forge.scirenderer.shapes.geometry.Geometry.FaceCullingMode;
20 import org.scilab.forge.scirenderer.tranformations.DegenerateMatrixException;
21 import org.scilab.forge.scirenderer.tranformations.Transformation;
22 import org.scilab.forge.scirenderer.tranformations.TransformationFactory;
23 import org.scilab.forge.scirenderer.tranformations.TransformationStack;
24 import org.scilab.forge.scirenderer.tranformations.Vector3d;
25 import org.scilab.forge.scirenderer.tranformations.Vector4d;
26 import org.scilab.modules.graphic_objects.axes.Axes;
27 import org.scilab.modules.graphic_objects.axes.Box;
28 import org.scilab.modules.graphic_objects.contouredObject.Line;
29 import org.scilab.modules.graphic_objects.graphicObject.ClippableProperty;
30 import org.scilab.modules.graphic_objects.graphicObject.ClippableProperty.ClipStateType;
31 import org.scilab.modules.graphic_objects.figure.ColorMap;
32 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
33 import org.scilab.modules.renderer.JoGLView.axes.ruler.AxesRulerDrawer;
34 import org.scilab.modules.renderer.JoGLView.label.AxisLabelPositioner;
35 import org.scilab.modules.renderer.JoGLView.label.LabelManager;
36 import org.scilab.modules.renderer.JoGLView.label.LabelPositioner;
37 import org.scilab.modules.renderer.JoGLView.label.TitlePositioner;
38 import org.scilab.modules.renderer.JoGLView.label.YAxisLabelPositioner;
39 import org.scilab.modules.renderer.JoGLView.util.ColorFactory;
41 import java.awt.geom.Rectangle2D;
42 import java.util.HashMap;
47 * AxesDrawer are used by {@see DrawerVisitor} to draw {@see Axes}.
49 * @author Pierre Lando
51 public class AxesDrawer {
52 private static final double DEFAULT_THETA = 270.0;
53 private static final Line.LineType HIDDEN_BORDER_PATTERN = Line.LineType.DASH;
55 /** An epsilon value used to move the clipping planes in order to prevent strict clipping. */
56 private static final double CLIPPING_EPSILON = 1e-5;
58 private final DrawerVisitor visitor;
59 private final Geometries geometries;
61 private final AxesRulerDrawer rulerDrawer;
63 /** The front face culling mode. */
64 private FaceCullingMode frontFaceCullingMode;
66 /** The back face culling mode. */
67 private FaceCullingMode backFaceCullingMode;
69 /** The label manager. */
70 private final LabelManager labelManager;
72 /** The x-axis label positioner. */
73 private AxisLabelPositioner xAxisLabelPositioner;
75 /** The y-axis label positioner. */
76 private AxisLabelPositioner yAxisLabelPositioner;
78 /** The z-axis label positioner. */
79 private AxisLabelPositioner zAxisLabelPositioner;
81 /** The title positioner. */
82 private TitlePositioner titlePositioner;
85 * The current reversed bounds. Used by the functions converting
86 * between object and box coordinates.
88 private double[] reversedBounds;
90 /** The current reversed bounds intervals. */
91 private double[] reversedBoundsIntervals;
93 /** The current projection (from object to window coordinates) used when drawing objects. */
94 private Transformation currentProjection;
96 /** The current data scale and translate transformation. */
97 private Transformation currentDataTransformation;
99 /** The set of object to window coordinate projections associated to all the Axes drawn by this drawer. */
100 private final Map<String, Transformation> projectionMap = new HashMap<String, Transformation>();
102 /** The set of object (in 2d view mode) to window coordinate projections associated to all the Axes drawn by this drawer. */
103 private final Map<String, Transformation> projection2dViewMap = new HashMap<String, Transformation>();
105 /** This is a __MAP__ */
106 private final Map<String, Transformation> sceneProjectionMap = new HashMap<String, Transformation>();
109 * Default constructor.
110 * @param visitor the parent {@see DrawerVisitor}.
112 public AxesDrawer(DrawerVisitor visitor) {
113 this.visitor = visitor;
114 this.labelManager = visitor.getLabelManager();
115 this.geometries = new Geometries(visitor.getCanvas());
116 this.rulerDrawer = new AxesRulerDrawer(visitor.getCanvas());
118 this.xAxisLabelPositioner = new AxisLabelPositioner();
119 this.yAxisLabelPositioner = new YAxisLabelPositioner();
120 this.zAxisLabelPositioner = new AxisLabelPositioner();
121 this.titlePositioner = new TitlePositioner();
123 reversedBounds = new double[6];
124 reversedBoundsIntervals = new double[3];
128 * Draw the given {@see Axes}.
129 * @param axes {@see Axes} to draw.
130 * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
132 public void draw(Axes axes) throws SciRendererException {
133 DrawingTools drawingTools = visitor.getDrawingTools();
134 Canvas canvas = visitor.getCanvas();
135 ColorMap colorMap = visitor.getColorMap();
136 TransformationStack modelViewStack = drawingTools.getTransformationManager().getModelViewStack();
137 TransformationStack projectionStack = drawingTools.getTransformationManager().getProjectionStack();
139 // Axes are drawn on top of everything.
140 drawingTools.clearDepthBuffer();
143 Transformation zoneProjection = computeZoneProjection(axes);
144 projectionStack.push(zoneProjection);
146 // Set box projection.
147 Transformation transformation = computeBoxTransformation(axes, canvas, false);
148 Transformation transformation2dView = computeBoxTransformation(axes, canvas, true);
149 modelViewStack.pushRightMultiply(transformation);
151 /* Compute the data scale and translate transformation. */
152 Transformation dataTransformation = computeDataTransformation(axes);
154 currentDataTransformation = dataTransformation;
156 /* Compute the object to window coordinates projection. */
157 currentProjection = zoneProjection.rightTimes(transformation);
158 currentProjection = currentProjection.rightTimes(dataTransformation);
160 sceneProjectionMap.put(axes.getIdentifier(), currentProjection);
161 Transformation windowTrans = drawingTools.getTransformationManager().getWindowTransformation().getInverseTransformation();
162 currentProjection = windowTrans.rightTimes(currentProjection);
164 /* Update the projection maps with the resulting projections. */
165 addProjection(axes.getIdentifier(), currentProjection);
167 /* 2d view projection, to do: optimize computation */
168 currentProjection = zoneProjection.rightTimes(transformation2dView);
169 currentProjection = currentProjection.rightTimes(dataTransformation);
170 currentProjection = windowTrans.rightTimes(currentProjection);
172 addProjection2dView(axes.getIdentifier(), currentProjection);
175 * Draw the axes background.
177 drawBackground(axes, drawingTools, colorMap);
180 * Mirror the transformation such that the corner with the maximum Z value was (-1, -1, -1).
183 Transformation cubeOrientation = computeCubeMirroring(transformation);
184 modelViewStack.pushRightMultiply(cubeOrientation);
185 drawBox(axes, drawingTools, colorMap);
186 modelViewStack.pop();
188 // Ruler are drawn in box coordinate.
189 rulerDrawer.drawRuler(axes, this, colorMap, drawingTools);
191 /* Compute reversed bounds. */
192 computeReversedBounds(axes);
195 * Scale and translate for data fitting.
198 modelViewStack.pushRightMultiply(dataTransformation);
200 /* Compute the front and back culling modes */
201 computeFaceCullingModes(axes);
203 visitor.askAcceptVisitor(axes.getChildren());
204 modelViewStack.pop();
206 // Reset transformation stacks.
207 modelViewStack.pop();
208 projectionStack.pop();
212 * Draw the axes background.
213 * @param axes the {@see Axes}
214 * @param drawingTools the {@see DrawingTools} to use.
215 * @param colorMap the current {@see ColorMap}
216 * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
218 private void drawBackground(Axes axes, DrawingTools drawingTools, ColorMap colorMap) throws SciRendererException {
219 if (axes.getFilled()) {
220 Appearance appearance = new Appearance();
221 appearance.setFillColor(ColorFactory.createColor(colorMap, axes.getBackground()));
222 drawingTools.draw(geometries.getCubeGeometry(), appearance);
223 drawingTools.clearDepthBuffer();
228 * Draw the box of the given {@see Axes}
229 * @param axes the given {@see Axes}.
230 * @param drawingTools the {@see DrawingTools} to use.
231 * @param colorMap the current {@see ColorMap}.
232 * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
234 private void drawBox(Axes axes, DrawingTools drawingTools, ColorMap colorMap) throws SciRendererException {
235 Box.BoxType boxed = axes.getBox().getBox();
236 if (boxed != Box.BoxType.OFF) {
237 Appearance appearance = new Appearance();
240 * Draw hidden part of box.
242 appearance.setLineColor(ColorFactory.createColor(colorMap, axes.getHiddenAxisColor()));
243 appearance.setLineWidth(axes.getLineThickness().floatValue());
244 appearance.setLinePattern(HIDDEN_BORDER_PATTERN.asPattern());
245 drawingTools.draw(geometries.getHiddenBoxBorderGeometry(), appearance);
248 if (boxed != Box.BoxType.HIDDEN_AXES) {
253 appearance.setLineColor(ColorFactory.createColor(colorMap, axes.getLineColor()));
254 appearance.setLineWidth(axes.getLineThickness().floatValue());
255 appearance.setLinePattern(axes.getLine().getLineStyle().asPattern());
256 drawingTools.draw(geometries.getBoxBorderGeometry(), appearance);
259 if (boxed != Box.BoxType.BACK_HALF) {
261 * Draw front part of box.
263 drawingTools.draw(geometries.getFrontBoxBorderGeometry(), appearance);
271 * Compute the mirroring matrix needed to have the [-1; -1; -1] point projected with the maximal Z value.
272 * @param transformation the current transformation.
273 * @return the mirroring matrix needed to have the [-1; -1; -1] point projected with the maximal Z value.
275 private Transformation computeCubeMirroring(Transformation transformation) {
276 double[] matrix = transformation.getMatrix();
278 return TransformationFactory.getScaleTransformation(
279 matrix[2] < 0 ? 1 : -1,
280 matrix[6] < 0 ? 1 : -1,
281 matrix[10] < 0 ? 1 : -1
283 } catch (DegenerateMatrixException e) {
284 // Should never happen.
285 return TransformationFactory.getIdentity();
291 * Compute zone where the axes is draw. In normalised window coordinate.
292 * @param axes the given {@see axes}.
293 * @return the zone where the axes is draw.
295 private Rectangle2D computeZone(Axes axes) {
296 Double[] axesBounds = axes.getAxesBounds();
297 Double[] margins = axes.getMargins();
300 double x = (axesBounds[0] + axesBounds[2] * margins[0]) * 2 - 1;
301 double y = (1.0 - axesBounds[1] - axesBounds[3] * (1.0 - margins[3])) * 2 - 1;
302 double w = (1 - margins[0] - margins[1]) * axesBounds[2];
303 double h = (1 - margins[2] - margins[3]) * axesBounds[3];
305 // Don't know what's the goal of this code (finally w=h=minSize, so why a square ???)
306 // Comment it: that fixes bug 11801.
307 /*if (axes.getIsoview()) {
308 double minSize = Math.min(w, h);
315 return new Rectangle2D.Double(x, y, w, h);
320 * Compute the projection for the given axes.
321 * @param axes the given axes.
322 * @return the projection matrix.
323 * @throws DegenerateMatrixException if axes represent a nul area.
325 private Transformation computeZoneProjection(Axes axes) throws DegenerateMatrixException {
326 Rectangle2D zone = computeZone(axes);
327 Transformation zoneTranslation = TransformationFactory.getTranslateTransformation(zone.getMaxX(), zone.getMaxY(), 0);
329 // We scale by 0.5 in Z to allow ruler to be drawn.
330 Transformation zoneScale = TransformationFactory.getScaleTransformation(zone.getWidth(), zone.getHeight(), .5);
332 return zoneTranslation.rightTimes(zoneScale);
336 * Compute data transformation for the given {@see Axes}.
338 * The data transformation is applied to data to fit in axes box.
340 * @param axes the given {@see Axes}
341 * @return data transformation.
342 * @throws DegenerateMatrixException if data bounds are not corrects.
344 private Transformation computeDataTransformation(Axes axes) throws DegenerateMatrixException {
345 Double[] bounds = axes.getDisplayedBounds();
347 // Reverse data if needed.
348 Transformation transformation = TransformationFactory.getScaleTransformation(
349 axes.getAxes()[0].getReverse() ? 1 : -1,
350 axes.getAxes()[1].getReverse() ? 1 : -1,
351 axes.getAxes()[2].getReverse() ? 1 : -1
355 Transformation scaleTransformation = TransformationFactory.getScaleTransformation(
356 2.0 / (bounds[1] - bounds[0]),
357 2.0 / (bounds[3] - bounds[2]),
358 2.0 / (bounds[5] - bounds[4])
360 transformation = transformation.rightTimes(scaleTransformation);
364 Transformation translateTransformation = TransformationFactory.getTranslateTransformation(
365 -(bounds[0] + bounds[1]) / 2.0,
366 -(bounds[2] + bounds[3]) / 2.0,
367 -(bounds[4] + bounds[5]) / 2.0
369 transformation = transformation.rightTimes(translateTransformation);
370 return transformation;
375 * Compute box transformation for the given axes.
377 * The box transformation is applied to the axes box to fit in the canvas.
379 * @param axes the given {@see Axes}.
380 * @param canvas the current {@see Canvas}.
381 * @param use2dView specifies whether the default 2d view rotation angles must be used (true) or the given Axes' ones (false).
382 * @return box transformation for the given axes.
383 * @throws DegenerateMatrixException if data bounds are incorrect or canvas with or length are zero.
385 private Transformation computeBoxTransformation(Axes axes, Canvas canvas, boolean use2dView) throws DegenerateMatrixException {
386 Double[] bounds = axes.getDisplayedBounds();
396 Transformation transformation;
398 // Set zone aspect ratio.
399 Rectangle2D zone = computeZone(axes);
400 double axesRatio = zone.getWidth() / zone.getHeight();
401 transformation = TransformationFactory.getPreferredAspectRatioTransformation(canvas.getDimension(), axesRatio);
406 theta = 2 * DEFAULT_THETA;
408 alpha = -axes.getRotationAngles()[0];
409 theta = DEFAULT_THETA + axes.getRotationAngles()[1];
412 Transformation alphaRotation = TransformationFactory.getRotationTransformation(alpha, 1.0, 0.0, 0.0);
413 transformation = transformation.rightTimes(alphaRotation);
414 Transformation thetaRotation = TransformationFactory.getRotationTransformation(theta, 0.0, 0.0, 1.0);
415 transformation = transformation.rightTimes(thetaRotation);
417 // If there is no cube scaling, we must take into account the distribution of data.
418 if (!axes.getCubeScaling()) {
419 tmpX = (bounds[1] - bounds[0]);
420 tmpY = (bounds[3] - bounds[2]);
421 tmpZ = (bounds[5] - bounds[4]);
424 * Here, we should divide the values by their maximum.
425 * But the next operation will automatically.
427 Transformation cubeScale = TransformationFactory.getScaleTransformation(tmpX, tmpY, tmpZ);
428 transformation = transformation.rightTimes(cubeScale);
431 // Compute bounds of projected data.
432 double[] matrix = transformation.getMatrix();
433 tmpX = 1 / (Math.abs(matrix[0]) + Math.abs(matrix[4]) + Math.abs(matrix[8]));
434 tmpY = 1 / (Math.abs(matrix[1]) + Math.abs(matrix[5]) + Math.abs(matrix[9]));
435 tmpZ = 1 / (Math.abs(matrix[2]) + Math.abs(matrix[6]) + Math.abs(matrix[10]));
437 // Scale projected data to fit in the cube.
438 Transformation isoScale;
439 if (axes.getIsoview()) {
440 Double[] axesBounds = axes.getAxesBounds();
441 double minScale = Math.min(tmpX * axesBounds[2], tmpY * axesBounds[3]);
442 isoScale = TransformationFactory.getScaleTransformation(minScale / axesBounds[2], minScale / axesBounds[3], tmpZ);
444 isoScale = TransformationFactory.getScaleTransformation(tmpX, tmpY, tmpZ);
446 transformation = transformation.leftTimes(isoScale);
448 return transformation;
453 * Computes and sets the reversed bounds of the given Axes.
454 * @param axes the given {@see Axes}.
456 private void computeReversedBounds(Axes axes) {
457 Double[] currentBounds = axes.getDisplayedBounds();
460 if (axes.getAxes()[0].getReverse()) {
461 reversedBounds[0] = currentBounds[1];
462 reversedBounds[1] = currentBounds[0];
464 reversedBounds[0] = currentBounds[0];
465 reversedBounds[1] = currentBounds[1];
468 if (axes.getAxes()[1].getReverse()) {
469 reversedBounds[2] = currentBounds[3];
470 reversedBounds[3] = currentBounds[2];
472 reversedBounds[2] = currentBounds[2];
473 reversedBounds[3] = currentBounds[3];
476 if (axes.getAxes()[2].getReverse()) {
477 reversedBounds[4] = currentBounds[5];
478 reversedBounds[5] = currentBounds[4];
480 reversedBounds[4] = currentBounds[4];
481 reversedBounds[5] = currentBounds[5];
485 * Interval values are set to 1 when bounds are equal to avoid divides by 0
486 * in the object to box coordinates conversion function.
488 if (reversedBounds[1] == reversedBounds[0]) {
489 reversedBoundsIntervals[0] = 1.0;
491 reversedBoundsIntervals[0] = reversedBounds[1] - reversedBounds[0];
494 if (reversedBounds[3] == reversedBounds[2]) {
495 reversedBoundsIntervals[1] = 1.0;
497 reversedBoundsIntervals[1] = reversedBounds[3] - reversedBounds[2];
500 if (reversedBounds[5] == reversedBounds[4]) {
501 reversedBoundsIntervals[2] = 1.0;
503 reversedBoundsIntervals[2] = reversedBounds[5] - reversedBounds[4];
509 * Computes the culling modes respectively corresponding to front and back faces
510 * of the given Axes' child objects as a function of its {X,Y,Z} reverse properties.
511 * It must be called by draw prior to rendering any child object.
512 * @param axes the given {@see Axes}.
514 private void computeFaceCullingModes(Axes axes) {
515 if (axes.getAxes()[0].getReverse() ^ axes.getAxes()[1].getReverse() ^ axes.getAxes()[2].getReverse()) {
517 this.frontFaceCullingMode = FaceCullingMode.CW;
518 this.backFaceCullingMode = FaceCullingMode.CCW;
521 this.frontFaceCullingMode = FaceCullingMode.CCW;
522 this.backFaceCullingMode = FaceCullingMode.CW;
527 * Converts a point from object coordinates to box coordinates (used when drawing axis rulers).
528 * @param point the point in object coordinates.
529 * @return the point in box coordinates.
531 public Vector3d getBoxCoordinates(Vector3d point) {
532 double[] dataCoordinates = new double[3];
534 dataCoordinates[0] = 1 - 2.0 * (point.getX() - reversedBounds[0]) / reversedBoundsIntervals[0];
535 dataCoordinates[1] = 1 - 2.0 * (point.getY() - reversedBounds[2]) / reversedBoundsIntervals[1];
536 dataCoordinates[2] = 1 - 2.0 * (point.getZ() - reversedBounds[4]) / reversedBoundsIntervals[2];
538 return new Vector3d(dataCoordinates);
542 * Converts a point from box coordinates (used when drawing axis rulers) to object coordinates.
543 * @param point the point in box coordinates.
544 * @return the point in object coordinates.
546 public Vector3d getObjectCoordinates(Vector3d point) {
547 double[] objectCoordinates = new double[3];
549 objectCoordinates[0] = 0.5 * (1.0 - point.getX()) * (reversedBounds[1] - reversedBounds[0]) + reversedBounds[0];
550 objectCoordinates[1] = 0.5 * (1.0 - point.getY()) * (reversedBounds[3] - reversedBounds[2]) + reversedBounds[2];
551 objectCoordinates[2] = 0.5 * (1.0 - point.getZ()) * (reversedBounds[5] - reversedBounds[4]) + reversedBounds[4];
553 return new Vector3d(objectCoordinates);
557 * Computes and return the object to window coordinate projection corresponding to the given Axes object.
558 * @param axes the given Axes.
559 * @param drawingTools the drawing tools.
560 * @param canvas the current canvas.
561 * @param use2dView specifies whether the default 2d view rotation angles must be used (true) or the given Axes' ones (false).
562 * @return the projection
564 private Transformation computeProjection(Axes axes, DrawingTools drawingTools, Canvas canvas, boolean use2dView) {
565 Transformation projection;
568 /* Compute the zone projection. */
569 Transformation zoneProjection = computeZoneProjection(axes);
571 /* Compute the box transformation. */
572 Transformation transformation = computeBoxTransformation(axes, canvas, use2dView);
574 /* Compute the data scale and translate transformation. */
575 Transformation dataTransformation = computeDataTransformation(axes);
577 /* Compute the object to window coordinates projection. */
578 projection = zoneProjection.rightTimes(transformation);
579 projection = projection.rightTimes(dataTransformation);
581 Transformation windowTrans = drawingTools.getTransformationManager().getWindowTransformation().getInverseTransformation();
582 projection = windowTrans.rightTimes(projection);
583 } catch (DegenerateMatrixException e) {
584 return TransformationFactory.getIdentity();
591 * Returns the current projection from object to window coordinates.
592 * @return the projection.
594 public Transformation getProjection() {
595 return currentProjection;
599 * Returns the current data scale and translate transformation.
600 * @return the data transformation.
602 public Transformation getDataTransformation() {
603 return currentDataTransformation;
607 * Adds the projection from object to window coordinates corresponding to a given Axes object
608 * to the projection map.
609 * @param axesId the identifier of the given Axes.
610 * @param projection the corresponding projection.
612 public synchronized void addProjection(String axesId, Transformation projection) {
613 projectionMap.put(axesId, projection);
617 * Returns the projection from object to window coordinates corresponding
618 * to a given Axes object.
619 * @param id the identifier of the given Axes.
620 * @return the projection.
622 public Transformation getProjection(String id) {
623 return projectionMap.get(id);
627 * Removes the object to window coordinate projection corresponding to a given Axes from
628 * the projection map.
629 * @param axesId the identifier of the given Axes.
631 public void removeProjection(String axesId) {
632 projectionMap.remove(axesId);
636 * Adds the projection from object (in 2d view mode) to window coordinates corresponding to a given Axes object
637 * to the projection map.
638 * @param axesId the identifier of the given Axes.
639 * @param projection the corresponding projection.
641 public synchronized void addProjection2dView(String axesId, Transformation projection) {
642 projection2dViewMap.put(axesId, projection);
646 * Returns the projection from object (in 2d view mode) to window coordinates corresponding
647 * to a given Axes object.
648 * @param id the identifier of the given Axes.
649 * @return the projection.
651 public Transformation getProjection2dView(String id) {
652 return projection2dViewMap.get(id);
655 public Transformation getSceneProjection(String id) {
656 return sceneProjectionMap.get(id);
660 * Removes the object (in 2d view mode) to window coordinate projection corresponding to a given Axes from
661 * the projection map.
662 * @param axesId the identifier of the given Axes.
664 public void removeProjection2dView(String axesId) {
665 projection2dViewMap.remove(axesId);
669 * Updates both the projection from object to window coordinates and the related
670 * object (in 2d view mode) to window coordinates projection for the given Axes object.
671 * @param axes the given Axes.
673 public static void updateAxesTransformation(Axes axes) {
674 DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
675 AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
677 Transformation transformation = axesDrawer.getProjection(axes.getIdentifier());
679 /* The projection must be updated */
680 if (transformation == null) {
681 Transformation projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), currentVisitor.getCanvas(), false);
683 axesDrawer.addProjection(axes.getIdentifier(), projection);
686 Transformation transformation2dView = axesDrawer.getProjection2dView(axes.getIdentifier());
688 /* The projection must be updated */
689 if (transformation2dView == null) {
690 Transformation projection2dView = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), currentVisitor.getCanvas(), true);
692 axesDrawer.addProjection2dView(axes.getIdentifier(), projection2dView);
697 * Computes and returns the coordinates of a point projected onto the default 2d view plane.
698 * To compute them, the point is projected using the object to window coordinate projection, then
699 * unprojected using the object to window coordinate projection corresponding to the default 2d view
700 * (which uses the default camera rotation angles).
701 * To do: optimize by using the already computed 3d view projection.
702 * @param axes the given Axes.
703 * @param coordinates the object (x,y,z) coordinates to project onto the 2d view plane (3-element array).
704 * @returns the 2d view coordinates (3-element array).
706 public static double[] compute2dViewCoordinates(Axes axes, double[] coordinates) {
707 DrawerVisitor currentVisitor;
708 AxesDrawer axesDrawer;
709 Transformation projection;
710 Transformation projection2d;
712 currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
714 Vector3d point = new Vector3d(coordinates);
716 if (currentVisitor != null) {
717 axesDrawer = currentVisitor.getAxesDrawer();
719 projection = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), currentVisitor.getCanvas(), false);
720 projection2d = axesDrawer.computeProjection(axes, currentVisitor.getDrawingTools(), currentVisitor.getCanvas(), true);
722 point = projection.project(point);
723 point = projection2d.unproject(point);
726 return new double[] {point.getX(), point.getY(), point.getZ()};
730 * Computes and returns the pixel coordinates from a point's coordinates expressed in the default
731 * 2d view coordinate frame, using the given Axes. The returned pixel coordinates are expressed
732 * in the AWT's 2d coordinate frame.
733 * @param axes the given Axes.
734 * @param coordinates the 2d view coordinates (3-element array: x, y, z).
735 * @returns the pixel coordinates (2-element array: x, y).
737 public static double[] computePixelFrom2dViewCoordinates(Axes axes, double[] coordinates) {
738 DrawerVisitor currentVisitor;
739 AxesDrawer axesDrawer;
741 double[] coords2dView = new double[] {0.0, 0.0, 0.0};
743 currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
745 if (currentVisitor != null) {
748 axesDrawer = currentVisitor.getAxesDrawer();
749 height = currentVisitor.getCanvas().getHeight();
751 Transformation projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
753 Vector3d point = new Vector3d(coordinates);
754 point = projection2d.project(point);
756 /* Convert the window coordinates to pixel coordinates, only y changes due to the differing y-axis convention */
757 coords2dView[0] = point.getX();
758 coords2dView[1] = height - point.getY();
765 * Computes and returns the coordinates of a point projected onto the default 2d view plane
766 * from its pixel coordinates, using the given Axes. Pixel coordinates are expressed in
767 * the AWT's 2d coordinate frame.
768 * The returned point's z component is set to 0, as we only have x and y as an input.
769 * @param axes the given Axes.
770 * @param coordinates the pixel coordinates (2-element array: x, y).
771 * @return coordinates the 2d view coordinates (3-element array: x, y, z).
773 public static double[] compute2dViewFromPixelCoordinates(Axes axes, double[] coordinates) {
774 DrawerVisitor currentVisitor;
775 AxesDrawer axesDrawer;
777 double[] coords2dView = new double[] {0.0, 0.0, 0.0};
779 currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
781 if (currentVisitor != null) {
784 axesDrawer = currentVisitor.getAxesDrawer();
785 height = currentVisitor.getCanvas().getHeight();
787 /* Convert the pixel coordinates to window coordinates, only y changes due to the differing y-axis convention */
788 Vector3d point = new Vector3d(coordinates[0], height - coordinates[1], 0.0);
790 Transformation projection2d = axesDrawer.getProjection2dView(axes.getIdentifier());
791 point = projection2d.unproject(point);
792 coords2dView = point.getData();
799 * Un-project the given point from AWT coordinate to given axes coordinate.
800 * @param axes returned coordinate are relative to this axes.
801 * @param point un-projected point.
802 * @return The un-projected point.
804 public static Vector3d unProject(Axes axes, Vector3d point) {
805 DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
807 if (currentVisitor != null) {
808 AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
809 double height = currentVisitor.getCanvas().getHeight() - 1;
811 Transformation projection2d = axesDrawer.getProjection(axes.getIdentifier());
812 return projection2d.unproject(new Vector3d(point.getX(), height - point.getY(), point.getZ()));
814 return new Vector3d(0, 0, 0);
819 * Computes and returns the viewing area corresponding to the given Axes object.
820 * The viewing area is described by the (x, y) coordinates of the Axes box's upper-left corner
821 * and the Axes box's dimensions (width and height), all values are in pixel.
822 * The 2d coordinate frame in which the area is expressed uses the AWT convention:
823 * upper-left window corner at (0, 0), y-axis pointing downwards).
824 * @param axes the given Axes.
825 * @return the Axes' viewing area (4-element array: x, y, width, height).
827 public static double[] getViewingArea(Axes axes) {
828 DrawerVisitor currentVisitor;
830 double[] viewingArea = new double[] {0.0, 0.0, 0.0, 0.0};
832 currentVisitor = DrawerVisitor.getVisitor(axes.getParentFigure());
834 if (currentVisitor != null) {
839 AxesDrawer axesDrawer = currentVisitor.getAxesDrawer();
841 width = (double) currentVisitor.getCanvas().getWidth();
842 height = (double) currentVisitor.getCanvas().getHeight();
844 Rectangle2D axesZone = axesDrawer.computeZone(axes);
846 /* Compute the upper-left point's y coordinate */
847 upperLeftY = axesZone.getY() + axesZone.getHeight() * 2.0;
849 /* Convert from normalized coordinates to 2D pixel coordinates */
850 viewingArea[0] = (axesZone.getX() + 1.0) * 0.5 * width;
851 viewingArea[1] = (1.0 - upperLeftY) * 0.5 * height;
852 viewingArea[2] = axesZone.getWidth() * width;
853 viewingArea[3] = axesZone.getHeight() * height;
860 * Returns the culling mode corresponding to front faces.
861 * @return the front face culling mode.
863 public FaceCullingMode getFrontFaceCullingMode() {
864 return this.frontFaceCullingMode;
868 * Returns the culling mode corresponding to back faces.
869 * @return the back face culling mode.
871 public FaceCullingMode getBackFaceCullingMode() {
872 return this.backFaceCullingMode;
876 * Enables clipping for the given {@link ClippableProperty}, which describes
877 * the clipping state of a clippable graphic object.
878 * Depending on the object's clip state property, clipping can be either
879 * disabled (OFF), performed against the parent Axes' box planes (CLIPGRF),
880 * or performed against the planes defined by the object's clip box.
881 * To do: find a better way to compute the clipping planes' offsets as the current one
882 * may lead to problems when the interval between the min and max bounds is too small.
883 * @param parentAxes the clipped object's parent Axes.
884 * @param clipProperty the clipping property of a clippable object.
886 public void enableClipping(Axes parentAxes, ClippableProperty clipProperty) {
887 DrawingTools drawingTools = visitor.getDrawingTools();
889 if (clipProperty.getClipState() != ClipStateType.OFF) {
891 Vector4d[] equations = null;
893 /* Stores the (xmin,xmax) ,(ymin,ymax) and (zmin,zmax) clipping bounds */
894 double[] clipBounds = new double[6];
896 /* The offsets used for the x, y and z planes in order to avoid strict clipping */
897 double[] offsets = new double[3];
899 if (clipProperty.getClipState() == ClipStateType.CLIPGRF) {
901 * All the clipping planes are set as clipping is performed
902 * against the Axes box planes.
905 Double[] bounds = parentAxes.getDisplayedBounds();
907 for (int i = 0; i < numPlanes; i++) {
908 clipBounds[i] = bounds[i];
911 offsets[0] = CLIPPING_EPSILON * (bounds[1] - bounds[0]);
912 offsets[1] = CLIPPING_EPSILON * (bounds[3] - bounds[2]);
913 offsets[2] = CLIPPING_EPSILON * (bounds[5] - bounds[4]);
914 } else if (clipProperty.getClipState() == ClipStateType.ON) {
916 * The clip box property defines values only for the x and y axes,
917 * we therefore set only the x and y clipping planes.
920 Double[] clipBox = clipProperty.getClipBox();
922 /* The clip box stores the upper-left point coordinates. */
923 clipBounds[0] = clipBox[0];
924 clipBounds[1] = clipBox[0] + clipBox[2];
925 clipBounds[2] = clipBox[1] - clipBox[3];
926 clipBounds[3] = clipBox[1];
928 Double[] bounds = parentAxes.getMaximalDisplayedBounds();
931 * The logarithmic scale must be applied to clip bounds values.
932 * If any of them are invalid, we set them to the displayed
933 * left bounds (xmin or ymin).
935 if (parentAxes.getXAxisLogFlag()) {
936 if (clipBounds[0] <= 0.0) {
937 clipBounds[0] = bounds[0];
939 clipBounds[0] = Math.log10(clipBounds[0]);
942 if (clipBounds[1] <= 0.0) {
943 clipBounds[1] = bounds[0];
945 clipBounds[1] = Math.log10(clipBounds[1]);
949 if (parentAxes.getYAxisLogFlag()) {
950 if (clipBounds[2] <= 0.0) {
951 clipBounds[2] = bounds[2];
953 clipBounds[2] = Math.log10(clipBounds[2]);
956 if (clipBounds[3] <= 0.0) {
957 clipBounds[3] = bounds[2];
959 clipBounds[3] = Math.log10(clipBounds[3]);
963 offsets[0] = CLIPPING_EPSILON * (clipBounds[1] - clipBounds[0]);
964 offsets[1] = CLIPPING_EPSILON * (clipBounds[3] - clipBounds[2]);
967 equations = new Vector4d[numPlanes];
969 equations[0] = new Vector4d(+1, 0, 0, -clipBounds[0] + offsets[0]);
970 equations[1] = new Vector4d(-1, 0, 0, +clipBounds[1] + offsets[0]);
971 equations[2] = new Vector4d(0, +1, 0, -clipBounds[2] + offsets[1]);
972 equations[3] = new Vector4d(0, -1, 0, +clipBounds[3] + offsets[1]);
974 /* If clipping is performed against the Axes box, the z plane equations must be initialized. */
975 if (numPlanes == 6) {
976 equations[4] = new Vector4d(0, 0, +1, -clipBounds[4] + offsets[2]);
977 equations[5] = new Vector4d(0, 0, -1, +clipBounds[5] + offsets[2]);
980 Transformation currentTransformation = drawingTools.getTransformationManager().getTransformation();
981 for (int i = 0 ; i < numPlanes; i++) {
982 ClippingPlane plane = drawingTools.getClippingManager().getClippingPlane(i);
983 plane.setTransformation(currentTransformation);
984 plane.setEquation(equations[i]);
985 plane.setEnable(true);
991 * Disables clipping for the given {@link ClippableProperty}.
992 * @param clipProperty the clip property for which clipping is disabled.
994 public void disableClipping(ClippableProperty clipProperty) {
997 if (clipProperty.getClipState() == ClipStateType.CLIPGRF) {
999 } else if (clipProperty.getClipState() == ClipStateType.ON) {
1003 for (int i = 0 ; i < numPlanes; i++) {
1004 ClippingPlane plane = visitor.getDrawingTools().getClippingManager().getClippingPlane(i);
1005 plane.setEnable(false);
1008 visitor.getDrawingTools().getClippingManager().disableClipping();
1012 * Returns the x-axis label positioner.
1013 * @return the x-axis label positioner.
1015 public AxisLabelPositioner getXAxisLabelPositioner() {
1016 return this.xAxisLabelPositioner;
1020 * Returns the y-axis label positioner.
1021 * @return the y-axis label positioner.
1023 public AxisLabelPositioner getYAxisLabelPositioner() {
1024 return this.yAxisLabelPositioner;
1028 * Returns the z-axis label positioner.
1029 * @return the z-axis label positioner.
1031 public AxisLabelPositioner getZAxisLabelPositioner() {
1032 return this.zAxisLabelPositioner;
1036 * Returns the title positioner.
1037 * @return the title positioner.
1039 public LabelPositioner getTitlePositioner() {
1040 return this.titlePositioner;
1043 public void disposeAll() {
1044 this.rulerDrawer.disposeAll();
1045 this.projectionMap.clear();
1046 this.projection2dViewMap.clear();
1047 this.sceneProjectionMap.clear();
1050 public void update(String id, int property) {
1051 this.rulerDrawer.update(id, property);
1054 public void dispose(String id) {
1055 this.rulerDrawer.dispose(id);
1056 projectionMap.remove(id);
1057 projection2dViewMap.remove(id);
1058 sceneProjectionMap.remove(id);