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 + * + * + */ + public void setViewPoint(double azimuth, double elevation, boolean updateView) { + setViewPoint(new Coord3d(azimuth, elevation, 0), updateView); + } + public void setViewPoint(double azimuth, double elevation) { + setViewPoint(new Coord3d(azimuth, elevation, 0), true); + } + + /** * Get the viewpoint. The Z dimension is the one defined by {@link updateCamera()}, which depends * on the view scaling. diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/View2D.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/View2D.java new file mode 100644 index 000000000..96b3a59fb --- /dev/null +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/View2D.java @@ -0,0 +1,5 @@ +package org.jzy3d.plot3d.rendering.view; + +public enum View2D { + XY, XZ, YZ +} diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/View2DProcessing.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/View2DProcessing.java index 87d0845de..cc45c0013 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/View2DProcessing.java +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/View2DProcessing.java @@ -14,6 +14,7 @@ import org.jzy3d.plot3d.primitives.axis.layout.LabelOrientation; import org.jzy3d.plot3d.rendering.legends.ILegend; import org.jzy3d.plot3d.rendering.view.layout.ViewAndColorbarsLayout; +import org.jzy3d.plot3d.rendering.view.modes.ViewPositionMode; /** * Process and store the layout of a 2D view having margins and axis labels defined by the @@ -71,14 +72,19 @@ public class View2DProcessing { // the overall margins on the width and height, summing all margin of each dimension - protected Area area; + protected Area marginArea; + // the bounds of the two dimensions of the 3D space that are currently viewed + protected Area spaceArea; + // the viewport dimensions + protected Area screenArea; - protected Coord2d modelToScreen; + protected Coord2d modelToScreen; + public View2DProcessing() { + } public View2DProcessing(View view) { - super(); this.view = view; } @@ -123,13 +129,29 @@ public void apply(ViewportConfiguration viewport, BoundingBox3d bounds) { if (view2DLayout.textAddMargin) { // consider space occupied by tick labels - tickTextWidth = axisLayout.getMaxYTickLabelWidth(painter); tickTextHeight = font.getHeight(); - // consider space occupied by the Y axis label - if (LabelOrientation.HORIZONTAL.equals(axisLayout.getYAxisLabelOrientation())) { + if(isXY()) { + tickTextWidth = axisLayout.getMaxYTickLabelWidth(painter); + } else if(isXZ() || isYZ()) { + tickTextWidth = axisLayout.getMaxZTickLabelWidth(painter); + } + + // consider space occupied by the vertical axis label + String leftAxisLabel = null; + LabelOrientation leftAxisOrientation = null; + + if(isXY()) { + leftAxisLabel = axisLayout.getYAxisLabel(); + leftAxisOrientation = axisLayout.getYAxisLabelOrientation(); + } else if(isXZ() || isYZ()) { + leftAxisLabel = axisLayout.getZAxisLabel(); + leftAxisOrientation = axisLayout.getZAxisLabelOrientation(); + } + + if (LabelOrientation.HORIZONTAL.equals(leftAxisOrientation)) { // horizontal Y axis involves considering the axis label width - axisTextWidth = painter.getTextLengthInPixels(font, axisLayout.getYAxisLabel()); + axisTextWidth = painter.getTextLengthInPixels(font, leftAxisLabel); } else { // vertical Y axis involves considering the axis label font height axisTextWidth = font.getHeight(); @@ -190,8 +212,6 @@ public void apply(ViewportConfiguration viewport, BoundingBox3d bounds) { marginBottomPx += horizontalAxisDistance; marginBottomPx += axisTextHeight; - //System.err.println("V2DProc : axisTextHeight : " + axisTextHeight + " tickHeight " + tickTextHeight + " H tick " + horizontalTickDistance + " H axis " + horizontalAxisDistance + " BOTTOM = " + marginBottomPx); - //System.err.println("V2DProc : axisTextWidth : " + axisTextWidth + " tickWidth " + tickTextWidth); // --------------------------------------------------- // case of a symetric layout requirement @@ -219,7 +239,6 @@ public void apply(ViewportConfiguration viewport, BoundingBox3d bounds) { for(ILegend legend: legends) { legend.updateMinimumDimension(painter); Dimension minDim = legend.getMinimumDimension(); - //System.out.println("View2DProcessing minDim " + minDim.width); marginRightPx += minDim.width; } } @@ -227,10 +246,29 @@ public void apply(ViewportConfiguration viewport, BoundingBox3d bounds) { // --------------------------------------------------- // The actual processing of margin - area = new Area(marginLeftPx + marginRightPx, marginTopPx + marginBottomPx); - - modelToScreen = getModelToScreenRatio(bounds, viewport, area); + marginArea = new Area(marginLeftPx + marginRightPx, marginTopPx + marginBottomPx); + screenArea = new Area(viewport.getWidth(), viewport.getHeight()); + + + // XY 2D view + if(isXY()) { + spaceArea = new Area(bounds.getXRange().getRange(), bounds.getYRange().getRange()); + } + // XZ 2D view + else if(isXZ()){ + spaceArea = new Area(bounds.getXRange().getRange(), bounds.getZRange().getRange()); + } + // YX 2D view + else if(isYZ()){ + spaceArea = new Area(bounds.getYRange().getRange(), bounds.getZRange().getRange()); + } + else { + throw new IllegalArgumentException("Irrelevant view mode : '" + view.getViewMode() + "'"); + } + + modelToScreen = getModelToScreenRatio(spaceArea, screenArea, marginArea); + // convert pixel margin to world coordinate to add compute the additional 3D space to grasp with // the camera marginLeftModel = marginLeftPx * modelToScreen.x; @@ -239,6 +277,18 @@ public void apply(ViewportConfiguration viewport, BoundingBox3d bounds) { marginBottomModel = marginBottomPx * modelToScreen.y; } + protected boolean isYZ() { + return ViewPositionMode.YZ.equals(view.getViewMode()); + } + + protected boolean isXZ() { + return ViewPositionMode.XZ.equals(view.getViewMode()); + } + + protected boolean isXY() { + return ViewPositionMode.TOP.equals(view.getViewMode()); + } + /** @@ -351,14 +401,14 @@ public Coord2d getModelToScreenRatio(Area space, Area canvas, Area margins) { float x = 1; - if (area.width != 0 && (canvas.width != margins.width)) { + if (marginArea.width != 0 && (canvas.width != margins.width)) { x = (((space.width * canvas.width) / (canvas.width - margins.width)) - space.width) / margins.width; } float y = 1; - if (area.height != 0 && (canvas.height != margins.height)) { + if (marginArea.height != 0 && (canvas.height != margins.height)) { y = (((space.height * canvas.height) / (canvas.height - margins.height)) - space.height) / margins.height; @@ -368,18 +418,6 @@ public Coord2d getModelToScreenRatio(Area space, Area canvas, Area margins) { return new Coord2d(x, y); } - /** - * @see {@link #getModelToScreenRatio(Area, Area, Area)} - */ - public Coord2d getModelToScreenRatio(BoundingBox3d bounds, ViewportConfiguration viewport, - Area margin) { - Area space = new Area(bounds.getXRange().getRange(), bounds.getYRange().getRange()); - Area screen = new Area(viewport.getWidth(), viewport.getHeight()); - return getModelToScreenRatio(space, screen, margin); - } - - - // ------------------------------------- // // RESULTS @@ -391,7 +429,7 @@ public Coord2d getModelToScreenRatio(BoundingBox3d bounds, ViewportConfiguration * to the axis and view layout settings */ public Area getArea() { - return new Area(area); + return new Area(marginArea); } /** @@ -463,6 +501,11 @@ public float getVerticalAxisDistance() { return verticalAxisDistance; } - - + public View getView() { + return view; + } + + public void setView(View view) { + this.view = view; + } } diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/modes/ViewPositionMode.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/modes/ViewPositionMode.java index d240d0dbd..061be5954 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/modes/ViewPositionMode.java +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/rendering/view/modes/ViewPositionMode.java @@ -7,10 +7,14 @@ * @author Martin Pernollet */ public enum ViewPositionMode { - /** Enforce view point on top of the scene. */ + /** Enforce view point on top of the scene, leading to a XY axis */ TOP, /** Enforce view point on profile of the scene. */ PROFILE, /** No enforcement of view point: let the user freely turn around the scene. */ - FREE + FREE, + /** Enforce view point on the side of the scene, leading to a YZ axis */ + YZ, + /** Enforce view point on the side of the scene, leading to a XZ axis */ + XZ } diff --git a/jzy3d-core/src/main/java/org/jzy3d/plot3d/text/renderers/TextRenderer.java b/jzy3d-core/src/main/java/org/jzy3d/plot3d/text/renderers/TextRenderer.java index ccdd94621..e91c81bf4 100644 --- a/jzy3d-core/src/main/java/org/jzy3d/plot3d/text/renderers/TextRenderer.java +++ b/jzy3d-core/src/main/java/org/jzy3d/plot3d/text/renderers/TextRenderer.java @@ -68,7 +68,8 @@ public BoundingBox3d drawText(IPainter painter, Font font, String text, Coord3d // process the aligned position in 3D coordinates Coord3d positionAligned = to3D(painter, screenAligned); - + + // process space stransform if any (log, etc) if (spaceTransformer != null) { positionAligned = spaceTransformer.compute(positionAligned); diff --git a/jzy3d-core/src/test/java/org/jzy3d/maths/TestCoord3d.java b/jzy3d-core/src/test/java/org/jzy3d/maths/TestCoord3d.java index 20e61075c..b9f7c0d64 100644 --- a/jzy3d-core/src/test/java/org/jzy3d/maths/TestCoord3d.java +++ b/jzy3d-core/src/test/java/org/jzy3d/maths/TestCoord3d.java @@ -113,4 +113,14 @@ public void dot() { } + + @Test + public void testValid() throws Exception { + Assert.assertFalse(new Coord3d(0,0,Float.NaN).isValid()); + Assert.assertFalse(new Coord3d(0,Float.POSITIVE_INFINITY, 0).isValid()); + Assert.assertFalse(new Coord3d(Float.NEGATIVE_INFINITY, 0, 0).isValid()); + Assert.assertTrue(Coord3d.ORIGIN.isValid()); + + } + } diff --git a/jzy3d-core/src/test/java/org/jzy3d/mocks/jzy3d/Mocks.java b/jzy3d-core/src/test/java/org/jzy3d/mocks/jzy3d/Mocks.java index 9133c6e23..7cd0de86b 100644 --- a/jzy3d-core/src/test/java/org/jzy3d/mocks/jzy3d/Mocks.java +++ b/jzy3d-core/src/test/java/org/jzy3d/mocks/jzy3d/Mocks.java @@ -94,6 +94,9 @@ public static View View(AxisBox axis, IPainter painter, ICanvas canvas) { return view; } + public static View ViewAndPainter() { + return ViewAndPainter(1); + } public static View ViewAndPainter(float viewScale) { return ViewAndPainter(viewScale, "macos", "10", null, null); diff --git a/jzy3d-core/src/test/java/org/jzy3d/plot3d/rendering/view/TestView2DProcessing.java b/jzy3d-core/src/test/java/org/jzy3d/plot3d/rendering/view/TestView2DProcessing.java index c9741cff7..330ef6972 100644 --- a/jzy3d-core/src/test/java/org/jzy3d/plot3d/rendering/view/TestView2DProcessing.java +++ b/jzy3d-core/src/test/java/org/jzy3d/plot3d/rendering/view/TestView2DProcessing.java @@ -10,6 +10,7 @@ import org.jzy3d.painters.IPainter; import org.jzy3d.plot3d.primitives.axis.layout.AxisLayout; import org.jzy3d.plot3d.primitives.axis.layout.LabelOrientation; +import org.jzy3d.plot3d.rendering.view.modes.ViewPositionMode; public class TestView2DProcessing { @@ -71,6 +72,8 @@ public void testView2DProcessing() { // When processing margins with a vertical Y AXIS, pixel scale = 1 when(view.getPixelScale()).thenReturn(new Coord2d(pixScale,pixScale)); + when(view.getViewMode()).thenReturn(ViewPositionMode.TOP); + when(axisLayout.getYAxisLabelOrientation()).thenReturn(LabelOrientation.VERTICAL); ViewportConfiguration viewport = new ViewportConfiguration(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, 0, 0); @@ -98,6 +101,7 @@ public void testView2DProcessing() { pixScale = 2; when(view.getPixelScale()).thenReturn(new Coord2d(pixScale,pixScale)); + when(view.getViewMode()).thenReturn(ViewPositionMode.TOP); processing.apply(viewport, bounds); @@ -193,6 +197,7 @@ public void whenNoMargin_ThenNo_NAN_factor() { // Given a view with settings View view = Mocks.View(Mocks.Axis(axisLayout), painter, Mocks.Canvas(true)); when(view.getPixelScale()).thenReturn(new Coord2d(1,1)); + when(view.getViewMode()).thenReturn(ViewPositionMode.TOP); View2DLayout layout = new View2DLayout(view); layout.setMargin(MARGIN); @@ -263,6 +268,7 @@ public void whenMarginEqualsCanvas_ThenNo_NAN_factor() { // Given a view with settings View view = Mocks.View(Mocks.Axis(axisLayout), painter, Mocks.Canvas(true)); when(view.getPixelScale()).thenReturn(new Coord2d(1,1)); + when(view.getViewMode()).thenReturn(ViewPositionMode.TOP); View2DLayout layout = new View2DLayout(view); layout.setMargin(MARGIN); diff --git a/jzy3d-emul-gl-awt/src/main/java/org/jzy3d/painters/EmulGLPainter.java b/jzy3d-emul-gl-awt/src/main/java/org/jzy3d/painters/EmulGLPainter.java index 248801512..7a4121ff9 100644 --- a/jzy3d-emul-gl-awt/src/main/java/org/jzy3d/painters/EmulGLPainter.java +++ b/jzy3d-emul-gl-awt/src/main/java/org/jzy3d/painters/EmulGLPainter.java @@ -1016,6 +1016,9 @@ public boolean gluUnProject(float winX, float winY, float winZ, float[] model, i double objX[] = new double[1]; double objY[] = new double[1]; double objZ[] = new double[1]; + + //Array.print("EmulGL Painter : ", proj); + //Array.print("EmulGL Painter : ", dbl(proj)); boolean st = glu.gluUnProject(winX, winY, winZ, dbl(model), dbl(proj), view, objX, objY, objZ); @@ -1027,6 +1030,26 @@ public boolean gluUnProject(float winX, float winY, float winZ, float[] model, i return st; } + //@Override + public boolean gluUnProject(float winX, float winY, float winZ, double[] model, int model_offset, + double[] proj, int proj_offset, int[] view, int view_offset, float[] objPos, + int objPos_offset) { + // throw new NotImplementedException(); + + double objX[] = new double[1]; + double objY[] = new double[1]; + double objZ[] = new double[1]; + + boolean st = glu.gluUnProject(winX, winY, winZ, model, + proj, view, objX, objY, objZ); + + objPos[0] = (float) objX[0]; + objPos[1] = (float) objY[0]; + objPos[2] = (float) objZ[0]; + + return st; + } + protected double[] dbl(float[] values) { double[] dbl = new double[values.length]; for (int i = 0; i < values.length; i++) { diff --git a/jzy3d-emul-gl-awt/src/main/java/org/jzy3d/plot3d/rendering/view/layout/EmulGLViewAndColorbarsLayout.java b/jzy3d-emul-gl-awt/src/main/java/org/jzy3d/plot3d/rendering/view/layout/EmulGLViewAndColorbarsLayout.java index 8e5e23fe3..f2bff5471 100644 --- a/jzy3d-emul-gl-awt/src/main/java/org/jzy3d/plot3d/rendering/view/layout/EmulGLViewAndColorbarsLayout.java +++ b/jzy3d-emul-gl-awt/src/main/java/org/jzy3d/plot3d/rendering/view/layout/EmulGLViewAndColorbarsLayout.java @@ -121,11 +121,4 @@ protected void renderLegends(IPainter painter, float left, float right, List{return x * Math.sin(x * y);}; - - Range range = new Range(-3, 3); - int steps = 50; - - Shape surface = - new SurfaceBuilder().orthonormal(new OrthonormalGrid(range, steps, range, steps), mapper); - surface.setPolygonOffsetFillEnable(false); - - ColorMapper colorMapper = new ColorMapper(new ColorMapRainbow(), surface.getBounds().getZmin(), - surface.getBounds().getZmax(), new Color(1, 1, 1, .5f)); - - surface.setColorMapper(colorMapper); - surface.setFaceDisplayed(true); - surface.setWireframeDisplayed(true); - surface.setWireframeColor(Color.BLACK); - return surface; + @Test + public void whenViewModeChange_ThenCameraSettingAreUpdatedAccordingly() { + + + // Given + ChartFactory f = new EmulGLChartFactory(); + AWTChart chart = (AWTChart)f.newChart(); + Shape surface = SampleGeom.surface(); + chart.add(surface); + + AWTView view = chart.getView(); + + // ----------------------------------------------------------- + // When 2D XY + view.setViewPositionMode(ViewPositionMode.TOP); + view.shoot(); + + // Then + Assert.assertEquals(new Coord3d(0,1,0), view.getCamera().getUp()); + + // When 2D XZ + view.setViewPositionMode(ViewPositionMode.XZ); + view.shoot(); + + // Then + Assert.assertEquals(new Coord3d(0,0,1), view.getCamera().getUp()); + + // When 2D YZ + view.setViewPositionMode(ViewPositionMode.YZ); + view.shoot(); + + // Then + Assert.assertEquals(new Coord3d(0,0,1), view.getCamera().getUp()); + + // ----------------------------------------------------------- + // When 3D + view.setViewPositionMode(ViewPositionMode.FREE); + view.shoot(); + + // Then + Assert.assertEquals(new Coord3d(0,0,1), view.getCamera().getUp()); + + // When 3D on top + view.setViewPositionMode(ViewPositionMode.FREE); + Coord3d viewpoint = view.getViewPoint().clone(); + viewpoint.y = View.PI_div2; + view.setViewPoint(viewpoint); + + // Then + Coord2d dir = new Coord2d(viewpoint.x, viewpoint.z).cartesian(); + Assert.assertEquals(new Coord3d(-dir.x,-dir.y,0), view.getCamera().getUp()); + + // When 3D on bottom + view.setViewPositionMode(ViewPositionMode.FREE); + viewpoint = view.getViewPoint().clone(); + viewpoint.y = -View.PI_div2; + view.setViewPoint(viewpoint); + + // Then + dir = new Coord2d(viewpoint.x, viewpoint.z).cartesian(); + Assert.assertEquals(new Coord3d(dir.x,dir.y,0), view.getCamera().getUp()); + } } diff --git a/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/GL.java b/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/GL.java index ce00e304e..6f767fe15 100644 --- a/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/GL.java +++ b/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/GL.java @@ -86,7 +86,7 @@ public abstract class GL { protected List pixelScaleListeners = new ArrayList<>(); public GL() {} - + public void setThrowExceptionOnGLError(boolean status) { this.CC.setThrowExceptionOnGLError(status); } @@ -129,12 +129,18 @@ public int getDesiredHeight() { return desiredHeight; } - /** Return the actual width, which is {@link #getDesiredWidth()} multiplied by {@link #getPixelScaleX()}*/ + /** + * Return the actual width, which is {@link #getDesiredWidth()} multiplied by + * {@link #getPixelScaleX()} + */ public int getActualWidth() { return actualWidth; } - /** Return the actual width, which is {@link #getDesiredHeight()} multiplied by {@link #getPixelScaleY()}*/ + /** + * Return the actual width, which is {@link #getDesiredHeight()} multiplied by + * {@link #getPixelScaleY()} + */ public int getActualHeight() { return actualHeight; } @@ -142,11 +148,11 @@ public int getActualHeight() { /* ******************** PROVIDE IMAGE ********************/ public void glShadeModel(int mode) { - if(CC.Mode != None) { - CC.gl_error(GL_INVALID_OPERATION, "glShadeModel"); - return; - } - CC.gl_shade_model(mode); + if (CC.Mode != None) { + CC.gl_error(GL_INVALID_OPERATION, "glShadeModel"); + return; + } + CC.gl_shade_model(mode); } @@ -242,13 +248,12 @@ public int getShiftHorizontally() { return shiftHorizontally; } - /** - * Allows shifting the image of the 3d scene and all images that have been append with - * {@link #appendImageToDraw(Object)} - * - * @param shift - * to the right if value is positive, to the left if the value is negative. - */ + /** + * Allows shifting the image of the 3d scene and all images that have been append with + * {@link #appendImageToDraw(Object)} + * + * @param shift to the right if value is positive, to the left if the value is negative. + */ public void setShiftHorizontally(int shiftHorizontally) { this.shiftHorizontally = shiftHorizontally; } @@ -271,10 +276,10 @@ public boolean isUseOSFontRendering() { return useOSFontRendering; } - /** - * If true, will use the OS for font rendering of all texts that have been append with - * {@link #appendTextToDraw(Object, String, int, int)} otherwise use a JVM based font rendering. - */ + /** + * If true, will use the OS for font rendering of all texts that have been append with + * {@link #appendTextToDraw(Object, String, int, int)} otherwise use a JVM based font rendering. + */ public void setUseOSFontRendering(boolean useOSFontRendering) { this.useOSFontRendering = useOSFontRendering; } @@ -298,25 +303,24 @@ public enum ImageLayer { FOREGROUND, BACKGROUND } - public void appendImageToDraw(ImageType image) { - - appendImageToDraw(image, 0, 0); - } + public void appendImageToDraw(ImageType image) { - public void appendImageToDraw(ImageType image, int x, int y) { + appendImageToDraw(image, 0, 0); + } - appendImageToDraw(image, x, y, ImageLayer.BACKGROUND); - } + public void appendImageToDraw(ImageType image, int x, int y) { + appendImageToDraw(image, x, y, ImageLayer.BACKGROUND); + } - public void appendImageToDraw(ImageType image, int x, int y, ImageLayer layer) { + public void appendImageToDraw(ImageType image, int x, int y, ImageLayer layer) { - synchronized(imageToDraw) { - imageToDraw.add(new ImageToDraw<>(x, y, image, layer)); - // System.out.println(imageToDraw.size() + " images to draw"); - } - } + synchronized (imageToDraw) { + imageToDraw.add(new ImageToDraw<>(x, y, image, layer)); + // System.out.println(imageToDraw.size() + " images to draw"); + } + } - public abstract ImageType getRenderedImage(); + public abstract ImageType getRenderedImage(); public void clearImagesBuffer() { synchronized (imageToDraw) { @@ -328,32 +332,34 @@ public void clearImagesBuffer() { - /** - * To be called by {@link GLUT#glutBitmapString(Object, String, float, float)} to append text to a - * list of text to render at {@link GL#glFlush()} step. - */ - public void appendTextToDraw(FontType font, String string, int x, int y) { + /** + * To be called by {@link GLUT#glutBitmapString(Object, String, float, float)} to append text to a + * list of text to render at {@link GL#glFlush()} step. + */ + public void appendTextToDraw(FontType font, String string, int x, int y) { synchronized (textsToDraw) { - textsToDraw.add(new TextToDraw<>(font, string, x, y)); + //System.out.println("GL:appendText : " + x+ " " + y); + + textsToDraw.add(new TextToDraw<>(font, string, x, y)); } } - /** - * To be called by {@link GLUT#glutBitmapString(Object, String, float, float)} to append text to a - * list of text to render at {@link GL#glFlush()} step. - */ - public void appendTextToDraw(FontType font, String string, int x, int y, float r, float g, float b, - float rotate) { + /** + * To be called by {@link GLUT#glutBitmapString(Object, String, float, float)} to append text to a + * list of text to render at {@link GL#glFlush()} step. + */ + public void appendTextToDraw(FontType font, String string, int x, int y, float r, float g, + float b, float rotate) { synchronized (textsToDraw) { - textsToDraw.add(new TextToDraw<>(font, string, x, y, r, g, b, rotate)); + textsToDraw.add(new TextToDraw<>(font, string, x, y, r, g, b, rotate)); + } + } + + public void clearTextToDraw() { + synchronized (textsToDraw) { + textsToDraw.clear(); // empty text buffer } } - - public void clearTextToDraw() { - synchronized (textsToDraw) { - textsToDraw.clear(); // empty text buffer - } - } /** @@ -540,8 +546,8 @@ private int translate_id(int n, int type, byte lists[]) { case GL_INT: case GL_UNSIGNED_INT: case GL_4_BYTES: - return (lists[n << 2]) << 24 | (lists[(n << 2) | 1]) << 16 - | (lists[(n << 2) | 2]) << 8 | (lists[(n << 2) | 3]); + return (lists[n << 2]) << 24 | (lists[(n << 2) | 1]) << 16 | (lists[(n << 2) | 2]) << 8 + | (lists[(n << 2) | 3]); case GL_FLOAT: return (int) ((lists[n << 2]) * 16777216.0f + (lists[(n << 2) | 1]) * 65536.0f + (lists[(n << 2) | 2]) * 256.0f + (lists[(n << 2) | 3])); @@ -1830,7 +1836,8 @@ public void glListBase(int base) { /** GLvoid glBegin (GLenum mode) */ public void glBegin(int mode) { if (CC.Mode != None) { - CC.gl_error(GL_INVALID_OPERATION, "glBegin can not be called as a geometry is already in progress : " + CC.Mode); + CC.gl_error(GL_INVALID_OPERATION, + "glBegin can not be called as a geometry is already in progress : " + CC.Mode); return; } switch (mode) { @@ -1850,8 +1857,9 @@ public void glBegin(int mode) { CC.gl_error(GL.GL_INVALID_ENUM, "glBegin(mode)"); } } - - static final String NEED_GL_BEGIN = " is called while no geometry was initialized with glBegin(geom)."; + + static final String NEED_GL_BEGIN = + " is called while no geometry was initialized with glBegin(geom)."; /** GLvoid glEnd (GLvoid) */ public void glEnd() { @@ -6370,7 +6378,7 @@ public void glPopName() { public static final int GLX_BAD_CONTEXT = 5; public static final int GLX_BAD_VALUE = 6; public static final int GLX_BAD_ENUM = 7; - /* Constant of GLE */ - public static final int GL_PHONG = GL_SMOOTH + 1; + /* Constant of GLE */ + public static final int GL_PHONG = GL_SMOOTH + 1; } diff --git a/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/wt/awt/GLU.java b/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/wt/awt/GLU.java index e42f56fd9..091b07b0b 100644 --- a/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/wt/awt/GLU.java +++ b/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/wt/awt/GLU.java @@ -17,7 +17,7 @@ package jgl.wt.awt; import java.lang.reflect.InvocationTargetException; - +import java.util.Arrays; import jgl.glu.GLUnurbsObj; import jgl.glu.GLUquadricObj; @@ -203,6 +203,21 @@ private double det44(double a1, double a2, double a3, double a4, double b1, doub - d1 * det33(a2, a3, a4, b2, b3, b4, c2, c3, c4)); } + private double det44_row(double[] m) { + return det44(m[0], m[1], m[2], m[3], + m[4], m[5], m[6], m[7], + m[8], m[9], m[10], m[11], + m[12], m[13], m[14], m[15]); + } + + private double det44_col(double[] m) { + return det44(m[0], m[4], m[8], m[12], + m[1], m[5], m[9], m[13], + m[2], m[6], m[10], m[14], + m[3], m[7], m[11], m[15]); + } + + private double[] adjoint44(double a[]) { double m[] = new double[16]; m[0] = det33(a[5], a[6], a[7], a[9], a[10], a[11], a[13], a[14], a[15]); @@ -233,12 +248,22 @@ private double[] inverseMatrix44(double a[]) { for (int i = 0; i < 4; i++) { out[4 * i + i] = 1; } - - for (int i = 0; i < 4; i++) { - double d = a[4 * i + i]; + + //print("Identity:", out, 4); + + // over rows + for (int i = 0; i < 4; i++) { + + // diagonal element : (0,0), (1,1), (2,2) or (3,3) + double d = a[4 * i + i]; + + //System.out.println(" inversing : with d=" + d); + if (d != 1) { for (int j = 0; j < 4; j++) { + out[4 * i + j] /= d; + a[4 * i + j] /= d; } } @@ -250,12 +275,14 @@ private double[] inverseMatrix44(double a[]) { for (int k = 0; k < 4; k++) { a[4 * j + k] -= mult * a[4 * i + k]; out[4 * j + k] -= mult * out[4 * i + k]; + } } } - } } + + //print("Inversed:", out, 4); return out; } @@ -271,6 +298,39 @@ private double[] swapRowsCols44(double[] matrix) { } return temp; } + + public static void print(double input[][]) { + for (int i = 0; i < input.length; i++) { + for (int j = 0; j < input[i].length; j++) { + System.out.print(input[i][j] + "\t"); + } + System.out.println(); + } + } + + public static void print(String info, double input[][]) { + System.out.print(info); + print(input); + } + + public static void print(double input[], int width) { + int k=0; + for (int i = 0; i < input.length; i++) { + System.out.print(input[i] + "\t"); + k++; + if(k%width==0) { + System.out.println(); + } + //System.out.println(); + } + } + + public static void print(String info, double input[], int width) { + System.out.println(info); + print(input, width); + } + + public GL gluGetGL() { return JavaGL; @@ -384,6 +444,11 @@ public boolean gluUnProject(double winx, double winy, double winz, double model[ A = mulMatrix44(proj, model); m = inverseMatrix44(A); + // Culprit: this result may contains NaN values + // https://github.com/jzy3d/jzy3d-api/issues/286 + // + //print("m = inverse(A)", m, 4); + out = mulMatrix41(m, in); if (out[3] == 0) return false; @@ -391,6 +456,7 @@ public boolean gluUnProject(double winx, double winy, double winz, double model[ objx[0] = out[0] / out[3]; objy[0] = out[1] / out[3]; objz[0] = out[2] / out[3]; + return true; } diff --git a/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/wt/awt/GLUT.java b/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/wt/awt/GLUT.java index c40f0b9c2..147e577d9 100644 --- a/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/wt/awt/GLUT.java +++ b/jzy3d-jGL/jzy3d-jGL-awt/src/main/java/jgl/wt/awt/GLUT.java @@ -154,6 +154,7 @@ public void glutBitmapString(Font font, String string, float x, float y) { */ public void glutBitmapString(Font font, String string, float x, float y, float z, float r, float g, float b, float rotate) { + //System.out.println("GLUT.glutBimap " + x + " " + y + " " + z); double[] win = modelToScreen(x, y, z); double winX = win[0]; @@ -171,10 +172,11 @@ protected double[] modelToScreen(float x, float y, float z) { double winz[] = new double[1]; if (!JavaGLU.gluProject(x, y, z, getModelViewAsDouble(), getProjectionAsDouble(), viewport, - winx, winy, winz)) + winx, winy, winz)) { System.err.println("GLUT.modelToScreen : Could not retrieve model coordinates in screen for " + x + ", " + y + ", " + z); - + } + double[] win = new double[3]; win[0] = winx[0]; win[1] = winy[0]; diff --git a/jzy3d-native-jogl-core/pom.xml b/jzy3d-native-jogl-core/pom.xml index a936213df..442db4d43 100644 --- a/jzy3d-native-jogl-core/pom.xml +++ b/jzy3d-native-jogl-core/pom.xml @@ -18,6 +18,14 @@ ${project.version} + + ${project.groupId} + jzy3d-core + ${project.version} + tests + test + +