/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ package org.geogebra.common.kernel.geos; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.MyPoint; import org.geogebra.common.kernel.PathMover; import org.geogebra.common.kernel.PathMoverGeneric; import org.geogebra.common.kernel.PathParameter; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.Transform; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.algos.AlgoJoinPointsSegment; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.NumberValue; 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.GeoSegmentND; import org.geogebra.common.main.Feature; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.util.ExtendedBoolean; /** * @author Markus Hohenwarter */ final public class GeoSegment extends GeoLine implements GeoSegmentND { // GeoSegment is constructed by AlgoJoinPointsSegment // private GeoPoint A, B; private double length; private boolean defined; private boolean allowOutlyingIntersections = false; private boolean keepTypeOnGeometricTransform = true; // for mirroring, // rotation, ... private boolean isShape = false; /** no decoration */ public static final int SEGMENT_DECORATION_NONE = 0; /** one tick */ public static final int SEGMENT_DECORATION_ONE_TICK = 1; /** two ticks */ public static final int SEGMENT_DECORATION_TWO_TICKS = 2; /** three ticks */ public static final int SEGMENT_DECORATION_THREE_TICKS = 3; /** one arrow */ public static final int SEGMENT_DECORATION_ONE_ARROW = 4; /** two arrows */ public static final int SEGMENT_DECORATION_TWO_ARROWS = 5; /** three arrows */ public static final int SEGMENT_DECORATION_THREE_ARROWS = 6; /** * Returns array of all decoration types * * @see #SEGMENT_DECORATION_ONE_TICK etc. * @return array of all decoration types */ public static final Integer[] getDecoTypes() { Integer[] ret = { Integer.valueOf(SEGMENT_DECORATION_NONE), Integer.valueOf(SEGMENT_DECORATION_ONE_TICK), Integer.valueOf(SEGMENT_DECORATION_TWO_TICKS), Integer.valueOf(SEGMENT_DECORATION_THREE_TICKS), Integer.valueOf(SEGMENT_DECORATION_ONE_ARROW), Integer.valueOf(SEGMENT_DECORATION_TWO_ARROWS), Integer.valueOf(SEGMENT_DECORATION_THREE_ARROWS) }; return ret; } @Override public void setDecorationType(int type) { setDecorationType(type, getDecoTypes().length); } /** * Creates new segment * * @param c * construction * @param A * first endpoint * @param B * second endpoint */ public GeoSegment(Construction c, GeoPoint A, GeoPoint B) { this(c); setPoints(A, B); } /** * common constructor * * @param c * construction */ public GeoSegment(Construction c) { super(c); setConstructionDefaults(); } /** * sets start and end points * * @param A * start point * @param B * end point */ public void setPoints(GeoPoint A, GeoPoint B) { setStartPoint(A); setEndPoint(B); } @Override public void setTwoPointsInhomCoords(Coords start, Coords end) { this.startPoint.setCoords(start.get(1), start.get(2), 1); this.endPoint.setCoords(end.get(1), end.get(2), 1); // set x, y, z coords for equation setCoords(start.getY() - end.getY(), end.getX() - start.getX(), start.getX() * end.getY() - start.getY() * end.getX()); setPoints(this.startPoint, this.endPoint); calcLength(); } @Override public GeoClass getGeoClassType() { return GeoClass.SEGMENT; } /** * the copy of a segment is a number (!) with its value set to the segments * current length * * public GeoElement copy() { return new GeoNumeric(cons, getLength()); } */ @Override public GeoElement copyInternal(Construction cons1) { GeoSegment seg; if (!this.isDefined()) { seg = new GeoSegment(cons1); } else { seg = new GeoSegment(cons1, (GeoPoint) startPoint.copyInternal(cons1), (GeoPoint) endPoint.copyInternal(cons1)); } seg.set(this); return seg; } @Override public void set(GeoElementND geo) { super.set(geo); if (!geo.isGeoSegment()) { return; } GeoSegment seg = (GeoSegment) geo; length = seg.length; defined = seg.defined; keepTypeOnGeometricTransform = seg.keepTypeOnGeometricTransform; startPoint = (GeoPoint) GeoLine.updatePoint(cons, startPoint, seg.startPoint); endPoint = (GeoPoint) GeoLine.updatePoint(cons, endPoint, seg.endPoint); } /** * @param s * start point * @param e * end point * @param line * line */ public void set(GeoPoint s, GeoPoint e, GeoVec3D line) { super.set(line); setStartPoint(s); setEndPoint(e); calcLength(); } @Override public void setVisualStyle(GeoElement geo) { super.setVisualStyle(geo); if (geo.isGeoSegment()) { GeoSegment seg = (GeoSegment) geo; allowOutlyingIntersections = seg.allowOutlyingIntersections; } } /** * Calculates this segment's length . This method should only be called by * its parent algorithm of type AlgoJoinPointsSegment */ public void calcLength() { defined = startPoint.isFinite() && endPoint.isFinite(); if (defined) { length = startPoint.distance(endPoint); if (Kernel.isZero(length)) { length = 0; } } else { length = Double.NaN; } } @Override public double getLength() { return length; } /* * overwrite GeoLine methods */ @Override public boolean isDefined() { return defined; } @Override public void setUndefined() { super.setUndefined(); length = Double.NaN; defined = false; } @Override public final boolean showInAlgebraView() { // return defined; return true; } @Override public boolean showInEuclidianView() { // segments of polygons can have thickness 0 return defined && getLineThickness() != 0; } /** * Yields true iff startpoint and endpoint of s are equal to startpoint and * endpoint of this segment. */ // Michael Borcherds 2008-05-01 @Override public boolean isEqual(GeoElementND geo) { // test 3D is geo is 3D if (geo.isGeoElement3D()) { return geo.isEqual(this); } if (!geo.isGeoSegment()) { return false; } GeoSegmentND s = (GeoSegmentND) geo; return ((startPoint.isEqualPointND(s.getStartPoint()) && endPoint.isEqualPointND(s.getEndPoint())) || (startPoint.isEqualPointND(s.getEndPoint()) && endPoint.isEqualPointND(s.getStartPoint()))); } @Override final public String toString(StringTemplate tpl) { sbToString.setLength(0); sbToString.append(label); sbToString.append(" = "); sbToString.append(kernel.format(length, tpl)); return sbToString.toString(); } @Override final public String toStringMinimal(StringTemplate tpl) { sbToString.setLength(0); sbToString.append(regrFormat(length)); return sbToString.toString(); } private StringBuilder sbToString = new StringBuilder(30); private boolean forceSimpleTransform; @Override final public String toValueString(StringTemplate tpl) { return kernel.format(length, tpl); } /** * interface NumberValue */ @Override public MyDouble getNumber() { return new MyDouble(kernel, getLength()); } @Override final public double getDouble() { return getLength(); } @Override final public boolean isConstant() { return false; } @Override public boolean isNumberValue() { return true; } @Override public boolean allowOutlyingIntersections() { return allowOutlyingIntersections; } @Override public void setAllowOutlyingIntersections(boolean flag) { allowOutlyingIntersections = flag; } @Override public boolean keepsTypeOnGeometricTransform() { return keepTypeOnGeometricTransform; } @Override public void setKeepTypeOnGeometricTransform(boolean flag) { keepTypeOnGeometricTransform = flag; } @Override final public boolean isLimitedPath() { return true; } @Override public boolean isIntersectionPointIncident(GeoPoint p, double eps) { if (allowOutlyingIntersections) { return isOnFullLine(p, eps); } return isOnPath(p, eps); } /* * GeoSegmentInterface interface */ @Override public GeoElement getStartPointAsGeoElement() { return getStartPoint(); } @Override public GeoElement getEndPointAsGeoElement() { return getEndPoint(); } @Override public double getPointX(double parameter) { return startPoint.inhomX + parameter * y; } @Override public double getPointY(double parameter) { return startPoint.inhomY - parameter * x; } /* * Path interface */ @Override public void pointChanged(GeoPointND P) { PathParameter pp = P.getPathParameter(); // special case: segment of length 0 if (length == 0) { P.setCoords2D(startPoint.inhomX, startPoint.inhomY, 1); P.updateCoordsFrom2D(false, null); if (!(pp.t >= 0 && pp.t <= 1)) { pp.t = 0.0; } return; } // project point on line super.pointChanged(P); // ensure that the point doesn't get outside the segment // i.e. ensure 0 <= t <= 1 if (pp.t < 0.0) { P.setCoords2D(startPoint.x, startPoint.y, startPoint.z); P.updateCoordsFrom2D(false, null); pp.t = 0.0; } else if (pp.t > 1.0) { P.setCoords2D(endPoint.x, endPoint.y, endPoint.z); P.updateCoordsFrom2D(false, null); pp.t = 1.0; } } @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; } PathParameter pp = P.getPathParameter(); // special case: segment of length 0 if (length == 0) { P.setCoords2D(startPoint.inhomX, startPoint.inhomY, 1); P.updateCoordsFrom2D(false, null); if (!(pp.t >= 0 && pp.t <= 1)) { pp.t = 0.0; } return; } if (pp.t < 0.0) { pp.t = 0; } else if (pp.t > 1.0) { pp.t = 1; } // calc point for given parameter P.setCoords2D(startPoint.inhomX + pp.t * y, startPoint.inhomY - pp.t * x, 1); P.updateCoordsFrom2D(false, null); } /** * Returns the smallest possible parameter value for this path. * * @return smallest possible parameter */ @Override public double getMinParameter() { return 0; } /** * Returns the largest possible parameter value for this path. * * @return largest possible parameter */ @Override public double getMaxParameter() { return 1; } @Override public PathMover createPathMover() { return new PathMoverGeneric(this); } /** * returns all class-specific xml tags for saveXML */ @Override protected void getXMLtags(StringBuilder sb) { super.getXMLtags(sb); // allowOutlyingIntersections sb.append("\t<outlyingIntersections val=\""); sb.append(allowOutlyingIntersections); sb.append("\"/>\n"); // keepTypeOnGeometricTransform sb.append("\t<keepTypeOnTransform val=\""); sb.append(keepTypeOnGeometricTransform); sb.append("\"/>\n"); } /** * creates new transformed segment */ @Override public GeoElement[] createTransformedObject(Transform t, String transformedLabel) { if (keepTypeOnGeometricTransform && t.isAffine()) { // mirror endpoints GeoPointND[] points = { getStartPoint(), getEndPoint() }; points = t.transformPoints(points); // create SEGMENT GeoElement segment = (GeoElement) kernel.segmentND(transformedLabel, points[0], points[1]); segment.setVisualStyleForTransformations(this); GeoElement[] geos = { segment, (GeoElement) points[0], (GeoElement) points[1] }; return geos; } else if (!t.isAffine()) { // mirror endpoints // boolean oldSuppressLabelCreation = cons.isSuppressLabelsActive(); // cons.setSuppressLabelCreation(true); this.forceSimpleTransform = true; GeoElement[] geos = { t.transform(this, transformedLabel)[0] }; return geos; } else { // create LINE GeoElement transformedLine = t.getTransformedLine(this); transformedLine.setLabel(transformedLabel); transformedLine.setVisualStyleForTransformations(this); GeoElement[] geos = { transformedLine }; return geos; } } @Override public boolean isGeoSegment() { return true; } @Override public void setZero() { setCoords(0, 1, 0); } ////////////////////////////////////// // 3D stuff ////////////////////////////////////// @Override public boolean hasDrawable3D() { return true; } @Override public Coords getLabelPosition() { return new Coords(getPointX(0.5), getPointY(0.5), 0, 1); } /** * returns the paramter for the closest point to P on the Segment * (extrapolated) so answers can be returned outside the range [0,1] * * @param ptx * point x-coord * @param pty * point y-coord * @return closest parameter */ final public double getParameter(double ptx, double pty) { double px = ptx; double py = pty; // project P on line // param of projection point on perpendicular line double t = -(z + x * px + y * py) / (x * x + y * y); // calculate projection point using perpendicular line px += t * x; py += t * y; // calculate parameter if (Math.abs(x) <= Math.abs(y)) { return (startPoint.z * px - startPoint.x) / (y * startPoint.z); } return (startPoint.y - startPoint.z * py) / (x * startPoint.z); } /** * Calculates the euclidian distance between this GeoSegment and GeoPoint P. * * returns distance from endpoints if appropriate */ @Override final public double distance(GeoPoint p) { double t = getParameter(p.inhomX, p.inhomY); // if t is outside the range [0,1] then the closest point is not on the // Segment if (t < 0) { return p.distance(startPoint); } if (t > 1) { return p.distance(endPoint); } return super.distance(p); } @Override final public double distance(double x0, double y0) { double t = getParameter(x0, y0); // if t is outside the range [0,1] then the closest point is not on the // Segment if (t < 0) { return startPoint.distance(x0, y0); } if (t > 1) { return endPoint.distance(x0, y0); } return super.distance(x0, y0); } private Coords pnt2D; @Override public boolean isOnPath(Coords Pnd, double eps) { if (pnt2D == null) { pnt2D = new Coords(3); } pnt2D.setCoordsIn2DView(Pnd); if (!super.isOnFullLine2D(pnt2D, eps)) { return false; } return respectLimitedPath(pnt2D, eps); } @Override public boolean respectLimitedPath(Coords Pnd, double eps) { if (pnt2D == null) { pnt2D = new Coords(3); } pnt2D.setCoordsIn2DView(Pnd); PathParameter pp = getTempPathParameter(); doPointChanged(pnt2D, pp); double t = pp.getT(); return t >= -eps && t <= 1 + eps; } /** * exact calculation for checking if point is on Segment[segStart,segEnd] * * @param segStart * start coords * @param segEnd * end coords * @param point * point to be checked * @param checkOnFullLine * - if true, do extra calculation to make sure. * @param eps * precision * @return true if point belongs to segment */ public static boolean checkOnPath(Coords segStart, Coords segEnd, Coords point, boolean checkOnFullLine, double eps) { if (checkOnFullLine) { if (segEnd.sub(segStart).crossProduct(point.sub(segStart)) .equalsForKernel(new Coords(0, 0, 0), Kernel.STANDARD_PRECISION)) { return false; } } double x1 = segStart.getInhom(0); double x2 = segEnd.getInhom(0); double x = point.getInhom(0); if (x1 - eps <= x2 && x2 <= x1 + eps) { double y1 = segStart.getInhom(1); double y2 = segEnd.getInhom(1); double y = point.getInhom(1); if (y1 - eps <= y2 && y2 <= y1 + eps) { return true; } return y1 - eps <= y && y <= y2 + eps || y2 - eps <= y && y <= y1 + eps; } return x1 - eps <= x && x <= x2 + eps || x2 - eps <= x && x <= x1 + eps; } @Override public boolean isAllEndpointsLabelsSet() { return !forceSimpleTransform && startPoint.isLabelSet() && endPoint.isLabelSet(); } @Override public void modifyInputPoints(GeoPointND P, GeoPointND Q) { AlgoJoinPointsSegment algo = (AlgoJoinPointsSegment) getParentAlgorithm(); algo.modifyInputPoints(P, Q); } private GeoElement meta = null; @Override public int getMetasLength() { if (meta == null) { return 0; } return 1; } @Override public GeoElement[] getMetas() { return new GeoElement[] { meta }; } /** * @param poly * polygon or polyhedron creating this segment */ public void setFromMeta(GeoElement poly) { meta = poly; } @Override public boolean respectLimitedPath(double parameter) { return Kernel.isGreaterEqual(parameter, 0) && Kernel.isGreaterEqual(1, parameter); } /** * dilate from S by r */ @Override final public void dilate(NumberValue rval, Coords S) { super.dilate(rval, S); startPoint.dilate(rval, S); endPoint.dilate(rval, S); calcLength(); } /** * rotate this line by angle phi around (0,0) */ @Override final public void rotate(NumberValue phiVal) { super.rotate(phiVal); startPoint.rotate(phiVal); endPoint.rotate(phiVal); // not needed for rotate // calcLength(); } /** * rotate this line by angle phi around Q */ @Override final public void rotate(NumberValue phiVal, GeoPointND point) { super.rotate(phiVal, point); Coords sCoords = point.getInhomCoords(); startPoint.rotate(phiVal, sCoords); endPoint.rotate(phiVal, sCoords); // not needed for rotate // calcLength(); } /** * mirror this line at point Q */ @Override final public void mirror(Coords Q) { super.mirror(Q); startPoint.mirror(Q); endPoint.mirror(Q); // not needed for mirror // calcLength(); } /** * mirror this point at line g */ @Override final public void mirror(GeoLineND g1) { super.mirror(g1); startPoint.mirror(g1); endPoint.mirror(g1); // not needed for mirror // calcLength(); } /** * translate by vector v */ @Override final public void translate(Coords v) { super.translate(v); startPoint.translate(v); endPoint.translate(v); // not needed for mirror // calcLength(); } @Override final public void matrixTransform(double p, double q, double r, double s) { super.matrixTransform(p, q, r, s); startPoint.matrixTransform(p, q, r, s); endPoint.matrixTransform(p, q, r, s); calcLength(); } @Override final public void matrixTransform(double a00, double a01, double a02, double a10, double a11, double a12, double a20, double a21, double a22) { super.matrixTransform(a00, a01, a02, a10, a11, a12, a20, a21, a22); startPoint.matrixTransform(a00, a01, a02, a10, a11, a12, a20, a21, a22); endPoint.matrixTransform(a00, a01, a02, a10, a11, a12, a20, a21, a22); calcLength(); } @Override public void setCoords(MyPoint locusPoint, MyPoint locusPoint2) { double x1 = locusPoint.x; double x2 = locusPoint2.x; double y1 = locusPoint.y; double y2 = locusPoint2.y; // line thro' 2 points setCoords(y1 - y2, x2 - x1, x1 * y2 - y1 * x2); startPoint.setCoords(x1, y1, 1.0); endPoint.setCoords(x2, y2, 1.0); } @Override public GeoElement copyFreeSegment() { GeoPoint startPoint1 = (GeoPoint) getStartPoint().copyInternal(cons); GeoPoint endPoint1 = (GeoPoint) getEndPoint().copyInternal(cons); AlgoJoinPointsSegment algo = new AlgoJoinPointsSegment(cons, null, startPoint1, endPoint1); return algo.getSegment(); } @Override public ExtendedBoolean isCongruent(GeoElement geo) { return ExtendedBoolean.newExtendedBoolean(geo.isGeoSegment() && Kernel .isEqual(getLength(), ((GeoSegmentND) geo).getLength())); } @Override public void setChangeableCoordParentIfNull(ChangeableCoordParent ccp) { // used for GeoPoint3D } @Override public boolean isShape() { return kernel.getApplication().has(Feature.BOUNDING_BOXES) && isShape; } /** * @param isShape * - true, if geo was created with shape tool */ @Override public void setIsShape(boolean isShape) { this.isShape = isShape; } }