package org.geogebra.common.geogebra3D.euclidian3D.draw; import org.geogebra.common.awt.GColor; import org.geogebra.common.euclidian.EuclidianController; import org.geogebra.common.geogebra3D.euclidian3D.EuclidianView3D; import org.geogebra.common.geogebra3D.euclidian3D.Hitting; import org.geogebra.common.geogebra3D.euclidian3D.openGL.PlotterBrush; import org.geogebra.common.geogebra3D.euclidian3D.openGL.PlotterSurface; import org.geogebra.common.geogebra3D.euclidian3D.openGL.Renderer; import org.geogebra.common.geogebra3D.kernel3D.geos.GeoSurfaceCartesian3D; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.Matrix.Coords3; import org.geogebra.common.kernel.Matrix.CoordsDouble3; import org.geogebra.common.kernel.geos.GProperty; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunction; import org.geogebra.common.kernel.geos.GeoFunctionNVar; import org.geogebra.common.kernel.kernelND.SurfaceEvaluable; import org.geogebra.common.kernel.kernelND.SurfaceEvaluable.LevelOfDetail; import org.geogebra.common.main.Feature; import org.geogebra.common.plugin.EuclidianStyleConstants; import org.geogebra.common.util.debug.Log; /** * Class for drawing a 2-var function * * @author mathieu * */ public class DrawSurface3D extends Drawable3DSurfaces { /** The function being rendered */ SurfaceEvaluable surfaceGeo; private double uDelta, vDelta; // number of intervals in root mesh (for each parameters, if parameters // delta are equals) private static final short ROOT_MESH_INTERVALS_SPEED = 10; private static final short ROOT_MESH_INTERVALS_SPEED_SQUARE = ROOT_MESH_INTERVALS_SPEED * ROOT_MESH_INTERVALS_SPEED; private static final short ROOT_MESH_INTERVALS_SPEED_TO_QUALITY_FACTOR = 2; // max factor private static final short ROOT_MESH_INTERVALS_MAX_FACTOR = 2; private static final double ROOT_MESH_INTERVALS_MAX_FACTOR_INVERSE = 1.0 / ROOT_MESH_INTERVALS_MAX_FACTOR; // number of split for boundary private static final short BOUNDARY_SPLIT = 10; // max split array size ( size +=4 for one last split) private static final int MAX_SPLIT_SPEED = 4096; private static final int MAX_SPLIT_QUALITY = MAX_SPLIT_SPEED * 2; private static final int MAX_SPLIT_IN_ONE_UPDATE_SPEED = 512; private static final int MAX_SPLIT_IN_ONE_UPDATE_QUALITY = MAX_SPLIT_IN_ONE_UPDATE_SPEED * 2; private SurfaceEvaluable.LevelOfDetail levelOfDetail = SurfaceEvaluable.LevelOfDetail.QUALITY; private int maxSplit; // draw array size ( size +=1 for one last draw) private int maxDraw; private int cornerListSize; /** * max splits in one update loop */ private int maxSplitsInOneUpdate; private DrawSurface3D.Corner[] currentSplit, nextSplit; protected DrawSurface3D.Corner[] cornerList; /** * list of things to draw */ protected CornerAndCenter[] drawList; private int currentSplitIndex, nextSplitIndex; protected int cornerListIndex; private int currentSplitStoppedIndex; protected int loopSplitIndex; protected int drawListIndex; /** Current culling box - set to view3d.(x|y|z)(max|min) */ private double[] cullingBox = new double[6]; /** * common constructor * * @param a_view3d * @param function */ public DrawSurface3D(EuclidianView3D a_view3d, SurfaceEvaluable surface) { super(a_view3d, (GeoElement) surface); this.surfaceGeo = surface; levelOfDetail = null; cornerForStillToSplit = new Corner[6]; cornerToDrawStillToSplit = new Corner[12]; for (int i = 0; i < 12; i++) { cornerToDrawStillToSplit[i] = new Corner(-1); } splitsStartedNotFinished = false; } private void setLevelOfDetail() { SurfaceEvaluable.LevelOfDetail lod = surfaceGeo.getLevelOfDetail(); if (levelOfDetail == lod) { return; } levelOfDetail = lod; // set sizes switch (levelOfDetail) { case SPEED: maxSplit = MAX_SPLIT_SPEED; maxSplitsInOneUpdate = MAX_SPLIT_IN_ONE_UPDATE_SPEED; break; case QUALITY: maxSplit = MAX_SPLIT_QUALITY; maxSplitsInOneUpdate = MAX_SPLIT_IN_ONE_UPDATE_QUALITY; break; } maxDraw = maxSplit; cornerListSize = maxDraw * 3; // create arrays currentSplit = new DrawSurface3D.Corner[maxSplit + 4]; nextSplit = new DrawSurface3D.Corner[maxSplit + 4]; drawList = new CornerAndCenter[maxDraw + 100]; cornerList = new DrawSurface3D.Corner[cornerListSize]; } private void setTolerances() { if (getView3D().getApplication().has(Feature.DIFFERENT_AXIS_RATIO_3D)) { maxRWPixelDistance = getView3D().getMaxPixelDistance(); } else { maxRWPixelDistance = getView3D().getMaxPixelDistance() / getView3D().getScale(); } // set sizes switch (levelOfDetail) { case SPEED: maxRWDistanceNoAngleCheck = 1 * maxRWPixelDistance; maxRWDistance = 5 * maxRWPixelDistance; maxBend = getView3D().getMaxBendSpeedSurface(); break; case QUALITY: maxRWDistanceNoAngleCheck = 1 * maxRWPixelDistance; maxRWDistance = 2 * maxRWPixelDistance; maxBend = getView3D().getMaxBend(); break; } } final static private boolean DEBUG = false; /** * console debug * * @param s * message */ final static protected void debug(String s) { if (DEBUG) { Log.debug(s); } } @Override public void drawGeometry(Renderer renderer) { renderer.setLayer(getLayer()); // +0f for z-fighting with planes renderer.getGeometryManager().draw(getSurfaceIndex()); renderer.setLayer(0); } @Override protected void drawSurfaceGeometry(Renderer renderer) { drawGeometry(renderer); } @Override void drawGeometryHiding(Renderer renderer) { drawSurfaceGeometry(renderer); } @Override public void drawGeometryHidden(Renderer renderer) { if (wireframeNeeded()) { if (!isVisible()) { return; } if (!wireframeVisible) { return; } if (getGeoElement() .getLineTypeHidden() == EuclidianStyleConstants.LINE_TYPE_HIDDEN_NONE) { return; } setDrawingColor(GColor.DARK_GRAY); setLineTextureHidden(renderer); renderer.getGeometryManager().draw(getGeometryIndex()); } } @Override public void drawOutline(Renderer renderer) { if (wireframeNeeded()) { if (!isVisible()) { return; } if (!wireframeVisible) { return; } setDrawingColor(GColor.DARK_GRAY); renderer.getTextures() .setDashFromLineType(getGeoElement().getLineType()); renderer.getGeometryManager().draw(getGeometryIndex()); } } private boolean drawFromScratch = true; private boolean drawUpToDate = false; /** * first corner from root mesh */ private Corner firstCorner; @Override protected boolean updateForItSelf() { if (!surfaceGeo.isDefined()) { return false; } if (((GeoElement) surfaceGeo).isGeoFunctionNVar()) { if (((GeoFunctionNVar) surfaceGeo).getVarNumber() != 2) { setSurfaceIndex(-1); setGeometryIndex(-1); return true; } } boolean drawOccured = false; if (drawFromScratch) { drawUpToDate = false; if (levelOfDetail == LevelOfDetail.QUALITY && splitsStartedNotFinished) { draw(); drawOccured = true; } // maybe set to null after redefine surfaceGeo.setDerivatives(); // calc min/max values double uBorderMin = surfaceGeo.getMinParameter(0); double uBorderMax = surfaceGeo.getMaxParameter(0); double vBorderMin = surfaceGeo.getMinParameter(1); double vBorderMax = surfaceGeo.getMaxParameter(1); double uStep = Double.NaN; double vStep = Double.NaN; if (((GeoElement) surfaceGeo).isGeoFunctionNVar() || (surfaceGeo instanceof GeoFunction)) { if (Double.isNaN(uBorderMin)) { uBorderMin = getView3D().getXmin(); } if (Double.isNaN(uBorderMax)) { uBorderMax = getView3D().getXmax(); } if (Double.isNaN(vBorderMin)) { vBorderMin = getView3D().getYmin(); } if (Double.isNaN(vBorderMax)) { vBorderMax = getView3D().getYmax(); } // don't draw borders wireframeBorderU = 0; wireframeBorderV = 0; // wireframe follows the grid uStep = getView3D().getAxisNumberingDistance(0); vStep = getView3D().getAxisNumberingDistance(1); } else if (((GeoSurfaceCartesian3D) surfaceGeo) .isSurfaceOfRevolutionAroundOx()) { // cartesian surface of revolution uBorderMin = getView3D().getXmin(); uBorderMax = getView3D().getXmax(); // draw borders for v, not for u=x wireframeBorderU = 0; wireframeBorderV = 1; // wireframe follows the grid uStep = getView3D().getAxisNumberingDistance(0); } else { // cartesian surface NOT of revolution // draw borders for u and v wireframeBorderU = 1; wireframeBorderV = 1; } uDelta = uBorderMax - uBorderMin; if (Kernel.isZero(uDelta)) { setSurfaceIndex(-1); setWireframeInvisible(); return true; } vDelta = vBorderMax - vBorderMin; if (Kernel.isZero(vDelta)) { setSurfaceIndex(-1); setWireframeInvisible(); return true; } // max values setLevelOfDetail(); setTolerances(); updateCullingBox(); initBounds(); debug("\nmax distances = " + maxRWDistance + ", " + maxRWDistanceNoAngleCheck); // create root mesh wireFrameStepU = 1; wireFrameStepV = 1; wireframeUniqueU = false; wireframeUniqueV = false; double uOverVFactor = uDelta / vDelta; if (uOverVFactor > ROOT_MESH_INTERVALS_SPEED) { uOverVFactor = ROOT_MESH_INTERVALS_SPEED; } else if (uOverVFactor < 1.0 / ROOT_MESH_INTERVALS_SPEED) { uOverVFactor = 1.0 / ROOT_MESH_INTERVALS_SPEED; } int uN = (int) (ROOT_MESH_INTERVALS_SPEED * uOverVFactor); int vN = ROOT_MESH_INTERVALS_SPEED_SQUARE / uN; uN += 2; vN += 2; double uMin = Double.NaN; double uMax = Double.NaN; double vMin = Double.NaN; double vMax = Double.NaN; if (!Double.isNaN(uStep)) { if (uStep > uDelta) { // we have maximum one wireframe line wireframeUniqueU = true; double uWireFrame = Math.ceil(uBorderMin / uStep) * uStep; wireFrameStepU = (int) ((uN * (uBorderMax - uWireFrame)) / uDelta); uStep = uDelta / uN; uMax = uWireFrame + uStep * wireFrameStepU; if (wireFrameStepU == uN - 1) { wireFrameStepU--; uMax -= uStep; } // uMin = uBorderMin; } else { double factor = uN * uStep / uDelta; if (factor > 1) { wireFrameStepU = (int) Math.ceil(factor); } else if (factor < ROOT_MESH_INTERVALS_MAX_FACTOR_INVERSE) { int stepFactor = (int) Math .ceil(ROOT_MESH_INTERVALS_MAX_FACTOR_INVERSE / factor); // Log.debug("stepFactor = " + stepFactor); uStep *= stepFactor; } if (levelOfDetail == LevelOfDetail.QUALITY && wireFrameStepU == 1) { wireFrameStepU *= ROOT_MESH_INTERVALS_SPEED_TO_QUALITY_FACTOR; } uMax = Math.floor(uBorderMax / uStep) * uStep; uMin = Math.ceil(uBorderMin / uStep) * uStep; uDelta = uMax - uMin; int ratioInt = (int) Math.ceil(uDelta / uStep); uN = (ratioInt + 1) * wireFrameStepU + 1; // delta has to widened a bit to start at a correct tick uDelta += (1 + 1.0 / wireFrameStepU) * uStep; } } else { if (levelOfDetail == LevelOfDetail.QUALITY) { wireFrameStepU *= ROOT_MESH_INTERVALS_SPEED_TO_QUALITY_FACTOR; uN *= ROOT_MESH_INTERVALS_SPEED_TO_QUALITY_FACTOR; } } if (!Double.isNaN(vStep)) { if (vStep > vDelta) { // we have maximum one wireframe line wireframeUniqueV = true; double vWireFrame = Math.ceil(vBorderMin / vStep) * vStep; wireFrameStepV = (int) ((vN * (vBorderMax - vWireFrame)) / vDelta); vStep = vDelta / vN; vMax = vWireFrame + vStep * wireFrameStepV; if (wireFrameStepV == vN - 1) { wireFrameStepV--; vMax -= vStep; } // vMin = vBorderMin; } else { double factor = vN * vStep / vDelta; if (factor > 1) { wireFrameStepV = (int) Math.ceil(factor); } else if (factor < ROOT_MESH_INTERVALS_MAX_FACTOR_INVERSE) { int stepFactor = (int) Math .ceil(ROOT_MESH_INTERVALS_MAX_FACTOR_INVERSE / factor); // Log.debug("stepFactor = " + stepFactor); vStep *= stepFactor; } if (levelOfDetail == LevelOfDetail.QUALITY && wireFrameStepV == 1) { wireFrameStepV *= ROOT_MESH_INTERVALS_SPEED_TO_QUALITY_FACTOR; } vMax = Math.floor(vBorderMax / vStep) * vStep; vMin = Math.ceil(vBorderMin / vStep) * vStep; vDelta = vMax - vMin; int ratioInt = (int) Math.ceil(vDelta / vStep); vN = (ratioInt + 1) * wireFrameStepV + 1; // delta has to widened a bit to start at a correct tick vDelta += (1 + 1.0 / wireFrameStepV) * vStep; } } else { if (levelOfDetail == LevelOfDetail.QUALITY) { wireFrameStepV *= ROOT_MESH_INTERVALS_SPEED_TO_QUALITY_FACTOR; vN *= ROOT_MESH_INTERVALS_SPEED_TO_QUALITY_FACTOR; } } debug("grids: " + uN + ", " + vN); cornerListIndex = 0; double du = uDelta / uN; double dv = vDelta / vN; if (Double.isNaN(uMax)) { uMax = uBorderMax - du; } if (Double.isNaN(vMax)) { vMax = vBorderMax - dv; } firstCorner = createRootMesh(uBorderMin, uMax, uBorderMax, uN, vBorderMin, vMax, vBorderMax, vN); // split root mesh as start currentSplitIndex = 0; currentSplitStoppedIndex = 0; nextSplitIndex = 0; drawListIndex = 0; notDrawn = 0; splitRootMesh(firstCorner); debug("\nnot drawn after split root mesh: " + notDrawn); // now splitted root mesh is ready drawFromScratch = false; } if (wireframeNeeded()) { if (drawUpToDate) { // update is called for visual style, i.e. line thickness drawWireframe(getView3D().getRenderer()); return true; } } // start recursive split loopSplitIndex = 0; // long time = System.currentTimeMillis(); stillRoomLeft = split(); // time = System.currentTimeMillis() - time; // if (time > 0){ // debug("split : "+time); // } debug("\ndraw size : " + drawListIndex + "\nnot drawn : " + notDrawn + "\nstill to split : " + (currentSplitIndex - currentSplitStoppedIndex) + "\nnext to split : " + nextSplitIndex + "\ncorner list size : " + cornerListIndex + "\nstill room left : " + stillRoomLeft); splitsStartedNotFinished = (currentSplitIndex - currentSplitStoppedIndex) + nextSplitIndex > 0; // time = System.currentTimeMillis(); // set old thickness to force wireframe update oldThickness = -1; switch (levelOfDetail) { case SPEED: default: draw(); // still room left and still split to do: still to update drawUpToDate = !splitsStartedNotFinished || !stillRoomLeft; return drawUpToDate; case QUALITY: splitsStartedNotFinished = splitsStartedNotFinished && stillRoomLeft; if (!splitsStartedNotFinished) { if (!drawOccured) { // no draw at start: can do the draw now draw(); drawUpToDate = true; return true; } // no room left or no split to do: update is finished, but // the // object may change return false; } // still room left and still split to do: still to update return false; } // time = System.currentTimeMillis() - time; // if (time > 0){ // debug("draw : "+time); // } } @Override public void setWaitForUpdateVisualStyle(GProperty prop) { super.setWaitForUpdateVisualStyle(prop); if (prop == GProperty.LINE_STYLE) { // also update for line width super.setWaitForUpdate(); } else if (prop == GProperty.VISIBLE) { if (isVisible()) { setWaitForUpdate(); } } } /** * ends geometry * * @param surface * surface plotter * */ static final private void endGeometry(PlotterSurface surface) { surface.endGeometryDirect(); } /** * draw all corners and centers * * @param surface * surface plotter * */ protected void drawCornersAndCenters(PlotterSurface surface) { // used with GL.drawElements() } final private void startTriangles(PlotterSurface surface) { surface.startTriangles(cornerListIndex * 16); } private void draw() { Renderer renderer = getView3D().getRenderer(); if (getView3D().getApplication().has(Feature.DIFFERENT_AXIS_RATIO_3D)) { // point were already scaled renderer.getGeometryManager().setScalerIdentity(); } // draw splitted, still to split, and next to split PlotterSurface surface = renderer.getGeometryManager().getSurface(); surface.start(getReusableSurfaceIndex()); if (!stillRoomLeft) { for (int i = currentSplitStoppedIndex; i < currentSplitIndex; i++) { currentSplit[i].split(true); } for (int i = 0; i < nextSplitIndex; i++) { nextSplit[i].split(true); } debug("\n--- draw size : " + drawListIndex); if (drawListIndex > 0) { startTriangles(surface); for (int i = 0; i < drawListIndex; i++) { drawList[i].draw(surface); } drawCornersAndCenters(surface); endGeometry(surface); } } else { if (drawListIndex > 0 || splitsStartedNotFinished) { startTriangles(surface); for (int i = 0; i < drawListIndex; i++) { drawList[i].draw(surface); } drawCornersAndCenters(surface); for (int i = currentSplitStoppedIndex; i < currentSplitIndex; i++) { currentSplit[i].drawAsStillToSplit(surface); } for (int i = 0; i < nextSplitIndex; i++) { nextSplit[i].drawAsNextToSplit(surface); } endGeometry(surface); } } setSurfaceIndex(surface.end()); if (getView3D().getApplication().has(Feature.DIFFERENT_AXIS_RATIO_3D)) { renderer.getGeometryManager().setScalerView(); } drawWireframe(renderer); } static final private boolean isDefinedForWireframe(Corner corner) { if (corner.p.isFinalUndefined()) { return false; } return corner.p.isDefined(); } // previously set thickness (-1 means needs update) private int oldThickness = -1; private boolean wireframeVisible = false; private void setWireframeInvisible() { wireframeVisible = false; } private void drawWireframe(Renderer renderer) { if (!wireframeNeeded()) { return; } int thickness = getGeoElement().getLineThickness(); if (thickness == 0) { setWireframeInvisible(); return; } // wireframe is visible wireframeVisible = true; if (thickness == oldThickness) { // surface and thickness have not changed return; } oldThickness = thickness; PlotterBrush brush = renderer.getGeometryManager().getBrush(); if (getView3D().getApplication().has(Feature.DIFFERENT_AXIS_RATIO_3D)) { // point were already scaled renderer.getGeometryManager().setScalerIdentity(); } brush.start(getReusableGeometryIndex()); brush.setThickness(thickness, (float) getView3D().getScale()); brush.setAffineTexture(0f, 0f); brush.setLength(1f); for (int i = 0; i < wireframeBottomCornersLength; i++) { Corner above = wireframeBottomCorners[i]; boolean currentPointIsDefined = isDefinedForWireframe(above); if (currentPointIsDefined) { brush.moveTo(above.p.getXd(), above.p.getYd(), above.p.getZd()); } boolean lastPointIsDefined = currentPointIsDefined; above = above.a; while (above != null) { currentPointIsDefined = isDefinedForWireframe(above); if (currentPointIsDefined) { if (lastPointIsDefined) { brush.drawTo(above.p.getXd(), above.p.getYd(), above.p.getZd(), true); } else { brush.moveTo(above.p.getXd(), above.p.getYd(), above.p.getZd()); } } lastPointIsDefined = currentPointIsDefined; above = above.a; } brush.endPlot(); } for (int i = 0; i < wireframeRightCornersLength; i++) { Corner left = wireframeRightCorners[i]; boolean currentPointIsDefined = isDefinedForWireframe(left); if (currentPointIsDefined) { brush.moveTo(left.p.getXd(), left.p.getYd(), left.p.getZd()); } boolean lastPointIsDefined = currentPointIsDefined; left = left.l; while (left != null) { currentPointIsDefined = isDefinedForWireframe(left); if (currentPointIsDefined) { if (lastPointIsDefined) { brush.drawTo(left.p.getXd(), left.p.getYd(), left.p.getZd(), true); } else { brush.moveTo(left.p.getXd(), left.p.getYd(), left.p.getZd()); } } lastPointIsDefined = currentPointIsDefined; left = left.l; } brush.endPlot(); } setGeometryIndex(brush.end()); if (getView3D().getApplication().has(Feature.DIFFERENT_AXIS_RATIO_3D)) { // point were already scaled renderer.getGeometryManager().setScalerView(); } } private boolean splitsStartedNotFinished, stillRoomLeft; @Override protected void updateForView() { if (getView3D().viewChangedByZoom() || getView3D().viewChangedByTranslate()) { setWaitForUpdate(); } } @Override public void setWaitForUpdate() { drawFromScratch = true; super.setWaitForUpdate(); } @Override public int getPickOrder() { return DRAW_PICK_ORDER_SURFACE; } private static boolean wireframeNeeded() { return true; } @Override public void addToDrawable3DLists(Drawable3DLists lists) { addToDrawable3DLists(lists, DRAW_TYPE_CLIPPED_SURFACES); if (wireframeNeeded()) { addToDrawable3DLists(lists, DRAW_TYPE_CLIPPED_CURVES); } } @Override public void removeFromDrawable3DLists(Drawable3DLists lists) { removeFromDrawable3DLists(lists, DRAW_TYPE_CLIPPED_SURFACES); if (wireframeNeeded()) { removeFromDrawable3DLists(lists, DRAW_TYPE_CLIPPED_CURVES); } } private boolean updateCullingBox() { EuclidianView3D view = getView3D(); double off = maxRWPixelDistance * 2; cullingBox[0] = view.getXmin() - off; cullingBox[1] = view.getXmax() + off; cullingBox[2] = view.getYmin() - off; cullingBox[3] = view.getYmax() + off; cullingBox[4] = view.getZmin() - off; cullingBox[5] = view.getZmax() + off; return true; } private boolean inCullingBox(Coords3 p) { // check point is in culling box if ((p.getXd() > cullingBox[0]) && (p.getXd() < cullingBox[1]) && (p.getYd() > cullingBox[2]) && (p.getYd() < cullingBox[3]) && (p.getZd() > cullingBox[4]) && (p.getZd() < cullingBox[5])) { return true; } return false; } private Coords boundsMin = new Coords(3), boundsMax = new Coords(3); private void initBounds() { boundsMin.set(Double.POSITIVE_INFINITY); boundsMax.set(Double.NEGATIVE_INFINITY); } private void updateBounds(Coords3 p) { // update bounds if (p.getXd() < boundsMin.getX()) { boundsMin.setX(p.getXd()); } if (p.getYd() < boundsMin.getY()) { boundsMin.setY(p.getYd()); } if (p.getZd() < boundsMin.getZ()) { boundsMin.setZ(p.getZd()); } if (p.getXd() > boundsMax.getX()) { boundsMax.setX(p.getXd()); } if (p.getYd() > boundsMax.getY()) { boundsMax.setY(p.getYd()); } if (p.getZd() > boundsMax.getZ()) { boundsMax.setZ(p.getZd()); } } @Override public void enlargeBounds(Coords min, Coords max) { if (!Double.isInfinite(boundsMin.getX())) { enlargeBounds(min, max, boundsMin, boundsMax); } } // corners for drawing wireframe (bottom and right sides) private Corner[] wireframeBottomCorners, wireframeRightCorners; private int wireframeBottomCornersLength, wireframeRightCornersLength; // says if we draw borders for wireframe // (we use short for array index shifting) private short wireframeBorderU, wireframeBorderV; // says if only one wireframe line is drawn private boolean wireframeUniqueU, wireframeUniqueV; // steps to draw wireframe private int wireFrameStepU, wireFrameStepV; private Corner createRootMesh(double uBorderMin, double uMax, double uBorderMax, int uN, double vBorderMin, double vMax, double vBorderMax, int vN) { if (wireframeNeeded()) { if (wireframeUniqueU) { if (wireFrameStepU < 0) { wireframeBottomCorners = new Corner[2 * wireframeBorderU]; } else { wireframeBottomCorners = new Corner[1 + 2 * wireframeBorderU]; } } else { wireframeBottomCorners = new Corner[(uN - 1) / wireFrameStepU + 2 * wireframeBorderU]; } if (wireframeUniqueV) { if (wireFrameStepV < 0) { wireframeRightCorners = new Corner[2 * wireframeBorderV]; } else { wireframeRightCorners = new Corner[1 + 2 * wireframeBorderV]; } } else { wireframeRightCorners = new Corner[(vN - 1) / wireFrameStepV + 2 * wireframeBorderV]; } } Corner bottomRight = newCorner(uBorderMax, vBorderMax); Corner first = bottomRight; wireframeBottomCornersLength = 0; wireframeRightCornersLength = 0; int wireFrameSetU = wireFrameStepU, wireFrameSetV = wireFrameStepV; if (wireframeNeeded()) { if (wireframeUniqueU) { wireFrameSetU = 0; } if (wireframeBorderU == 1) { // draw edges wireframeBottomCorners[0] = first; wireframeBottomCornersLength = 1; wireFrameSetU = 1; } if (wireframeUniqueV) { wireFrameSetV = 0; } if (wireframeBorderV == 1) { // draw edges wireframeRightCorners[0] = first; wireframeRightCornersLength = 1; wireFrameSetV = 1; } } // first row Corner right = bottomRight; for (int i = 0; i < uN - 1; i++) { right = addLeftToMesh(right, uMax - (uDelta * i) / uN, vBorderMax); if (wireframeNeeded()) { if (wireFrameSetU == wireFrameStepU) { // set wireframe wireframeBottomCorners[wireframeBottomCornersLength] = right; wireframeBottomCornersLength++; if (wireframeUniqueU) { wireFrameSetU++; } else { wireFrameSetU = 1; } } else { wireFrameSetU++; } } } right = addLeftToMesh(right, uBorderMin, vBorderMax); if (wireframeNeeded()) { if (wireframeBorderU == 1) { wireframeBottomCorners[wireframeBottomCornersLength] = right; wireframeBottomCornersLength++; } } // all intermediate rows for (int j = 0; j < vN - 1; j++) { bottomRight = addRowAboveToMesh(bottomRight, vMax - (vDelta * j) / vN, uBorderMin, uBorderMax, uMax, uN); if (wireframeNeeded()) { if (wireFrameSetV == wireFrameStepV) { // set wireframe wireframeRightCorners[wireframeRightCornersLength] = bottomRight; wireframeRightCornersLength++; if (wireframeUniqueV) { wireFrameSetV++; } else { wireFrameSetV = 1; } } else { wireFrameSetV++; } } } // last row bottomRight = addRowAboveToMesh(bottomRight, vBorderMin, uBorderMin, uBorderMax, uMax, uN); if (wireframeNeeded()) { if (wireframeBorderV == 1) { wireframeRightCorners[wireframeRightCornersLength] = bottomRight; wireframeRightCornersLength++; } } return first; } final private Corner addLeftToMesh(Corner right, double u, double v) { Corner left = newCorner(u, v); right.l = left; return left; } final private Corner addRowAboveToMesh(Corner bottomRight, double v, double uBorderMin, double uBorderMax, double uMax, int uN) { Corner below = bottomRight; Corner right = newCorner(uBorderMax, v); below.a = right; for (int i = 0; i < uN - 1; i++) { right = addLeftToMesh(right, uMax - (uDelta * i) / uN, v); below = below.l; below.a = right; } right = addLeftToMesh(right, uBorderMin, v); below = below.l; below.a = right; return bottomRight.a; } protected int notDrawn; private static void splitRootMesh(Corner first) { Corner nextAbove, nextLeft; Corner current = first; while (current.a != null) { nextAbove = current.a; while (current.l != null) { nextLeft = current.l; if (nextLeft.a == null) { // already splitted by last row nextLeft = nextLeft.l; } // Log.debug(current.u + "," + current.v); current.split(false); current = nextLeft; } current = nextAbove; } } private boolean split() { if (currentSplitStoppedIndex == currentSplitIndex) { // swap stacks Corner[] tmp = currentSplit; currentSplit = nextSplit; nextSplit = tmp; currentSplitIndex = nextSplitIndex; nextSplitIndex = 0; currentSplitStoppedIndex = 0; } while (currentSplitStoppedIndex < currentSplitIndex && loopSplitIndex < maxSplitsInOneUpdate) { currentSplit[currentSplitStoppedIndex].split(false); currentSplitStoppedIndex++; if (drawListIndex + (currentSplitIndex - currentSplitStoppedIndex) + nextSplitIndex >= maxDraw) { // no room left for new draw return false; } if (nextSplitIndex >= maxSplit) { // no room left for new split return false; } } // debug("nextSplitIndex = " + nextSplitIndex + " , drawListIndex = " + // drawListIndex); if (loopSplitIndex < maxSplitsInOneUpdate && nextSplitIndex > 0) { return split(); } return true; // went to end of loop } private Coords3 evaluatedPoint = newCoords3(); private Coords3 evaluatedNormal = newCoords3(); /** * * @return new coords 3 */ final static protected Coords3 newCoords3() { return new CoordsDouble3(); } final private void scaleXYZ(Coords3 p) { if (getView3D().getApplication().has(Feature.DIFFERENT_AXIS_RATIO_3D)) { getView3D().scaleXYZ(p); } } final private void scaleAndNormalizeNormalXYZ(Coords3 n) { if (getView3D().getApplication().has(Feature.DIFFERENT_AXIS_RATIO_3D)) { getView3D().scaleAndNormalizeNormalXYZ(n); } } protected Coords3 evaluatePoint(double u, double v) { surfaceGeo.evaluatePoint(u, v, evaluatedPoint); if (!evaluatedPoint.isDefined()) { return Coords3.UNDEFINED; } updateBounds(evaluatedPoint); if (inCullingBox(evaluatedPoint)) { scaleXYZ(evaluatedPoint); return evaluatedPoint.copyVector(); } return Coords3.UNDEFINED; } protected Coords3 evaluatePoint(double u, double v, Coords3 p) { // p is final value: use evaluatedPoint to compute if (p == null || p.isFinalUndefined()) { surfaceGeo.evaluatePoint(u, v, evaluatedPoint); if (!evaluatedPoint.isDefined()) { return Coords3.UNDEFINED; } updateBounds(evaluatedPoint); if (inCullingBox(evaluatedPoint)) { scaleXYZ(evaluatedPoint); return evaluatedPoint.copyVector(); } return Coords3.UNDEFINED; } // p is not final value surfaceGeo.evaluatePoint(u, v, p); if (!p.isDefined()) { return Coords3.UNDEFINED; } updateBounds(p); if (inCullingBox(p)) { scaleXYZ(p); return p; } return Coords3.UNDEFINED; } protected Coords3 evaluateNormal(Coords3 p, double u, double v, Coords3 normal) { boolean defined; // normal is final value: use evaluatedNormal to compute if (normal == null || normal.isFinalUndefined()) { defined = surfaceGeo.evaluateNormal(p, u, v, evaluatedNormal); if (!defined) { return Coords3.UNDEFINED; } scaleAndNormalizeNormalXYZ(evaluatedNormal); return evaluatedNormal.copyVector(); } // normal is not final value defined = surfaceGeo.evaluateNormal(p, u, v, normal); if (!defined) { return Coords3.UNDEFINED; } scaleAndNormalizeNormalXYZ(normal); return normal; } class Corner { Coords3 p; Coords3 normal; double u, v; boolean isNotEnd; Corner a, l; // above, left int id; public Corner(int id) { this.id = id; } public Corner(double u, double v, int id) { this.id = id; set(u, v); } public void set(double u, double v) { this.u = u; this.v = v; p = evaluatePoint(u, v, p); if (p.isFinalUndefined()) { normal = Coords3.UNDEFINED; } else { normal = evaluateNormal(p, u, v, normal); } isNotEnd = true; a = null; l = null; } public void set(Corner c) { u = c.u; v = c.v; p = c.p; normal = c.normal; id = c.id; } public Corner(double u, double v, Coords3 p) { set(u, v, p); } public void set(double u, double v, Coords3 p) { this.u = u; this.v = v; this.p = p; normal = evaluateNormal(p, u, v, normal); isNotEnd = true; a = null; l = null; } /** * draw this corner as part of "next to split" list * * @param surface * surface plotter */ public void drawAsNextToSplit(PlotterSurface surface) { // if (this.p.isNotFinalUndefined()){ // if (a.p.isNotFinalUndefined()){ // if (l.p.isNotFinalUndefined()){ // drawTriangle(surface, this, a, l); // if (l.a.p.isNotFinalUndefined()){ // drawTriangle(surface, l, a, l.a); // } // }else{ // if (l.a.p.isNotFinalUndefined()){ // drawTriangle(surface, this, a, l.a); // } // } // }else{ // if (l.p.isNotFinalUndefined() && l.a.p.isNotFinalUndefined()){ // drawTriangle(surface, this, l.a, l); // } // } // }else{ // this undefined // if (l.p.isNotFinalUndefined() && a.p.isNotFinalUndefined() && // l.a.p.isNotFinalUndefined()){ // drawTriangle(surface, l, a, l.a); // } // } drawAsStillToSplit(surface); } /** * draw this corner as part of "still to split" list * * @param surface * surface plotter */ public void drawAsStillToSplit(PlotterSurface surface) { // prevent keeping old element id for (int i = 0; i < cornerToDrawStillToSplit.length; i++) { cornerToDrawStillToSplit[i].id = -1; } // create ring about corners int length; cornerForStillToSplit[0] = this; cornerForStillToSplit[1] = this.l; if (this.l.a == null) { // a split occurred cornerForStillToSplit[2] = this.l.l; cornerForStillToSplit[3] = this.l.l.a; length = 4; } else { cornerForStillToSplit[2] = this.l.a; length = 3; } if (this.a.l == null) { // a split occurred cornerForStillToSplit[length] = this.a.a; length++; cornerForStillToSplit[length] = this.a; length++; } else { cornerForStillToSplit[length] = this.a; length++; } // check defined and create intermediate corners if needed Corner previous = cornerForStillToSplit[length - 1]; int index = 0; for (int i = 0; i < length; i++) { Corner current = cornerForStillToSplit[i]; if (current.p.isNotFinalUndefined()) { if (previous.p.isFinalUndefined()) { // previous undefined -- current defined : create // intermediate if (Kernel.isEqual(previous.u, current.u)) { findV(current, previous, BOUNDARY_SPLIT, cornerToDrawStillToSplit[index]); } else { findU(current, previous, BOUNDARY_SPLIT, cornerToDrawStillToSplit[index]); } index++; } // add current for drawing cornerToDrawStillToSplit[index].set(current); index++; } else { if (previous.p.isNotFinalUndefined()) { // previous defined -- current undefined : create // intermediate if (Kernel.isEqual(previous.u, current.u)) { findV(previous, current, BOUNDARY_SPLIT, cornerToDrawStillToSplit[index]); } else { findU(previous, current, BOUNDARY_SPLIT, cornerToDrawStillToSplit[index]); } index++; } } previous = current; } if (index < 3) { // Log.debug("index = "+index); return; } Coords3 v0 = new CoordsDouble3(), n0 = new CoordsDouble3(); setBarycenter(v0, n0, index, cornerToDrawStillToSplit); for (int i = 0; i < index; i++) { drawTriangle(surface, v0, n0, cornerToDrawStillToSplit[(i + 1) % index], cornerToDrawStillToSplit[i]); } } public void split(boolean draw) { Corner left, above, subLeft, subAbove; if (l.a == null) { left = l.l; subLeft = l; } else { left = l; subLeft = null; } if (a.l == null) { above = a.a; subAbove = a; } else { above = a; subAbove = null; } if (p.isFinalUndefined()) { if (left.p.isFinalUndefined()) { if (above.p.isFinalUndefined()) { if (left.a.p.isFinalUndefined()) { // all undefined: nothing to draw /0/ notDrawn++; } else { // l.a is defined /1/ // find defined between l.a and a Corner n = newCorner(); findU(left.a, above, BOUNDARY_SPLIT, n); // find defined between l.a and l Corner w = newCorner(); findV(left.a, left, BOUNDARY_SPLIT, w); boolean split; if (draw) { // time to draw split = false; } else if (n.p.isFinalUndefined() || w.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else { // check distance double d = getDistance(left.a, n, w); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, left.a, n, w)) { // angle // ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // new neighbors n.l = left.a; above.l = n; // new neighbors w.a = left.a; left.a = w; // draw addToDrawList(w.a, n, w, w.a); } } } else { if (left.a.p.isFinalUndefined()) { // a defined /1/ // find defined between a and l.a Corner n = newCorner(); findU(above, left.a, BOUNDARY_SPLIT, n); // find defined between a and this Corner e; if (subAbove != null) { e = subAbove; } else { e = newCorner(); findV(above, this, BOUNDARY_SPLIT, e); } boolean split; if (draw) { // time to draw split = false; } else if (n.p.isFinalUndefined() || e.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else { // check distance double d = getDistance(above, n, e); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, above, n, e)) { // angle // ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // new neighbors if (subAbove == null) { this.a = e; e.a = above; } n.l = left.a; above.l = n; // drawing addToDrawList(left.a, n, e, above); } } else { // a and l.a defined /2/ boolean split; if (draw) { // time to draw split = false; } else if (subAbove != null && subAbove.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else { // check distance double d = getDistance(above, left.a); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, above, left.a)) { // angle // ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // find defined between a and this Corner e; if (subAbove != null) { e = subAbove; } else { e = newCorner(); findV(above, this, BOUNDARY_SPLIT, e); } // find defined between l.a and left Corner w = newCorner(); findV(left.a, left, BOUNDARY_SPLIT, w); if (!draw) { // check distances double d = getDistanceNoLoop(above, e, w, left.a); if (Double.isInfinite(d)) { // d > // maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOKNoLoop(maxBend, above, e, w, left.a)) { // angle ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { if (subAbove == null) { // new neighbors this.a = e; e.a = above; } // new neighbors w.a = left.a; left.a = w; // drawing addToDrawList(w.a, e, above, left.a, w); } } } } } else { if (above.p.isFinalUndefined()) { if (left.a.p.isFinalUndefined()) { // l defined /1/ // find defined between l and this Corner s; if (subLeft != null) { s = subLeft; } else { s = newCorner(); findU(left, this, BOUNDARY_SPLIT, s); } // find defined between l and l.a Corner w = newCorner(); findV(left, left.a, BOUNDARY_SPLIT, w); boolean split; if (draw) { // time to draw split = false; } else if (s.p.isFinalUndefined() || w.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else { // check distance double d = getDistance(left, s, w); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, left, s, w)) { // angle // ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // new neighbors if (subLeft == null) { this.l = s; s.l = left; } w.a = left.a; left.a = w; // drawing addToDrawList(w.a, s, w, left); } } else { // l and l.a defined /2/ boolean split; if (draw) { // time to draw split = false; } else if (subLeft != null && subLeft.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else { // check distance double d = getDistance(left.a, left); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, left.a, left)) { // angle // ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // find defined between l and this Corner s; if (subLeft != null) { s = subLeft; } else { s = newCorner(); findU(left, this, BOUNDARY_SPLIT, s); } // find defined between l.a and a Corner n = newCorner(); findU(left.a, above, BOUNDARY_SPLIT, n); if (!draw) { // check distances double d = getDistanceNoLoop(left.a, n, s, left); if (Double.isInfinite(d)) { // d > // maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOKNoLoop(maxBend, left.a, n, s, left)) { // angle ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { if (subLeft == null) { // new neighbors this.l = s; s.l = left; } // new neighbors n.l = left.a; above.l = n; // drawing addToDrawList(left.a, s, n, left.a, left); } } } } else { if (left.a.p.isFinalUndefined()) { // l and a not undefined /2/diag/ boolean split; if (draw) { // time to draw split = false; } else { // check distance double d = getDistance(left, above); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, left, above)) { // angle // ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // find defined between l and this Corner s; if (subLeft != null) { s = subLeft; } else { s = newCorner(); findU(left, this, BOUNDARY_SPLIT, s); // new neighbors this.l = s; s.l = left; } // find defined between a and this Corner e; if (subAbove != null) { e = subAbove; } else { e = newCorner(); findV(above, this, BOUNDARY_SPLIT, e); // new neighbors this.a = e; e.a = above; } // find defined between l and l.a Corner w = newCorner(); findV(left, left.a, BOUNDARY_SPLIT, w); w.a = left.a; left.a = w; // find defined between a and l.a Corner n = newCorner(); findU(above, left.a, BOUNDARY_SPLIT, n); n.l = above.l; above.l = n; // drawing addToDrawList(w.a, left, above); } } else { // l, a and l.a not undefined /3/ boolean split; if (draw) { // time to draw split = false; } else if (subLeft != null && subLeft.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else if (subAbove != null && subAbove.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else { // check distance double d = getDistance(left.a, left, above); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, left.a, left, above)) { // angle ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // find defined between l and this Corner s; if (subLeft != null) { s = subLeft; } else { s = newCorner(); findU(left, this, BOUNDARY_SPLIT, s); // new neighbors this.l = s; s.l = left; } // find defined between a and this Corner e; if (subAbove != null) { e = subAbove; } else { e = newCorner(); findV(above, this, BOUNDARY_SPLIT, e); // new neighbors this.a = e; e.a = above; } // drawing addToDrawList(left.a, left, above, left.a); } } } } } else { if (left.p.isFinalUndefined()) { if (above.p.isFinalUndefined()) { if (left.a.p.isFinalUndefined()) { // this defined /1/ // find defined between this and l Corner s; if (subLeft != null) { s = subLeft; } else { s = newCorner(); findU(this, left, BOUNDARY_SPLIT, s); } // find defined between this and a Corner e; if (subAbove != null) { e = subAbove; } else { e = newCorner(); findV(this, above, BOUNDARY_SPLIT, e); } boolean split; if (draw) { // time to draw split = false; } else if (s.p.isFinalUndefined() || e.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else { // check distance double d = getDistance(this, s, e); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, this, s, e)) { // angle // ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // new neighbors if (subLeft == null) { this.l = s; s.l = left; } if (subAbove == null) { this.a = e; e.a = above; } // drawing addToDrawList(left.a, s, e, this); } } else { // this and l.a not undefined /2/diag/ boolean split; if (draw) { // time to draw split = false; } else { // check distance double d = getDistance(left.a, this); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, left.a, this)) { // angle // ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // find defined between l and this Corner s; if (subLeft != null) { s = subLeft; } else { s = newCorner(); findU(this, left, BOUNDARY_SPLIT, s); // new neighbors this.l = s; s.l = left; } // find defined between a and this Corner e; if (subAbove != null) { e = subAbove; } else { e = newCorner(); findV(this, above, BOUNDARY_SPLIT, e); // new neighbors this.a = e; e.a = above; } // find defined between l and l.a Corner w = newCorner(); findV(left.a, left, BOUNDARY_SPLIT, w); w.a = left.a; left.a = w; // find defined between a and l.a Corner n = newCorner(); findU(left.a, above, BOUNDARY_SPLIT, n); n.l = above.l; above.l = n; // drawing addToDrawList(w.a, this, left.a); } } } else { if (left.a.p.isFinalUndefined()) { // this and a defined /2/ boolean split; if (draw) { // time to draw split = false; } else if (subLeft != null && subLeft.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else { // check distance double d = getDistance(this, above); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, this, above)) { // angle // ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // find defined between this and l Corner s; if (subLeft != null) { s = subLeft; } else { s = newCorner(); findU(this, left, BOUNDARY_SPLIT, s); } // find defined between a and l.a Corner n = newCorner(); findU(above, left.a, BOUNDARY_SPLIT, n); if (!draw) { // check distances double d = getDistanceNoLoop(this, s, n, above); if (Double.isInfinite(d)) { // d > // maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOKNoLoop(maxBend, this, s, n, above)) { // angle ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { if (subLeft == null) { // new neighbors this.l = s; s.l = left; } // new neighbors n.l = left.a; above.l = n; // drawing addToDrawList(left.a, this, above, n, s); } } } else { // this, a and l.a defined /3/ boolean split; if (draw) { // time to draw split = false; } else if (subLeft != null && subLeft.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else { // check distance double d = getDistance(above, left.a, this); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, above, left.a, this)) { // angle ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // find defined between this and l Corner s; if (subLeft != null) { s = subLeft; } else { s = newCorner(); findU(this, left, BOUNDARY_SPLIT, s); // new neighbors this.l = s; s.l = left; } // find defined between l.a and l Corner w = newCorner(); findV(left.a, left, BOUNDARY_SPLIT, w); // new neighbors w.a = left.a; left.a = w; // drawing addToDrawList(w.a, above, left.a, this); } } } } else { if (above.p.isFinalUndefined()) { if (left.a.p.isFinalUndefined()) { // this and l defined /2/ boolean split; if (draw) { // time to draw split = false; } else if (subAbove != null && subAbove.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else { // check distance double d = getDistance(this, left); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, this, left)) { // angle // ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // find defined between this and a Corner e; if (subAbove != null) { e = subAbove; } else { e = newCorner(); findV(this, above, BOUNDARY_SPLIT, e); } // find defined between l and l.a Corner w = newCorner(); findV(left, left.a, BOUNDARY_SPLIT, w); if (!draw) { // check distances double d = getDistanceNoLoop(this, e, w, left); if (Double.isInfinite(d)) { // d > // maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOKNoLoop(maxBend, this, e, w, left)) { // angle ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { if (subAbove == null) { // new neighbors this.a = e; e.a = above; } // new neighbors w.a = left.a; left.a = w; // drawing addToDrawList(w.a, this, e, w, left); } } } else { // this, l and l.a not undefined /3/ boolean split; if (draw) { // time to draw split = false; } else if (subAbove != null && subAbove.p.isFinalUndefined()) { // some // undefined // point: // force // split split = true; } else { // check distance double d = getDistance(left, left.a, this); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, left, left.a, this)) { // angle ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // find defined between l.a and a Corner n = newCorner(); findU(left.a, above, BOUNDARY_SPLIT, n); // find defined between this and a Corner e; if (subAbove != null) { e = subAbove; } else { e = newCorner(); findV(this, above, BOUNDARY_SPLIT, e); // new neighbors this.a = e; e.a = above; } // new neighbors n.l = left.a; above.l = n; // drawing addToDrawList(left.a, left, left.a, this); } } } else { if (left.a.p.isFinalUndefined()) { // this, l and a not undefined /3/ boolean split; if (draw) { // time to draw split = false; } else { // check distance double d = getDistance(this, left, above); if (Double.isInfinite(d)) { // d > maxRWDistance split = true; } else if (d > maxRWDistanceNoAngleCheck) { // check // angle if (isAngleOK(maxBend, this, left, above)) { // angle // ok split = false; } else { // angle not ok split = true; } } else { // no need to check angle split = false; } } if (split) { split(subLeft, left, subAbove, above); } else { // find defined between a and l.a Corner n = newCorner(); findU(above, left.a, BOUNDARY_SPLIT, n); // find defined between l and l.a Corner w = newCorner(); findV(left, left.a, BOUNDARY_SPLIT, w); // new neighbors n.l = left.a; above.l = n; // new neighbors w.a = left.a; left.a = w; // drawing addToDrawList(w.a, this, left, above); } } else { // this, l, a and l.a defined /4/ if (draw) { // drawing addToDrawList(left.a, this, left, above, left.a); } else { // check distances double d = getDistance(this, left, above, left.a); if (Double.isInfinite(d) || (d > maxRWDistanceNoAngleCheck && !isAngleOK(maxBend, this, left, above, left.a))) { split(subLeft, left, subAbove, above); } else { // drawing addToDrawList(left.a, this, left, above, left.a); } } } } } } } private void split(Corner subLeft, Corner left, Corner subAbove, Corner above) { // new corners double um = (u + left.u) / 2; double vm = (v + above.v) / 2; if (subLeft != null) { um = subLeft.u; } if (subAbove != null) { vm = subAbove.v; } Corner e; if (subAbove != null) { e = subAbove; } else { e = newCorner(u, vm); // new neighbors this.a = e; e.a = above; } Corner s; if (subLeft != null) { s = subLeft; } else { s = newCorner(um, v); // new neighbors this.l = s; s.l = left; } Corner m = newCorner(um, vm); s.a = m; e.l = m; Corner n = newCorner(um, above.v); n.l = above.l; above.l = n; m.a = n; Corner w = newCorner(left.u, vm); w.a = left.a; left.a = w; m.l = w; // next split addToNextSplit(this); addToNextSplit(s); addToNextSplit(e); addToNextSplit(m); loopSplitIndex += 4; } private void addToDrawList(Corner end, Corner... corners) { CornerAndCenter cc = drawList[drawListIndex]; if (cc == null) { cc = new CornerAndCenter(this, drawListIndex); drawList[drawListIndex] = cc; } else { cc.setCorner(this); } drawListIndex++; loopSplitIndex++; setBarycenter(cc.getCenter(), cc.getCenterNormal(), corners); end.isNotEnd = false; } private void findU(Corner defined, Corner undefined, int depth, Corner corner) { findU(defined.p, defined.u, defined.u, undefined.u, defined.v, depth, corner, true); } private void findU(Coords3 lastDefined, double uLastDef, double uDef, double uUndef, double vRow, int depth, Corner corner, boolean lastDefinedIsFirst) { double uNew = (uDef + uUndef) / 2; Coords3 coords = evaluatePoint(uNew, vRow); if (depth == 0) { // no more split if (coords.isFinalUndefined()) { // return last defined point if (lastDefinedIsFirst) { corner.set(uLastDef, vRow, lastDefined.copyVector()); } else { corner.set(uLastDef, vRow, lastDefined); } } else { corner.set(uNew, vRow, coords); } } else { if (coords.isFinalUndefined()) { findU(lastDefined, uLastDef, uDef, uNew, vRow, depth - 1, corner, lastDefinedIsFirst); } else { findU(coords, uNew, uNew, uUndef, vRow, depth - 1, corner, false); } } } private void findV(Corner defined, Corner undefined, int depth, Corner corner) { findV(defined.p, defined.v, defined.v, undefined.v, defined.u, depth, corner, true); } private void findV(Coords3 lastDefined, double vLastDef, double vDef, double vUndef, double uRow, int depth, Corner corner, boolean lastDefinedIsFirst) { double vNew = (vDef + vUndef) / 2; Coords3 coords = evaluatePoint(uRow, vNew); if (depth == 0) { // no more split if (coords.isFinalUndefined()) { // return last defined point if (lastDefinedIsFirst) { corner.set(uRow, vLastDef, lastDefined.copyVector()); } else { corner.set(uRow, vLastDef, lastDefined); } } else { corner.set(uRow, vNew, coords); } } else { if (coords.isFinalUndefined()) { findV(lastDefined, vLastDef, vDef, vNew, uRow, depth - 1, corner, lastDefinedIsFirst); } else { findV(coords, vNew, vNew, vUndef, uRow, depth - 1, corner, false); } } } } /** * set center as barycenter for points * * @param center * center * @param normal * normal for center point * @param c * corners * */ static protected void setBarycenter(Coords3 center, Coords3 normal, Corner... c) { setBarycenter(center, normal, c.length, c); } /** * set center as barycenter for points * * @param center * center * @param normal * normal for center point * @param length * length of considered corners * @param c * corners * */ static protected void setBarycenter(Coords3 center, Coords3 normal, int length, Corner... c) { double f = 1.0 / length; // // try first barycenter about parameters // double u = 0, v = 0; // for (int j = 0; j < length; j++) { // u += c[j].u; // v += c[j].v; // } // u *= f; // v *= f; // Coords3 ret = evaluatePoint(u, v, center); // if (ret.isNotFinalUndefined()){ // center is not undefined // ret = evaluateNormal(center, u, v, normal); // if (ret.isNotFinalUndefined()){ // normal is not undefined // return; // } // } // center is undefined : barycenter about coords center.set(0, 0, 0); normal.set(0, 0, 0); // int lengthDefined = 0; for (int j = 0; j < length; j++) { // if (!center.isFinalUndefined()){ center.addInside(c[j].p); normal.addInside(c[j].normal); // lengthDefined ++; // } } // f = 1.0 / lengthDefined; center.mulInside(f); normal.normalizeIfPossible(); // if (!center.isDefined()) { // App.printStacktrace("!center.isDefined()"); // } // if (!normal.isDefined()) { // App.printStacktrace("!normal.isDefined()"); // } } /** * used to draw "still to split" corners */ protected Corner[] cornerForStillToSplit, cornerToDrawStillToSplit; /** * max distance in real world from view */ private double maxRWPixelDistance; /** * max distance in real world for splitting */ private double maxRWDistance; /** * max distance in real world under which we don't check angles */ protected double maxRWDistanceNoAngleCheck; protected double maxBend; /** * * @param c1 * first corner * @param c2 * second corner * @return distance between c1 and c2, or POSITIVE_INFINITY if distance is * more than maxRWDistance */ protected double getDistance(Corner c1, Corner c2) { double ret = 0; double d = Math.abs(c1.p.getXd() - c2.p.getXd()); if (d > maxRWDistance) { return Double.POSITIVE_INFINITY; } if (d > ret) { ret = d; } d = Math.abs(c1.p.getYd() - c2.p.getYd()); if (d > maxRWDistance) { return Double.POSITIVE_INFINITY; } if (d > ret) { ret = d; } d = Math.abs(c1.p.getZd() - c2.p.getZd()); if (d > maxRWDistance) { return Double.POSITIVE_INFINITY; } if (d > ret) { ret = d; } return ret; } /** * * @param c1 * first corner * @param c2 * second corner * @param c3 * third corner * @param c4 * fourth corner * @return max distance between c1-c2 / c2-c3 / c3-c4 / c4-c1, or * POSITIVE_INFINITY if distance is more than maxRWDistance */ protected double getDistance(Corner c1, Corner c2, Corner c3, Corner c4) { double ret = 0; double d; d = getDistance(c1, c2); if (Double.isInfinite(d)) { return d; } ret = d; d = getDistance(c2, c3); if (Double.isInfinite(d)) { return d; } if (d > ret) { ret = d; } d = getDistance(c3, c4); if (Double.isInfinite(d)) { return d; } if (d > ret) { ret = d; } d = getDistance(c4, c1); if (Double.isInfinite(d)) { return d; } if (d > ret) { ret = d; } return ret; } /** * * @param c1 * first corner * @param c2 * second corner * @param c3 * third corner * @param c4 * fourth corner * @return max distance between c1-c2 / c2-c3 / c3-c4, or POSITIVE_INFINITY * if distance is more than maxRWDistance */ protected double getDistanceNoLoop(Corner c1, Corner c2, Corner c3, Corner c4) { double ret = 0; double d; d = getDistance(c1, c2); if (Double.isInfinite(d)) { return d; } ret = d; d = getDistance(c2, c3); if (Double.isInfinite(d)) { return d; } if (d > ret) { ret = d; } d = getDistance(c3, c4); if (Double.isInfinite(d)) { return d; } if (d > ret) { ret = d; } return ret; } /** * * @param c1 * first corner * @param c2 * second corner * @param c3 * third corner * @return max distance between c1-c2 / c2-c3 / c3-c1, or POSITIVE_INFINITY * if distance is more than maxRWDistance */ protected double getDistance(Corner c1, Corner c2, Corner c3) { double ret = 0; double d; d = getDistance(c1, c2); if (Double.isInfinite(d)) { return d; } ret = d; d = getDistance(c2, c3); if (Double.isInfinite(d)) { return d; } if (d > ret) { ret = d; } d = getDistance(c3, c1); if (Double.isInfinite(d)) { return d; } if (d > ret) { ret = d; } return ret; } /** * Returns whether the angle between the vectors (vx, vy) and (wx, wy) is * smaller than MAX_BEND, where MAX_BEND = tan(MAX_ANGLE). */ private static boolean isAngleOK(Coords3 v, Coords3 w, double bend) { // |v| * |w| * sin(alpha) = |det(v, w)| // cos(alpha) = v . w / (|v| * |w|) // tan(alpha) = sin(alpha) / cos(alpha) // tan(alpha) = |det(v, w)| / v . w // small angle: tan(alpha) < MAX_BEND // |det(v, w)| / v . w < MAX_BEND // |det(v, w)| < MAX_BEND * (v . w) double innerProduct = v.getXd() * w.getXd() + v.getYd() * w.getYd() + v.getZd() * w.getZd(); if (innerProduct <= 0) { // angle >= 90 degrees return false; } // angle < 90 degrees // small angle: |det(v, w)| < MAX_BEND * (v . w) double d1 = v.getXd() * w.getYd() - v.getYd() * w.getXd(); double d2 = v.getYd() * w.getZd() - v.getZd() * w.getYd(); double d3 = v.getZd() * w.getXd() - v.getXd() * w.getZd(); double det = Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3); return det < bend * innerProduct; } /** * * @param bend * @param c1 * @param c2 * @param c3 * @param c4 * @return true if angle is ok between c1-c2 */ protected static boolean isAngleOK(double bend, Corner c1, Corner c2) { if (!isAngleOK(c1.normal, c2.normal, bend)) { return false; } return true; } /** * * @param bend * @param c1 * @param c2 * @param c3 * @param c4 * @return true if angle is ok between c1-c2 and c2-c3 and c3-c1 */ protected static boolean isAngleOK(double bend, Corner c1, Corner c2, Corner c3) { if (!isAngleOK(c1.normal, c2.normal, bend)) { return false; } if (!isAngleOK(c2.normal, c3.normal, bend)) { return false; } if (!isAngleOK(c3.normal, c1.normal, bend)) { return false; } return true; } /** * * @param bend * @param c1 * @param c2 * @param c3 * @param c4 * @return true if angle is ok between c1-c2 and c2-c3 and c3-c4 and c4-c1 */ protected static boolean isAngleOK(double bend, Corner c1, Corner c2, Corner c3, Corner c4) { if (!isAngleOK(c1.normal, c2.normal, bend)) { return false; } if (!isAngleOK(c2.normal, c3.normal, bend)) { return false; } if (!isAngleOK(c3.normal, c4.normal, bend)) { return false; } if (!isAngleOK(c4.normal, c1.normal, bend)) { return false; } return true; } /** * * @param bend * @param c1 * @param c2 * @param c3 * @param c4 * @return true if angle is ok between c1-c2 and c2-c3 and c3-c4 */ protected static boolean isAngleOKNoLoop(double bend, Corner c1, Corner c2, Corner c3, Corner c4) { if (!isAngleOK(c1.normal, c2.normal, bend)) { return false; } if (!isAngleOK(c2.normal, c3.normal, bend)) { return false; } if (!isAngleOK(c3.normal, c4.normal, bend)) { return false; } return true; } class CornerAndCenter { private Corner corner; Coords3 center; Coords3 centerNormal; int id; public CornerAndCenter(Corner corner, int id) { center = newCoords3(); centerNormal = newCoords3(); setCorner(corner); this.id = id; } /** * set the corner * * @param corner * corner */ public void setCorner(Corner corner) { this.corner = corner; } /** * * @return corner */ public Corner getCorner() { return corner; } /** * * @return center */ public Coords3 getCenter() { return center; } /** * * @return center normal */ public Coords3 getCenterNormal() { return centerNormal; } public void drawDebug(PlotterSurface surface) { surface.startTrianglesWireFrame(); draw(surface); surface.endGeometryDirect(); surface.startTrianglesWireFrameSurface(); draw(surface); surface.endGeometryDirect(); } public void draw(PlotterSurface surface) { Corner current, sw, ne; Corner p1, p2; // go left current = corner; // get first defined point on south (if exists) Corner sw1 = current; Corner sw2 = sw1; // draw south p1 = sw1; do { p2 = current.l; if (p2.p.isNotFinalUndefined()) { if (p1.p.isNotFinalUndefined()) { if (sw1.p.isFinalUndefined()) { sw1 = p1; } drawTriangle(surface, this, p2, p1); } p1 = p2; sw2 = p2; } current = current.l; } while (current.a == null); sw = current; // go above current = corner; // get first defined point on east (if exists) Corner ne1 = current; Corner ne2 = ne1; // draw east p1 = ne1; do { p2 = current.a; if (p2.p.isNotFinalUndefined()) { if (p1.p.isNotFinalUndefined()) { drawTriangle(surface, this, p1, p2); if (ne1.p.isFinalUndefined()) { ne1 = p1; } } p1 = p2; ne2 = p2; } current = current.a; } while (current.l == null); ne = current; // west side current = sw; p1 = sw2; if (sw1.p.isFinalUndefined()) { sw1 = p1; } do { p2 = current.a; if (p2.p.isNotFinalUndefined()) { if (p1.p.isNotFinalUndefined()) { drawTriangle(surface, this, p2, p1); if (sw1.p.isFinalUndefined()) { sw1 = p1; } } p1 = p2; sw2 = p2; } current = current.a; } while (current.isNotEnd); // north side current = ne; p1 = ne2; if (ne1.p.isFinalUndefined()) { ne1 = p1; } do { p2 = current.l; if (p2.p.isNotFinalUndefined()) { if (p1.p.isNotFinalUndefined()) { drawTriangle(surface, this, p1, p2); if (ne1.p.isFinalUndefined()) { ne1 = p1; } } p1 = p2; ne2 = p2; } current = current.l; } while (current.isNotEnd); // closure triangles if needed if (sw1 != ne1) { drawTriangleCheckCorners(surface, this, sw1, ne1); } if (sw2 != ne2) { drawTriangleCheckCorners(surface, this, ne2, sw2); } if (ne1.p.isFinalUndefined() && ne2.p.isFinalUndefined()) { drawTriangleCheckCorners(surface, this, sw2, sw1); } if (sw1.p.isFinalUndefined() && sw2.p.isFinalUndefined()) { drawTriangleCheckCorners(surface, this, ne1, ne2); } } } /** * draw triangle with surface plotter * * @param surface * surface plotter * @param p0 * first point * @param n0 * first point normal * * @param c1 * second point * @param c2 * third point */ protected void drawTriangle(PlotterSurface surface, Coords3 p0, Coords3 n0, Corner c1, Corner c2) { surface.normalDirect(n0); surface.vertexDirect(p0); surface.normalDirect(c2.normal); surface.vertexDirect(c2.p); surface.normalDirect(c1.normal); surface.vertexDirect(c1.p); } /** * draws triangle between center and two corners * * @param surface * surface plotter * @param cc * center * @param c1 * first corner * @param c2 * second corner */ protected void drawTriangle(PlotterSurface surface, CornerAndCenter cc, Corner c1, Corner c2) { drawTriangle(surface, cc.center, cc.centerNormal, c1, c2); } /** * draw triangle with surface plotter, check if second and third points are * defined * * @param surface * surface plotter * @param cc * first point and normal * * @param c1 * second point * @param c2 * third point */ final protected void drawTriangleCheckCorners(PlotterSurface surface, CornerAndCenter cc, Corner c1, Corner c2) { if (c1.p.isFinalUndefined()) { return; } if (c2.p.isFinalUndefined()) { return; } drawTriangle(surface, cc, c1, c2); } /** * add the corner to next split array * * @param corner * corner */ protected void addToNextSplit(Corner corner) { nextSplit[nextSplitIndex] = corner; nextSplitIndex++; } /** * * @param u * first parameter * @param v * second parameter * @return new corner calculated for parameters u, v */ protected Corner newCorner(double u, double v) { Corner c = cornerList[cornerListIndex]; if (c == null) { c = new Corner(u, v, cornerListIndex); cornerList[cornerListIndex] = c; } else { c.set(u, v); } cornerListIndex++; return c; } /** * @return new corner */ protected Corner newCorner() { Corner c = cornerList[cornerListIndex]; if (c == null) { c = new Corner(cornerListIndex); cornerList[cornerListIndex] = c; } cornerListIndex++; return c; // return new Corner(); } final private static int HIT_SAMPLES = 10; final private static double DELTA_SAMPLES = 1.0 / HIT_SAMPLES; private double[] xyzuv; @Override public boolean hit(Hitting hitting) { if (waitForReset) { // prevent NPE return false; } if (getGeoElement() .getAlphaValue() < EuclidianController.MIN_VISIBLE_ALPHA_VALUE) { return false; } if (((GeoElement) surfaceGeo).isGeoFunctionNVar()) { GeoFunctionNVar geoF = (GeoFunctionNVar) surfaceGeo; hitting.calculateClippedValues(); if (Double.isNaN(hitting.x0)) { // hitting doesn't intersect // clipping box resetLastHitParameters(geoF); return false; } double[][] xyzf = geoF.getXYZF(); // compute samples from xyz0 to xyz1, try to find consecutive +/- geoF.setXYZ(hitting.x0, hitting.y0, hitting.z0, xyzf[GeoFunctionNVar.DICHO_LAST]); boolean isLessZ0 = false, isLessZ1; isLessZ1 = GeoFunctionNVar .isLessZ(xyzf[GeoFunctionNVar.DICHO_LAST]); double t = 0; for (int i = 1; i <= HIT_SAMPLES; i++) { double[] tmp = xyzf[GeoFunctionNVar.DICHO_FIRST]; xyzf[GeoFunctionNVar.DICHO_FIRST] = xyzf[GeoFunctionNVar.DICHO_LAST]; xyzf[GeoFunctionNVar.DICHO_LAST] = tmp; t = i * DELTA_SAMPLES; geoF.setXYZ(hitting.x0 * (1 - t) + hitting.x1 * t, hitting.y0 * (1 - t) + hitting.y1 * t, hitting.z0 * (1 - t) + hitting.z1 * t, xyzf[GeoFunctionNVar.DICHO_LAST]); isLessZ0 = isLessZ1; isLessZ1 = GeoFunctionNVar .isLessZ(xyzf[GeoFunctionNVar.DICHO_LAST]); if (isLessZ0 ^ isLessZ1) { break; // found } } // set - as first value, + as second value, or return false if (isLessZ0) { if (isLessZ1) { resetLastHitParameters(geoF); return false; } double dx = xyzf[GeoFunctionNVar.DICHO_FIRST][0] - hitting.origin.getX(); double dy = xyzf[GeoFunctionNVar.DICHO_FIRST][1] - hitting.origin.getY(); double dz = xyzf[GeoFunctionNVar.DICHO_FIRST][2] - hitting.origin.getZ(); double d = Math.sqrt(dx * dx + dy * dy + dz * dz); setZPick(-d, -d); setLastHitParameters(geoF, false); return true; } if (isLessZ1) { double dx = xyzf[GeoFunctionNVar.DICHO_FIRST][0] - hitting.origin.getX(); double dy = xyzf[GeoFunctionNVar.DICHO_FIRST][1] - hitting.origin.getY(); double dz = xyzf[GeoFunctionNVar.DICHO_FIRST][2] - hitting.origin.getZ(); double d = Math.sqrt(dx * dx + dy * dy + dz * dz); setZPick(-d, -d); setLastHitParameters(geoF, true); return true; } resetLastHitParameters(geoF); return false; } else if (((GeoElement) surfaceGeo).isGeoSurfaceCartesian()) { if (!getView3D().getApplication() .has(Feature.HIT_PARAMETRIC_SURFACE)) { return false; } // maybe set to null after redefine surfaceGeo.setDerivatives(); GeoSurfaceCartesian3D surface = (GeoSurfaceCartesian3D) surfaceGeo; surface.resetLastHitParameters(); hitting.calculateClippedValues(); if (Double.isNaN(hitting.x0)) { // hitting doesn't intersect // clipping box // TODO reset last hit parameters ? return false; } if (xyzuv == null) { xyzuv = new double[5]; } boolean found = surface.getBestColinear(hitting.x0, hitting.x1, hitting.y0, hitting.z0, hitting.vx, hitting.vy, hitting.vz, hitting.squareNorm, xyzuv); if (found) { double dx = xyzuv[0] - hitting.origin.getX(); double dy = xyzuv[1] - hitting.origin.getY(); double dz = xyzuv[2] - hitting.origin.getZ(); double d = Math.sqrt(dx * dx + dy * dy + dz * dz); setZPick(-d, -d); setZPick(-d, -d); surface.setLastHitParameters(xyzuv[3], xyzuv[4]); return true; } return false; } return false; } private static void setLastHitParameters(GeoFunctionNVar geoF, boolean swap) { geoF.setLastHitParameters(swap); } private static void resetLastHitParameters(GeoFunctionNVar geoF) { geoF.resetLastHitParameters(); } }