diff --git a/jzy3d-core/src/main/java/org/jzy3d/chart/Chart.java b/jzy3d-core/src/main/java/org/jzy3d/chart/Chart.java index e326837a5..ceb5b1c78 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/chart/Chart.java +++ b/jzy3d-core/src/main/java/org/jzy3d/chart/Chart.java @@ -43,8 +43,8 @@ import org.jzy3d.plot3d.rendering.lights.Light; import org.jzy3d.plot3d.rendering.scene.Scene; import org.jzy3d.plot3d.rendering.view.View; +import org.jzy3d.plot3d.rendering.view.View2D; import org.jzy3d.plot3d.rendering.view.ViewportMode; -import org.jzy3d.plot3d.rendering.view.layout.IViewportLayout; import org.jzy3d.plot3d.rendering.view.layout.ViewAndColorbarsLayout; import org.jzy3d.plot3d.rendering.view.lod.LODCandidates; import org.jzy3d.plot3d.rendering.view.lod.LODPerf; @@ -139,14 +139,19 @@ public Chart color(Color background, Color axis) { * tick and axis labels. */ public Chart view2d() { + return view2d(View2D.XY); + } + + public Chart view2d(View2D view2D) { AxisLayout axisLayout = getAxisLayout(); View view = getView(); // Remember 3D layout if(view.is3D()) { - axisZTickLabelDisplayed = axisLayout.isZTickLabelDisplayed(); + axisXLabelOrientation = axisLayout.getXAxisLabelOrientation(); axisYLabelOrientation = axisLayout.getYAxisLabelOrientation(); - axisZLabelDisplayed = axisLayout.isZAxisLabelDisplayed(); + axisZLabelOrientation = axisLayout.getZAxisLabelOrientation(); + isTickLineDisplayed = axisLayout.isTickLineDisplayed(); isSquaredViewActive = view.getSquared(); @@ -155,14 +160,36 @@ public Chart view2d() { viewpoint = view.getViewPoint().clone(); } - // Apply 2D layout to axis - axisLayout.setTickLineDisplayed(false); - axisLayout.setZAxisLabelDisplayed(false); - axisLayout.setZTickLabelDisplayed(false); - axisLayout.setYAxisLabelOrientation(LabelOrientation.VERTICAL); + if(View2D.XY.equals(view2D)) { + // Apply 2D layout to axis + axisLayout.setXAxisLabelOrientation(LabelOrientation.HORIZONTAL); + axisLayout.setYAxisLabelOrientation(LabelOrientation.VERTICAL); + + // Apply 2D layout to view + view.setViewPositionMode(ViewPositionMode.TOP); + } + else if(View2D.XZ.equals(view2D)) { + // Apply 2D layout to axis + axisLayout.setXAxisLabelOrientation(LabelOrientation.HORIZONTAL); + axisLayout.setZAxisLabelOrientation(LabelOrientation.VERTICAL); + + // Apply 2D layout to view + view.setViewPositionMode(ViewPositionMode.XZ); + } + + else if(View2D.YZ.equals(view2D)) { + // Apply 2D layout to axis + axisLayout.setYAxisLabelOrientation(LabelOrientation.HORIZONTAL); + axisLayout.setZAxisLabelOrientation(LabelOrientation.VERTICAL); - // Apply 2D layout to view - view.setViewPositionMode(ViewPositionMode.TOP); + // Apply 2D layout to view + view.setViewPositionMode(ViewPositionMode.YZ); + } + + // General 2D axis layout + axisLayout.setTickLineDisplayed(false); + + // General 2D view settings view.setSquared(false); view.getCamera().setViewportMode(ViewportMode.STRETCH_TO_FILL); @@ -174,9 +201,9 @@ public Chart view2d() { } // memory of 3D settings before switching to 2D + protected LabelOrientation axisXLabelOrientation = null; protected LabelOrientation axisYLabelOrientation = null; - protected boolean axisZLabelDisplayed = true; - protected boolean axisZTickLabelDisplayed = true; + protected LabelOrientation axisZLabelOrientation = null; protected boolean isTickLineDisplayed = true; protected boolean isSquaredViewActive = true; protected ViewPositionMode viewPositionMode = ViewPositionMode.FREE; @@ -187,13 +214,16 @@ public Chart view3d() { AxisLayout axisLayout = getAxisLayout(); // Restore 3D layout to axis + axisLayout.setTickLineDisplayed(isTickLineDisplayed); + if(axisXLabelOrientation!=null){ + axisLayout.setXAxisLabelOrientation(axisXLabelOrientation); + } if(axisYLabelOrientation!=null){ axisLayout.setYAxisLabelOrientation(axisYLabelOrientation); } - - axisLayout.setZAxisLabelDisplayed(axisZLabelDisplayed); - axisLayout.setZTickLabelDisplayed(axisZTickLabelDisplayed); - axisLayout.setTickLineDisplayed(isTickLineDisplayed); + if(axisZLabelOrientation!=null){ + axisLayout.setZAxisLabelOrientation(axisZLabelOrientation); + } // Restore 3D layout to view View view = getView(); diff --git a/jzy3d-core/src/main/java/org/jzy3d/maths/Coord3d.java b/jzy3d-core/src/main/java/org/jzy3d/maths/Coord3d.java index 01788f784..2966be03b 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/maths/Coord3d.java +++ b/jzy3d-core/src/main/java/org/jzy3d/maths/Coord3d.java @@ -684,5 +684,9 @@ public static Coord3d getCoordAt(float[] array, int i) { return new Coord3d(array[i], array[i+1], array[i+2]); } + public boolean isValid() { + return Float.isFinite(x) && Float.isFinite(y) && Float.isFinite(z); + } + } diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/Arrow.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/Arrow.java index 64d967455..50b49b8f6 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/Arrow.java +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/Arrow.java @@ -14,16 +14,37 @@ public class Arrow extends Composite { protected Cylinder cylinder; protected Cone cone; + + protected Vector3d vector; + + protected static Vector3d createVector3d(Coord3d pos, Coord3d dir, float length) { + Coord3d dirN = dir.getNormalizedTo(length / 2f); + Coord3d end = pos.add(dirN); + dirN = dirN.negative(); + Coord3d start = pos.add(dirN); + return new Vector3d(start, end); + } + + public Arrow() {} + + public Arrow(Coord3d pos, Coord3d dir, float length, float radius, int slices, int rings, Color color) { + this(); + setData(createVector3d(pos, dir, length), radius, slices, rings, color); + } - public void setData(Vector3d vec, float radius, int slices, int rings, Color color) { - Coord3d position = vec.getCenter(); - float length = vec.norm(); + public void setData(Vector3d vector, float radius, int slices, int rings, Color color) { + this.vector = vector; + + Coord3d position = vector.getCenter(); + float length = vector.norm(); float coneHeight = radius * 2.5f; float cylinderHeight = length - coneHeight; + // Arrow body cylinder = new Cylinder(); cylinder.setData(new Coord3d(0, 0, -length / 2f), cylinderHeight, radius, slices, rings, color); + // Arrow extremity cone = new Cone(); cone.setData(new Coord3d(0, 0, length / 2d - coneHeight), coneHeight, radius * 1.6f, slices, rings, color); @@ -31,13 +52,24 @@ public void setData(Vector3d vec, float radius, int slices, int rings, Color col add(cylinder); add(cone); + // Apply the same wireframe settings to each element + // of this composite setWireframeDisplayed(isWireframeDisplayed()); + Transform trans = new Transform(); - Rotate rot = createRotateTo(new Coord3d(0d, 0d, 1d), vec.vector()); - trans.add(rot); + + // Rotate shape + Rotate rot = createRotateTo(new Coord3d(0d, 0d, 1d), vector.vector()); + + if(Float.isFinite(rot.getAngle())){ + trans.add(rot); + } + + // Shift shape Translate translate = new Translate(position); trans.add(translate); + applyGeometryTransform(trans); } @@ -47,4 +79,10 @@ private static Rotate createRotateTo(Coord3d from, Coord3d to) { double angle = Math.acos(from.dot(to) / fromMag / toMag) * 180f / Math.PI; return new Rotate(angle, from.cross(to)); } + + public Vector3d getVector() { + return vector; + } + + } diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisBox.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisBox.java index 42526e211..529bc6c5a 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisBox.java +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisBox.java @@ -31,7 +31,7 @@ */ public class AxisBox implements IAxis { static Logger LOGGER = LogManager.getLogger(AxisBox.class); - + protected static final int PRECISION = 6; @@ -80,13 +80,18 @@ public class AxisBox implements IAxis { public static final int AXE_X = 0; public static final int AXE_Y = 1; public static final int AXE_Z = 2; - + + public static final int EDGE_0 = 0; + public static final int EDGE_1 = 1; + public static final int EDGE_2 = 2; + public static final int EDGE_3 = 3; + protected boolean depthRangeTrick = true; - + /** - * The higher the value, the more the line are far from the faces and hence - * no z-fighting occurs between faces and lines. In case of higher value, line - * will be display more often, but also lines that should be behind the polygon + * The higher the value, the more the line are far from the faces and hence no z-fighting occurs + * between faces and lines. In case of higher value, line will be display more often, but also + * lines that should be behind the polygon */ public static float NO_OVERLAP_DEPTH_RATIO = 0.5f; @@ -94,22 +99,26 @@ public class AxisBox implements IAxis { protected SpaceTransformer spaceTransformer; + public AxisBox() { + this(null); + } + public AxisBox(BoundingBox3d bbox) { this(bbox, new AxisLayout()); } public AxisBox(BoundingBox3d bbox, AxisLayout layout) { this.layout = layout; - //if (bbox.valid()) - setAxe(bbox); - //else - // setAxe(new BoundingBox3d(-1, 1, -1, 1, -1, 1)); - wholeBounds = new BoundingBox3d(); - textRenderer = new TextRenderer(); - rotateLabel = new AxisLabelRotator(); - labels = new AxisLabelProcessor(this); - ticks = new AxisTickProcessor(this); + this.wholeBounds = new BoundingBox3d(); + this.textRenderer = new TextRenderer(); + this.rotateLabel = new AxisLabelRotator(); + this.labels = new AxisLabelProcessor(this); + this.ticks = new AxisTickProcessor(this); + init(); + + if (bbox != null) + setAxe(bbox); } /** @@ -180,13 +189,13 @@ public void drawFace(IPainter painter) { public void drawGrid(IPainter painter) { Color gridcolor = layout.getGridColor(); - + // Push far from camera, to ensure the axis grid // Will remain covered by surface - if(depthRangeTrick) + if (depthRangeTrick) painter.glDepthRangef(NO_OVERLAP_DEPTH_RATIO, 1f); - + painter.glPolygonMode(PolygonMode.BACK, PolygonFill.LINE); painter.color(gridcolor); painter.glLineWidth(1); @@ -203,11 +212,11 @@ public void drawGrid(IPainter painter) { if (!quadIsHidden[quad]) drawGridOnQuad(painter, quad); painter.glDisable_LineStipple(); - - + + // Reset depth range - - if(depthRangeTrick) + + if (depthRangeTrick) painter.glDepthRangef(0f, 1f); } @@ -229,7 +238,7 @@ protected void drawCube(IPainter painter, RenderMode mode) { // Draw a grid on the desired quad. protected void drawGridOnQuad(IPainter painter, int quad) { - + // Draw X grid along X axis if ((quad != 0) && (quad != 1)) { double[] xticks = layout.getXTicks(); @@ -264,7 +273,7 @@ protected void drawGridOnQuad(IPainter painter, int quad) { /////////////////////////// - + /** Trigger all axis ticks and labels rendering if conditions are met. */ public void drawTicksAndLabels(IPainter painter) { wholeBounds.reset(); wholeBounds.add(boxBounds); @@ -274,76 +283,144 @@ public void drawTicksAndLabels(IPainter painter) { drawTicksAndLabelsZ(painter); } + /** + * Select an X axis for ticks and labels rendering if conditions are met (range > 0 and axis + * layout configured to display this axis) + */ public void drawTicksAndLabelsX(IPainter painter) { if (xrange > 0 && layout.isXTickLabelDisplayed()) { + BoundingBox3d textBounds = null; + // 2D case - if (view != null && view.is2D()) { - BoundingBox3d bbox = - ticks.drawTicks(painter, 1, AXE_X, layout.getXTickColor(), Horizontal.LEFT, Vertical.TOP); - wholeBounds.add(bbox); + if (is2DWithX()) { + textBounds = ticks.drawTicks(painter, EDGE_1, AXE_X, layout.getXTickColor(), + Horizontal.CENTER, Vertical.GROUND); } // 3D case - else { - int xselect = findClosestXaxe(painter.getCamera()); - if (xselect >= 0) { - BoundingBox3d bbox = ticks.drawTicks(painter, xselect, AXE_X, layout.getXTickColor()); - wholeBounds.add(bbox); + else if (is3D()) { + + int edgeId = findClosestXaxe(painter.getCamera()); + + if (edgeId >= 0) { + + textBounds = ticks.drawTicks(painter, edgeId, AXE_X, layout.getXTickColor()); } else { - // HACK: handles "on top" view, when all face of cube are - // drawn, which forbid to select an axe automatically - BoundingBox3d bbox = - ticks.drawTicks(painter, 2, AXE_X, layout.getXTickColor(), Horizontal.CENTER, Vertical.TOP); - wholeBounds.add(bbox); + // handles "on top" view, when all face of cube are drawn, which forbid to select an axe + // automatically + textBounds = ticks.drawTicks(painter, EDGE_2, AXE_X, layout.getXTickColor(), + Horizontal.CENTER, Vertical.TOP); } } + + // Keep track of text occupation for layout + if (textBounds != null) + wholeBounds.add(textBounds); + } } + /** + * Select an Y axis for ticks and labels rendering if conditions are met (range > 0 and axis + * layout configured to display this axis) + */ public void drawTicksAndLabelsY(IPainter painter) { if (yrange > 0 && layout.isYTickLabelDisplayed()) { - - //2D case - if (view != null && view.is2D()) { - BoundingBox3d bbox = - ticks.drawTicks(painter, 2, AXE_Y, layout.getYTickColor(), Horizontal.LEFT, Vertical.GROUND); - wholeBounds.add(bbox); - } - + + BoundingBox3d textBounds = null; + + // 2D case + if (is2DWithY()) { + if (view.is2D_XY()) { + textBounds = ticks.drawTicks(painter, EDGE_2, AXE_Y, layout.getYTickColor(), + Horizontal.LEFT, Vertical.CENTER); + } else if (view.is2D_YZ()) { + textBounds = ticks.drawTicks(painter, EDGE_2, AXE_Y, layout.getYTickColor(), + Horizontal.CENTER, Vertical.GROUND); + } + } + // 3D case - else { - int yselect = findClosestYaxe(painter.getCamera()); - if (yselect >= 0) { - BoundingBox3d bbox = ticks.drawTicks(painter, yselect, AXE_Y, layout.getYTickColor()); - wholeBounds.add(bbox); + else if (is3D()) { + + int edgeId = findClosestYaxe(painter.getCamera()); + + if (edgeId >= 0) { + + textBounds = ticks.drawTicks(painter, edgeId, AXE_Y, layout.getYTickColor()); } else { - // HACK: handles "on top" view, when all face of cube are - // drawn, which forbid to select an axe automatically - BoundingBox3d bbox = ticks.drawTicks(painter, 1, AXE_Y, layout.getYTickColor(), + // handles "on top" view, when all face of cube are drawn, which forbid to select an axe + // automatically + textBounds = ticks.drawTicks(painter, EDGE_1, AXE_Y, layout.getYTickColor(), Horizontal.RIGHT, Vertical.GROUND); - wholeBounds.add(bbox); } } + + // Keep track of text occupation for layout + if (textBounds != null) + wholeBounds.add(textBounds); } } + /** + * Select a Z axis for ticks and labels rendering if conditions are met (range > 0 and axis + * layout configured to display this axis) + */ public void drawTicksAndLabelsZ(IPainter painter) { if (zrange > 0 && layout.isZTickLabelDisplayed()) { + + BoundingBox3d textBounds = null; + + // 2D case + if (is2DWithZ()) { + if (view.is2D_XZ()) { + textBounds = ticks.drawTicks(painter, EDGE_2, AXE_Z, layout.getZTickColor(), + Horizontal.LEFT, Vertical.GROUND); + } else if (view.is2D_YZ()) { + textBounds = ticks.drawTicks(painter, EDGE_3, AXE_Z, layout.getZTickColor(), + Horizontal.LEFT, Vertical.GROUND); + } + } + // 3D case only - if (view != null && view.is3D()) { - int zselect = findClosestZaxe(painter.getCamera()); - if (zselect >= 0) { - BoundingBox3d bbox = ticks.drawTicks(painter, zselect, AXE_Z, layout.getZTickColor()); - wholeBounds.add(bbox); + else if (is3D()) { + + int edgeId = findClosestZaxe(painter.getCamera()); + + if (edgeId >= 0) { + textBounds = ticks.drawTicks(painter, edgeId, AXE_Z, layout.getZTickColor()); } } - - // Do not render Z ticks in 2D + + // Keep track of text occupation for layout + if (textBounds != null) + wholeBounds.add(textBounds); + } } /////////////////////////// + /** returns true if view is configured for a 3D chart where all axes are visible. */ + protected boolean is3D() { + return view != null && view.is3D(); + } + + /** returns true if view is configured for a 2D chart where X is a visible axis. */ + protected boolean is2DWithX() { + return view != null && (view.is2D_XY() || view.is2D_XZ()); + } + + /** returns true if view is configured for a 2D chart where Y is a visible axis. */ + protected boolean is2DWithY() { + return view != null && (view.is2D_XY() || view.is2D_YZ()); + } + + /** returns true if view is configured for a 2D chart where Z is a visible axis. */ + protected boolean is2DWithZ() { + return view != null && (view.is2D_XZ() || view.is2D_YZ()); + } + protected boolean isZAxeLabelDisplayed(int direction) { return isZ(direction) && layout.isZAxisLabelDisplayed(); } @@ -376,7 +453,7 @@ else if (isY(direction)) else return layout.getZTicks(); } - + /* ************************************************/ /* ************** AXIS SELECTION ******************/ @@ -820,7 +897,7 @@ protected void setAxeBox(float xmin, float xmax, float ymin, float ymax, float z * 5);6 */ } - + /* ************************************************/ /* ************ AXIS GETTER/SETTER ****************/ /* ************************************************/ @@ -947,4 +1024,29 @@ public BoundingBox3d.Corners getCorners() { return getBounds().getCorners(); } + public AxisLabelRotator getLabelRotator() { + return rotateLabel; + } + + public void setLabelRotator(AxisLabelRotator rotateLabel) { + this.rotateLabel = rotateLabel; + } + + public AxisLabelProcessor getLabelProcessor() { + return labels; + } + + public void setLabelProcessor(AxisLabelProcessor labels) { + this.labels = labels; + } + + public AxisTickProcessor getTickProcessor() { + return ticks; + } + + public void setTickProcessor(AxisTickProcessor ticks) { + this.ticks = ticks; + } + + } diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisLabelProcessor.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisLabelProcessor.java index a7dc5df0a..7711f090d 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisLabelProcessor.java +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisLabelProcessor.java @@ -42,41 +42,66 @@ protected void drawAxisLabel(IPainter painter, int direction, Color color, BoundingBox3d labelBounds = axis.textRenderer.drawText(painter, layout.getFont(), label, position, rotation, align, color, offset); + if (labelBounds != null) ticksTxtBounds.add(labelBounds); } } + /** Return an axis label alignment suitable for the type of chart : 3D, 2D XY, 2D XZ, 2D YZ */ protected TextAlign getAxisLabelTextAlign(int direction) { - // 2D case - if (axis.getView().is2D()) { - // X axis label + // 3D case + if (axis.getView().is3D()) { + return new TextAlign(Horizontal.CENTER, Vertical.CENTER); + } + + // 2D case XY + else if (axis.getView().is2D_XY()) { + // horizontal axis if (axis.isX(direction)) { return new TextAlign(Horizontal.CENTER, Vertical.BOTTOM); } - // Y axis label + // vertical axis else if (axis.isY(direction)) { return new TextAlign(Horizontal.LEFT, Vertical.CENTER); } - // Z axis label : should be hidden - else { - return new TextAlign(Horizontal.CENTER, Vertical.CENTER); + } + + // 2D case XZ + else if (axis.getView().is2D_XZ()) { + // horizontal axis + if (axis.isX(direction)) { + return new TextAlign(Horizontal.CENTER, Vertical.BOTTOM); + } + // vertical axis + else if (axis.isZ(direction)) { + return new TextAlign(Horizontal.LEFT, Vertical.CENTER); } } - // 3D case - else { - return new TextAlign(Horizontal.CENTER, Vertical.CENTER); + // 2D case XZ + else if (axis.getView().is2D_YZ()) { + // horizontal axis + if (axis.isY(direction)) { + return new TextAlign(Horizontal.CENTER, Vertical.BOTTOM); + } + // vertical axis + else if (axis.isZ(direction)) { + return new TextAlign(Horizontal.LEFT, Vertical.CENTER); + } } + // Default + return new TextAlign(Horizontal.CENTER, Vertical.CENTER); } /** - * Compute the offset to apply to a vertical Z label to avoid covering the tick labels. + * Computes the offset to apply to a vertical Z label to avoid covering the tick labels in the case + * of a 3D chart. * - * Retrieve pixel scale in view to adapt margin + * Retrieves pixel scale in view to adapt margin. * * @param painter * @param info @@ -127,7 +152,7 @@ protected Coord2d axisLabelOffsetVertical(IPainter painter, AxisRenderingInfo in } /** - * Offset for oblique labels + * Offset for oblique labels. * * @param painter * @param info @@ -236,14 +261,10 @@ protected String axisLabel(int dimension) { * */ protected Coord3d axisLabelPosition(int direction, float tickLength, Coord3d pos, Coord3d dir) { - - if (axis.getView().is3D()) { - return axisLabelPosition_3D(direction, tickLength, pos, dir); } else { return axisLabelPosition_2D(direction, pos); - } } @@ -261,56 +282,69 @@ protected Coord3d axisLabelPosition_2D(int direction, Coord3d pos) { View view = axis.getView(); AxisLayout axisLayout = axis.getLayout(); View2DProcessing processing2D = view.get2DProcessing(); - + Coord2d pixelScale = view.getPixelScale(); // for Y axis label, we do a shift along X dimension - // the shift will be from the Y axis segment center, - // hence a positive shift is equivalent to moving the + // the shift will be from the Y axis segment center, + // hence a positive shift is equivalent to moving the // label to the left - + float xShiftPx = processing2D.getVerticalTickDistance(); xShiftPx += processing2D.getTickTextWidth(); xShiftPx += processing2D.getVerticalAxisDistance(); - + // A non horizontal - hence rotated - text needs to be offset // Non horizontal being either vertical or parallel to axis, we always // consider it as a 90° rotation. // see https://github.com/jzy3d/jzy3d-api/issues/283 - - boolean isVertical = !LabelOrientation.HORIZONTAL.equals(axisLayout.getYAxisLabelOrientation()); + + String verticalAxisLabel = null; + LabelOrientation verticalAxisLayout = null; + + if (view.is2D_XY()) { + verticalAxisLabel = axisLayout.getYAxisLabel(); + verticalAxisLayout = axisLayout.getYAxisLabelOrientation(); + } else if (view.is2D_XZ() || view.is2D_YZ()) { + verticalAxisLabel = axisLayout.getZAxisLabel(); + verticalAxisLayout = axisLayout.getZAxisLabelOrientation(); + } + + boolean isVertical = !LabelOrientation.HORIZONTAL.equals(verticalAxisLayout); boolean isEmulGL = !view.getCanvas().isNative(); - - if(isVertical) { - // consider the rotation & offset due to vertical text - int textLength = view.getPainter().getTextLengthInPixels(axisLayout.getFont(), axisLayout.getYAxisLabel()); - + + if (isVertical) { + // consider the rotation & offset due to vertical text + int textLength = + view.getPainter().getTextLengthInPixels(axisLayout.getFont(), verticalAxisLabel); + // hack the emulgl vertical Y axis case by ignoring pixel scale for text - if(isEmulGL) + if (isEmulGL) { textLength /= pixelScale.y; + } // move the text next to its anchor point, since it has been rotated - // from the text center and not from the anchor point that is on the + // from the text center and not from the anchor point that is on the // right most letter side. - xShiftPx -= textLength/2; - + xShiftPx -= textLength / 2; + int textHeight = axisLayout.getFont().getHeight(); - + // hack the emulgl vertical Y axis case by ignoring pixel scale for text - if(isEmulGL) { - textHeight /= (pixelScale.x*2); + if (isEmulGL) { + textHeight /= (pixelScale.x * 2); // I can't explain why we need this x2 factor for emulglGL vertical text } - - xShiftPx += (textHeight/2); + + xShiftPx += (textHeight / 2); } // for X axis label, we do a shift along Y dimension float yShiftPx = processing2D.getHorizontalTickDistance(); yShiftPx += processing2D.getTickTextHeight(); yShiftPx += processing2D.getHorizontalAxisDistance(); - - + + // Now convert this pixel shift into real world coordinates // shift Coord2d modelToScreenRatio = processing2D.getModelToScreen(); @@ -322,17 +356,43 @@ protected Coord3d axisLabelPosition_2D(int direction, Coord3d pos) { double ylab = 0; double zlab = 0; - // Build the axis label position - if (axis.isX(direction)) { - xlab = axis.center.x; - ylab = pos.y - yShift; - zlab = pos.z; - } else if (axis.isY(direction)) { - xlab = pos.x - xShift; - ylab = axis.center.y; - zlab = pos.z; + // Build the axis label position for each 2D chart projection + + if (view.is2D_XY()) { + + if (axis.isX(direction)) { + xlab = axis.center.x; + ylab = pos.y - yShift; + zlab = pos.z; + } else if (axis.isY(direction)) { + xlab = pos.x - xShift; + ylab = axis.center.y; + zlab = pos.z; + } + } else if (view.is2D_XZ()) { + if (axis.isX(direction)) { + xlab = axis.center.x; + ylab = pos.y; + zlab = pos.z - yShift; + } else if (axis.isZ(direction)) { + xlab = pos.x - xShift; + ylab = pos.y; + zlab = axis.center.z; + } + } else if (view.is2D_YZ()) { + if (axis.isY(direction)) { + xlab = pos.x; + ylab = axis.center.y; + zlab = pos.z - yShift; + } else if (axis.isZ(direction)) { + xlab = pos.x; + ylab = pos.y - xShift; + zlab = axis.center.z; + } } + return new Coord3d(xlab, ylab, zlab); + } /** diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisTickProcessor.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisTickProcessor.java index da9d6f620..7ce221bac 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisTickProcessor.java +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/AxisTickProcessor.java @@ -25,16 +25,18 @@ public class AxisTickProcessor { protected AxisBox axis; protected AxisLayout layout; protected AxisLabelProcessor labels; - + protected View2DProcessing view2D; - + + public AxisTickProcessor() { + } + public AxisTickProcessor(AxisBox axis) { - this.axis = axis; - this.layout = axis.getLayout(); - this.labels = axis.labels; - + setAxis(axis); } + + public BoundingBox3d drawTicks(IPainter painter, int axis, int dimension, Color color) { return drawTicks(painter, axis, dimension, color, null, null); } @@ -62,16 +64,6 @@ public BoundingBox3d drawTicks(IPainter painter, int axis, int dimension, Color quad_1 = this.axis.axeZquads[axis][1]; } - // Override tick labels alignment when 2D so that they appear centered - if(this.axis.getView().is2D()) { - if (this.axis.isX(dimension)) { - hal = Horizontal.CENTER; - } - else if(this.axis.isY(dimension)) { - val = Vertical.CENTER; - } - } - // -------------------------------------------------------------- // Computes POSition of ticks lying on the selected axe (i.e. 1st point of the tick line) @@ -153,35 +145,69 @@ protected float getTickLength3D_OrComputeTickLength2D(IPainter painter, int dime View2DProcessing processing2D = view.get2DProcessing(); Coord2d modelToScreen = processing2D.getModelToScreen(); - // ------------------------------- - // Distance of X tick labels to tick axis - // according to Y range, canvas height and font height - - if (this.axis.isX(dimension)) { - float fontHeight = processing2D.getTickTextHeight(); - float worldTickLen = (processing2D.getHorizontalTickDistance() + fontHeight) * modelToScreen.y; - float range = this.axis.getBounds().getYRange().getRange(); - float magic = range/worldTickLen; - return magic; - } - - // ------------------------------- - // Distance of Y tick labels to tick axis - // according to X range, canvas width and font width + float fontHeight = processing2D.getTickTextHeight(); + float horizontalTickLen = (processing2D.getHorizontalTickDistance() + fontHeight) * modelToScreen.y; + float verticalTickLen = processing2D.getVerticalTickDistance() * modelToScreen.x; + - else if (this.axis.isY(dimension)) { - float worldTickLen = processing2D.getVerticalTickDistance() * modelToScreen.x; - float range = this.axis.getBounds().getXRange().getRange(); - float magic = range/worldTickLen; - return magic; - } + if(view.is2D_XY()) { + // ------------------------------- + // Distance of X tick labels to tick axis + // according to Y range, canvas height and font height + + if (this.axis.isX(dimension)) { + return this.axis.getBounds().getYRange().getRange()/horizontalTickLen; + } + + // ------------------------------- + // Distance of Y tick labels to tick axis + // according to X range, canvas width and font width + + else if (this.axis.isY(dimension)) { + return this.axis.getBounds().getXRange().getRange()/verticalTickLen; + } - // ------------------------------- - // Z case should never occur in 2D - else { - return 1; + } else if(view.is2D_XZ()) { + + // ------------------------------- + // Distance of X tick labels to tick axis + // according to Z range, canvas height and font height + + if (this.axis.isX(dimension)) { + return this.axis.getBounds().getZRange().getRange()/horizontalTickLen; + } + + // ------------------------------- + // Distance of Z tick labels to tick axis + // according to X range, canvas width and font width + + else if (this.axis.isZ(dimension)) { + return this.axis.getBounds().getXRange().getRange()/verticalTickLen; + } + + } else if(view.is2D_YZ()) { + + // ------------------------------- + // Distance of X tick labels to tick axis + // according to Z range, canvas height and font height + + if (this.axis.isY(dimension)) { + return this.axis.getBounds().getZRange().getRange()/horizontalTickLen; + } + + // ------------------------------- + // Distance of Z tick labels to tick axis + // according to X range, canvas width and font width + + else if (this.axis.isZ(dimension)) { + return this.axis.getBounds().getYRange().getRange()/verticalTickLen; + } + } - + + + return Float.NaN; + } // ------------------------------------------------------- @@ -303,6 +329,8 @@ public AxisRenderingInfo drawAxisTicks(IPainter painter, int dimension, Color co drawAxisTickNumericLabel(painter, dimension, color, hAlign, vAlign, ticksTxtBounds, tickLabel, tickLabelPosition); + //System.out.println(tickLabelPosition); + // Draw the tick line if (layout.isTickLineDisplayed()) { drawTickLine(painter, color, tickStartPosition, tickLabelPosition); @@ -416,4 +444,16 @@ protected Coord3d tickDirection(int quad_0, int quad_1) { return new Coord3d(xdir, ydir, zdir); } + + //////////////////////////// + + public void setAxis(AxisBox axis) { + this.axis = axis; + this.layout = axis.getLayout(); + this.labels = axis.labels; + } + + public AxisBox getAxis() { + return axis; + } } diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/layout/AxisLayout.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/layout/AxisLayout.java index 9e169c0dd..a9c931366 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/layout/AxisLayout.java +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/layout/AxisLayout.java @@ -162,9 +162,8 @@ public int getRightMostXTickLabelWidth(IPainter painter) { /** * Return the maximum text length in pixel as displayed on screen, given the current ticks and - * renderer + * renderer on the Y axis */ - public int getMaxYTickLabelWidth(IPainter painter) { int maxWidth = 0; @@ -180,6 +179,25 @@ public int getMaxYTickLabelWidth(IPainter painter) { } + /** + * Return the maximum text length in pixel as displayed on screen, given the current ticks and + * renderer on the Z axis + */ + public int getMaxZTickLabelWidth(IPainter painter) { + int maxWidth = 0; + + for (double t : getZTicks()) { + String label = getZTickRenderer().format(t); + + int width = painter.getTextLengthInPixels(font, label); + if (width > maxWidth) { + maxWidth = width; + } + } + return maxWidth; + + } + // ********************* TICKS PROPERTIES ************************ // diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/layout/renderers/TrigonometricTickRenderer.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/layout/renderers/TrigonometricTickRenderer.java index 29a48c533..dfab6160b 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/layout/renderers/TrigonometricTickRenderer.java +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/layout/renderers/TrigonometricTickRenderer.java @@ -6,8 +6,10 @@ * Render ticks as multiple of π if the input value to be formated is not further to a multiple of π * than delta. * - * @author Martin Pernollet + * + * * + * @author Martin Pernollet */ public class TrigonometricTickRenderer implements ITickRenderer { static final double PI = Math.PI; @@ -47,10 +49,6 @@ protected void autoDelta(int maxDenominator) { delta = (Math.PI/maxDenominator)/10; } - /* - * public static void main(String[] args) { System.out.println(π); } - */ - @Override public String format(double value) { if (eq(value, 0)) { diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/layout/renderers/doc-files/trigonometric-circle.png b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/layout/renderers/doc-files/trigonometric-circle.png new file mode 100644 index 000000000..4efce20c0 Binary files /dev/null and b/jzy3d-core/src/main/java/org/jzy3d/plot3d/primitives/axis/layout/renderers/doc-files/trigonometric-circle.png differ diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/Camera.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/Camera.java index 8bb83e639..819fe0f96 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/Camera.java +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/Camera.java @@ -5,8 +5,8 @@ import java.util.Vector; import java.util.function.Predicate; import org.jzy3d.colors.Color; +import org.jzy3d.maths.Array; import org.jzy3d.maths.BoundingBox2d; -import org.jzy3d.maths.Coord2d; import org.jzy3d.maths.Coord3d; import org.jzy3d.maths.PolygonArray; import org.jzy3d.painters.IPainter; @@ -372,10 +372,19 @@ public Coord3d screenToModel(IPainter painter, Coord3d screen) { int viewport[] = painter.getViewPortAsInt(); float modelView[] = painter.getModelViewAsFloat(); float projection[] = painter.getProjectionAsFloat(); + + //Array.print("Camera.screenToModel : viewport : ", viewport); + //Array.print("Camera.screenToModel : modelView : ", modelView); + //Array.print("Camera.screenToModel : projection : ", projection); + + //double modelView[] = painter.getModelViewAsDouble(); + //double projection[] = painter.getProjectionAsDouble(); float worldcoord[] = new float[3];// wx, wy, wz;// returned xyz coords boolean s = painter.gluUnProject(screen.x, screen.y, screen.z, modelView, 0, projection, 0, viewport, 0, worldcoord, 0); + + if (!s) failedProjection("Could not retrieve screen coordinates in model."); @@ -685,6 +694,8 @@ protected void projectionOrtho2D() { ortho.update(renderingSquare.xmin(), renderingSquare.xmax(), renderingSquare.ymin(), renderingSquare.ymax(), near, far); + //System.out.println("Camera:" + ortho.toString()); + //System.out.println("Camera:" + up); //painter.glOrtho(left, right, bottom, top, near, far); diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/View.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/View.java index f3d99e747..4b80963a0 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/View.java +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/View.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.List; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jzy3d.chart.Chart; @@ -62,6 +61,8 @@ */ public class View { + + protected static Logger LOGGER = LogManager.getLogger(View.class); @@ -123,13 +124,23 @@ public class View { protected boolean initialized = false; // constants - public static final float PI_div2 = (float) Math.PI / 2; + public static final float PI = (float)Math.PI; + public static final float PI_div2 = (float)Math.PI/2; public static final float DISTANCE_DEFAULT = 2000; + private static final float AZIMUTH_FACING_X_DECREASING = PI_div2; + private static final float AZIMUTH_FACING_X_INCREASING = -PI_div2; + private static final float AZIMUTH_FACING_Y_DECREASING = PI; + private static final float AZIMUTH_FACING_Y_INCREASING = 0; + + private static final float ELEVATION_ON_TOP = PI_div2; + private static final float ELEVATION_0 = 0; + private static final float ELEVATION_ON_BOTTOM = -PI_div2; + /** A viewpoint allowing to have min X and Y values near viewer, growing toward horizon. */ public static final Coord3d VIEWPOINT_X_Y_MIN_NEAR_VIEWER = - new Coord3d((Math.PI) + (Math.PI / 3), Math.PI / 3, DISTANCE_DEFAULT); + new Coord3d(PI + (Math.PI / 3), Math.PI / 3, DISTANCE_DEFAULT); /** A viewpoint where two corners of the axis box touch top and bottom lines of the canvas. */ public static final Coord3d VIEWPOINT_AXIS_CORNER_TOUCH_BORDER = @@ -242,12 +253,12 @@ public void pixelScaleChanged(double pixelScaleX, double pixelScaleY) { // Store current pixel scale pixelScale.x = Double.isNaN(pixelScaleX) ? 1 : (float) pixelScaleX; pixelScale.y = Double.isNaN(pixelScaleY) ? 1 : (float) pixelScaleY; - - if(pixelScale.x<=0) + + if (pixelScale.x <= 0) pixelScale.x = 1; - if(pixelScale.y<=0) + if (pixelScale.y <= 0) pixelScale.y = 1; - + // Convert pixel scale to HiDPI status if (pixelScaleX <= 1) { hidpi = HiDPI.OFF; @@ -258,22 +269,22 @@ public void pixelScaleChanged(double pixelScaleX, double pixelScaleY) { // Edit font size accordingly axis.getLayout().applyFontSizePolicy(); - - //getLayout().update(chart); - - for(ILegend legend : scene.getGraph().getLegends()) { + + // getLayout().update(chart); + + for (ILegend legend : scene.getGraph().getLegends()) { legend.updatePixelScale(pixelScale); - //legend.updateMinimumDimension(painter); + // legend.updateMinimumDimension(painter); } - + // -------------------------- // Trigger new render for different reasons // EmulGL need this to layout colorbar properly // Native need this to /sometime/ get the good resolution - //chart.render(2); + // chart.render(2); - //System.out.println("View :update pix scale"); + // System.out.println("View :update pix scale"); } }); } @@ -315,20 +326,23 @@ public Coord3d projectMouse(int x, int y) { return projectMouse(screen); } - /** Perform the 3d projection of a 2d coordinate. The z component of the screen coordinate should be between 0 and 1, representing the position in the depth range*/ + /** + * Perform the 3d projection of a 2d coordinate. The z component of the screen coordinate should + * be between 0 and 1, representing the position in the depth range + */ public Coord3d projectMouse(Coord3d screen) { painter.acquireGL(); Coord3d p = cam.screenToModel(painter, screen); painter.releaseGL(); return p; } - + public Coord3d projectMouse(Coord2d mouse) { - return projectMouse((int)mouse.x, (int)mouse.y); + return projectMouse((int) mouse.x, (int) mouse.y); } public Coord3d projectMouse(IntegerCoord2d mouse) { - return projectMouse(mouse.x, mouse.y); + return projectMouse(mouse.x, mouse.y); } /** @@ -937,11 +951,6 @@ public void updateCameraWithoutShooting(ViewportConfiguration viewport, Bounding Coord3d cameraUp = computeCameraUp(viewpoint); Coord3d cameraEye = computeCameraEye(cameraTarget, viewmode, viewpoint); - // Force the up vector of the camera to grow along Y axis - if (is2D()) { - cameraUp = new Coord3d(0.0, 1.0, 0.0); - } - cam.setPosition(cameraEye, cameraTarget, cameraUp, scaling); computeCameraRenderingVolume(cam, viewport, bounds); @@ -965,16 +974,38 @@ protected Coord3d computeCameraEye(Coord3d target) { return computeCameraEye(target, viewMode, viewpoint); } + /** + * Compute the camera's eye position in cartesian coordinates based on viewmode, target, and a + * viewpoint given in polar coordinates around the target + */ protected Coord3d computeCameraEye(Coord3d target, ViewPositionMode viewmode, Coord3d viewpoint) { if (viewmode == ViewPositionMode.FREE) { return computeCameraEyeFree(viewpoint, target); - } else if (viewmode == ViewPositionMode.TOP) { - return computeCameraEyeTop(viewpoint, target); } else if (viewmode == ViewPositionMode.PROFILE) { return computeCameraEyeProfile(viewpoint, target); + } else if (viewmode == ViewPositionMode.TOP) { + return computeCameraEyeTop(viewpoint, target); + } else if (viewmode == ViewPositionMode.XZ) { + return computeCameraEyeXZ(viewpoint, target); + } else if (viewmode == ViewPositionMode.YZ) { + return computeCameraEyeYZ(viewpoint, target); } else throw new RuntimeException("Unsupported ViewMode: " + viewmode); } + + /*protected Coord3d computeCameraEyeXZ_DEBUG(Coord3d viewpoint, Coord3d target) { + Coord3d eye = viewpoint; + //eye.x = AZIMUTH_FACING_X_DECREASING; // facing X so that value decrease + eye.x = AZIMUTH_FACING_X_INCREASING; // facing X so that value increase + eye.y = ELEVATION_0 + 0.0001f; // on side + eye = eye.cartesian().add(target); + return eye; + }*/ + + + protected Coord3d computeCameraEyeFree(Coord3d viewpoint, Coord3d target) { + return viewpoint.cartesian().add(target); + } /** * @@ -984,25 +1015,55 @@ protected Coord3d computeCameraEye(Coord3d target, ViewPositionMode viewmode, Co */ protected Coord3d computeCameraEyeProfile(Coord3d viewpoint, Coord3d target) { Coord3d eye = viewpoint; - eye.y = 0; + eye.y = ELEVATION_0; eye = eye.cartesian().add(target); return eye; } protected Coord3d computeCameraEyeTop(Coord3d viewpoint, Coord3d target) { Coord3d eye = viewpoint; - eye.x = -(float) Math.PI / 2; // on x - eye.y = (float) Math.PI / 2; // on top + eye.x = AZIMUTH_FACING_X_INCREASING; // on x + eye.y = ELEVATION_ON_TOP; // on top eye = eye.cartesian().add(target); return eye; } - protected Coord3d computeCameraEyeFree(Coord3d viewpoint, Coord3d target) { - return viewpoint.cartesian().add(target); + protected Coord3d computeCameraEyeYZ(Coord3d viewpoint, Coord3d target) { + Coord3d eye = viewpoint; + //eye.x = AZIMUTH_FACING_Y_DECREASING; // facing Y so that value decrease + eye.x = AZIMUTH_FACING_Y_INCREASING; // facing Y so that value decrease + eye.y = ELEVATION_0; // on side + + // see https://github.com/jzy3d/jzy3d-api/issues/286 + if(!canvas.isNative() && JGL_INVERSE_MATRIX_WORKAROUND) { + eye.x += 0.01f; + eye.y += 0.01f; + } + + eye = eye.cartesian().add(target); + return eye; } + protected Coord3d computeCameraEyeXZ(Coord3d viewpoint, Coord3d target) { + Coord3d eye = viewpoint; + //eye.x = AZIMUTH_FACING_X_DECREASING; // facing X so that value decrease + eye.x = AZIMUTH_FACING_X_INCREASING; // facing X so that value increase + eye.y = ELEVATION_0; // on side + + // see https://github.com/jzy3d/jzy3d-api/issues/286 + if(!canvas.isNative() && JGL_INVERSE_MATRIX_WORKAROUND) { + eye.y += 0.0001f; + } + + eye = eye.cartesian().add(target); + return eye; + } + + protected boolean JGL_INVERSE_MATRIX_WORKAROUND = true; + + protected void triggerCameraUpEvents(Coord3d viewpoint) { - if (Math.abs(viewpoint.y) == PI_div2) { // handle "on-top" events + if (Math.abs(viewpoint.y) == ELEVATION_ON_TOP) { // handle "on-top" events if (!wasOnTopAtLastRendering) { wasOnTopAtLastRendering = true; fireViewOnTopEvent(true); @@ -1014,16 +1075,39 @@ protected void triggerCameraUpEvents(Coord3d viewpoint) { } } + /** + * Compute the direction of the top of the camera relative to its center. + * + * Coord3d(0, 0, 1) means a vector parallel to Z axis, so camera is looking horizontally + * Coord3d(0, 1, 0) means a vector parallel to Y axis, so camera is looking vertically + */ protected Coord3d computeCameraUp(Coord3d viewpoint) { - if (Math.abs(viewpoint.y) == PI_div2) { // handle on top - Coord2d direction = new Coord2d(viewpoint.x, viewpoint.z).cartesian(); - if (viewpoint.y > 0) { - return new Coord3d(-direction.x, -direction.y, 0); - } else { - return new Coord3d(direction.x, direction.y, 0); + + // 2D cases + if (is2D_XY()) { + return new Coord3d(0, 1, 0); // use y axis as up vector + } + else if(is2D_XZ() || is2D_YZ()) { + return new Coord3d(0, 0, 1); // use z axis as up vector + } + + // -------- + // 3D case + else { + // handle "on top" or "on bottom" 3D view + if (Math.abs(viewpoint.y) == ELEVATION_ON_TOP) { + Coord2d direction = new Coord2d(viewpoint.x, viewpoint.z).cartesian(); + if (viewpoint.y > 0) { + return new Coord3d(-direction.x, -direction.y, 0); + } else { + return new Coord3d(direction.x, direction.y, 0); + } + } + // handle standard 3D view + else { + // use z axis as up vector, if not on top or bottom of the axis + return new Coord3d(0, 0, 1); } - } else { - return new Coord3d(0, 0, 1); // use z axis as up vector, if not on top } } @@ -1078,77 +1162,71 @@ protected void computeCamera3D_RenderingSphere(Camera cam, ViewportConfiguration protected void computeCamera2D_RenderingSquare(Camera cam, ViewportConfiguration viewport, BoundingBox3d bounds) { - // bounds = getSceneGraphBounds(); - Coord2d c = AbstractViewportManager.apply_WindowsHiDPI_Workaround(getPainter(), viewport.width, viewport.height); - - viewport = viewport.clone(); - viewport.width = (int)c.x; - viewport.height = (int)c.y; + // ----------------------------------------------- + // Hack Windows HiDPI issue on AWT - view2DProcessing.apply(viewport, bounds); + Coord2d c = AbstractViewportManager.apply_WindowsHiDPI_Workaround(getPainter(), viewport.width, + viewport.height); + viewport = viewport.clone(); + viewport.width = (int) c.x; + viewport.height = (int) c.y; - // The rendering squared dimension will be applied at the current camera position - float xrange = bounds.getXRange().getRange(); - float yrange = bounds.getYRange().getRange(); - - float xmin = -xrange / 2 - view2DProcessing.marginLeftModel; - float xmax = +xrange / 2 + view2DProcessing.marginRightModel; - float ymin = -yrange / 2 - view2DProcessing.marginBottomModel; - float ymax = +yrange / 2 + view2DProcessing.marginTopModel; - - BoundingBox2d xySquare = new BoundingBox2d(xmin, xmax, ymin, ymax); + // ----------------------------------------------- + // Computes the 2D layout + + view2DProcessing.apply(viewport, bounds); + // ----------------------------------------------- + // The rendering squared dimension will be applied + // at the current camera position - // Z range float dist = (float) cam.getEye().distance(cam.getTarget()); - float zmax = dist - bounds.getZRange().getMax() - 1; - float zmin = dist - bounds.getZRange().getMin() + 1; - - // configure camera rendering volume - cam.setRenderingSquare(xySquare, zmax, zmin); - } - - - /** - * Only used for top/2D views. Performs a rendering to get the whole bounds occupied by the Axis - * Box and its text labels. - * - * Then edit the camera position to fit within this new bounds AND modify the clipping planes. - * - * @param painter - * @param viewport - */ - @Deprecated - protected void correctCameraPositionForIncludingTextLabels(IPainter painter, - ViewportConfiguration viewport) { - cam.setViewPort(viewport); - cam.shoot(painter, cameraMode); - axis.draw(painter); - clear(); - - // Base camera radius on {@link AxisBox#getWholeBounds} to ensure we display text labels - // complete - BoundingBox3d newBounds = axis.getWholeBounds().scale(scaling); + float near = 0; // max depth + float far = 0; // min depth + float hrange = 0; // horizontal range of 2D chart + float vrange = 0; // vertical range of 2D chart + float drange = 0; // depth range of 2D chart + float offset = 1; // expand the clipping plane to ensure we do not cut the axis + + if(is2D_XY()) { + // Z range for processing camera clipping planes + drange = bounds.getZRange().getRange(); + // X/Y range for processing rendering square + hrange = bounds.getXRange().getRange(); + vrange = bounds.getYRange().getRange(); + } + else if(is2D_XZ()) { + // Y range for processing camera clipping planes + drange = bounds.getYRange().getRange(); + // X/Z range for processing rendering square + hrange = bounds.getXRange().getRange(); + vrange = bounds.getZRange().getRange(); + } else if(is2D_YZ()) { + // X range for processing camera clipping planes + drange = bounds.getXRange().getRange(); + // Y/Z range for processing rendering square + hrange = bounds.getYRange().getRange(); + vrange = bounds.getZRange().getRange(); + } + + near = dist - drange/2 - offset; + far = dist + drange/2 + offset; - // Compute camera position based on whole bounds - Coord3d target = newBounds.getCenter(); - Coord3d eye = viewpoint.cartesian().add(target); - cam.setPosition(eye, target); + float hmin = -hrange / 2 - view2DProcessing.marginLeftModel; + float hmax = +hrange / 2 + view2DProcessing.marginRightModel; + float vmin = -vrange / 2 - view2DProcessing.marginBottomModel; + float vmax = +vrange / 2 + view2DProcessing.marginTopModel; - // 2D case - if (is2D()) { - computeCamera2D_RenderingSquare(cam, viewport, newBounds); - } + BoundingBox2d xySquare = new BoundingBox2d(hmin, hmax, vmin, vmax); + + // ----------------------------------------------- + // Configure camera rendering volume - // 3D case - else { - computeCamera3D_RenderingSphere(cam, viewport, newBounds); - } + cam.setRenderingSquare(xySquare, near, far); } - /* AXE BOX RENDERING */ protected void renderAxeBox() { @@ -1332,9 +1410,21 @@ public ViewPositionMode getViewMode() { } public boolean is2D() { + return is2D_XY() || is2D_XZ() || is2D_YZ(); + } + + public boolean is2D_XY() { return ViewPositionMode.TOP.equals(getViewMode()); } + public boolean is2D_XZ() { + return ViewPositionMode.XZ.equals(getViewMode()); + } + + public boolean is2D_YZ() { + return ViewPositionMode.YZ.equals(getViewMode()); + } + public boolean is3D() { return !is2D(); } @@ -1375,7 +1465,28 @@ public void setViewPoint(Coord3d polar, boolean updateView) { public void setViewPoint(Coord3d polar) { setViewPoint(polar, true); } + + /** + * Set the viewpoint using polar coordinates relative to the target (i.e. the center of the + * scene). Only X and Y dimensions are required, as the distance to center will be computed + * automatically by {@link updateCamera()}. + * + * The input coordinate is polar and considers + *