package org.geogebra.common.geogebra3D.kernel3D.geos; import org.geogebra.common.geogebra3D.kernel3D.transform.MirrorableAtPlane; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.PathParameter; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoPolyLine; import org.geogebra.common.kernel.geos.GeoPolygon; import org.geogebra.common.kernel.kernelND.GeoCoordSys2D; import org.geogebra.common.kernel.kernelND.GeoDirectionND; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoLineND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.kernelND.RotateableND; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.util.debug.Log; /** * Class extending {@link GeoPolygon} in 3D world. * * @author ggb3D * */ public class GeoPolyLine3D extends GeoPolyLine implements RotateableND, MirrorableAtPlane { private int index1; private int index2; private Coords direction1 = null; private Coords direction2 = null; private Coords direction3 = null; /** for possibly planar object */ private boolean isPlanar = false; private Coords normal = null; /** * common constructor for 3D. * * @param c * the construction * @param points * vertices */ public GeoPolyLine3D(Construction c, GeoPointND[] points) { super(c, points); } /** * @param c * construction */ public GeoPolyLine3D(Construction c) { super(c); } // /////////////////////////////////////// // GeoPolyLine3D @Override public GeoClass getGeoClassType() { return GeoClass.POLYLINE3D; } /** * it's a 3D GeoElement. * * @return true */ @Override public boolean isGeoElement3D() { return true; } @Override public boolean hasFillType() { return false; } // /////////////////////////////////////// // Overwrite GeoPolyLine @Override public GeoElement copyInternal(Construction cons1) { GeoPolyLine3D ret = new GeoPolyLine3D(cons1, null); ret.points = GeoElement.copyPointsND(cons1, points); ret.set(this); return ret; } @Override public void set(GeoElementND geo) { GeoPolyLine3D poly = (GeoPolyLine3D) geo; length = poly.length; defined = poly.defined; // make sure both arrays have same size if (points.length != poly.points.length) { GeoPointND[] tempPoints = new GeoPointND[poly.points.length]; for (int i = 0; i < tempPoints.length; i++) { tempPoints[i] = i < points.length ? points[i] : createNewPoint(); } points = tempPoints; } for (int i = 0; i < points.length; i++) { points[i].set(poly.points[i]); } } /** * The only place where GeoPoint3D is directly referred to * * @return 3D point */ protected GeoPointND createNewPoint() { return new GeoPoint3D(cons); } private GeoSegment3D seg = new GeoSegment3D(cons); private void setSegmentPoints(GeoPointND geoPoint, GeoPointND geoPoint2) { seg.setCoord(geoPoint, geoPoint2); } @Override public boolean isOnPath(GeoPointND P, double eps) { if (P.getPath() == this) { return true; } // check if P is on one of the segments for (int i = 0; i < points.length - 1; i++) { setSegmentPoints(points[i], points[i + 1]); if (seg.isOnPath(P, eps)) { return true; } } return false; } @Override public void pathChanged(GeoPointND P) { // if kernel doesn't use path/region parameters, do as if point changed // its coords if (!getKernel().usePathAndRegionParameters(P)) { pointChanged(P); return; } // parameter is between 0 and points.length - 1, // i.e. floor(parameter) gives the point index int index; PathParameter pp = P.getPathParameter(); double t = pp.getT(); if (t == points.length - 1) { // at very end of path index = points.length - 2; } else { t = t % (points.length - 1); if (t < 0) { t += (points.length - 1); } index = (int) Math.floor(t); } setSegmentPoints(points[index], points[index + 1]); double segParameter = t - index; // calc point for given parameter; must NOT doPathOrRegion if (tmpCoords == null) { tmpCoords = new Coords(4); } seg.getPointCoords(segParameter, tmpCoords); P.setCoords(tmpCoords, false); pp.setT(t); } private Coords tmpCoords; @Override public void pointChanged(GeoPointND P) { // double qx = P.x/P.z; // double qy = P.y/P.z; // double minDist = Double.POSITIVE_INFINITY; // double resx=0, resy=0, resz=0, param=0; PathParameter pp = P.getPathParameter(); double t = pp.getT(); double localT = 0; int index; // find projection on the line of current segment // if the projection is out of the segment, look at the next (or the // previous) segment index = (int) Math.floor(t); // direction indicates in which way we are stepping to prevent infinite // loop int direction = 0; while (index >= 0 && index < getNumPoints() - 1) { setSegmentPoints(points[index], points[index + 1]); localT = seg.getParamOnLine(P); if (localT < 0 && direction <= 0) { direction = -1; index--; } else if (localT > 1 && direction >= 0) { direction = 1; index++; } else { break; } } if (index >= getNumPoints() - 1) { index = getNumPoints() - 1; } else if (index < 0) { index = 0; } t = index + Math.min(1, Math.max(0, localT)); pp.setT(t); // udpate point using pathChanged pathChanged(P); } @Override public void calcLength() { // last point not checked in loop if (!points[points.length - 1].isDefined()) { setUndefined(); length = Double.NaN; return; } length = 0; for (int i = 0; i < points.length - 1; i++) { if (!points[i].isDefined()) { setUndefined(); length = Double.NaN; return; } setSegmentPoints(points[i], points[i + 1]); length += seg.getLength(); } setDefined(); } @Override public void rotate(NumberValue r) { // TODO } @Override public void rotate(NumberValue r, GeoPointND S) { // TODO } @Override public void matrixTransform(double a00, double a01, double a10, double a11) { // TODO } @Override public void matrixTransform(double a00, double a01, double a02, double a10, double a11, double a12, double a20, double a21, double a22) { // TODO } /** * @deprecated use getPointND(int i) */ @Deprecated @Override public GeoPoint getPoint(int i) { return null; } // ///////////////////// // /isPlanar /** * @return whether this is in plane */ public boolean isPlanar() { return isPlanar; } /** * Update the planar flag */ public void calcIsPlanar() { if (!isDefined()) { return; } if (getNumPoints() <= 3) { isPlanar = true; return; } normal = null; index1 = index2 = 0; direction1 = direction2 = direction3 = null; for (; index1 < getNumPoints() - 1; index1++) { if (!points[index1].getInhomCoordsInD3().equalsForKernel( points[0].getInhomCoordsInD3(), Kernel.STANDARD_PRECISION)) { direction1 = points[index1].getInhomCoordsInD3() .sub(points[0].getInhomCoordsInD3()); break; } } if (direction1 == null) { isPlanar = true; return; } for (index2 = index1 + 1; index2 < getNumPoints(); index2++) { direction2 = points[index2].getInhomCoordsInD3() .sub(points[index1].getInhomCoordsInD3()); normal = direction1.crossProduct(direction2); if (!normal.equalsForKernel(new Coords(0, 0, 0), Kernel.STANDARD_PRECISION)) { break; } direction2 = null; normal = null; } if (direction2 == null || index2 == getNumPoints() - 1) { isPlanar = true; return; } if (index2 + 1 < getNumPoints()) { direction3 = points[index2 + 1].getInhomCoordsInD3() .sub(points[index2].getInhomCoordsInD3()); if (!direction3.crossProduct(normal).equalsForKernel( new Coords(0, 0, 0), Kernel.STANDARD_PRECISION)) { isPlanar = false; return; } isPlanar = true; direction3 = null; return; } } public void rotate(NumberValue r, GeoPointND S, GeoDirectionND orientation) { for (int i = 0; i < points.length; i++) { ((RotateableND) points[i]).rotate(r, S, orientation); Log.debug(points[i]); } } public void rotate(NumberValue r, GeoLineND line) { for (int i = 0; i < points.length; i++) { ((RotateableND) points[i]).rotate(r, line); Log.debug(points[i]); } } public void mirror(GeoCoordSys2D plane) { for (int i = 0; i < points.length; i++) { ((MirrorableAtPlane) points[i]).mirror(plane); Log.debug(points[i]); } } }