/*
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.
*/
/*
* GeoLine.java
*
* Created on 30. August 2001, 17:39
*/
package org.geogebra.common.kernel.geos;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.MatrixTransformable;
import org.geogebra.common.kernel.Path;
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.Matrix.CoordMatrix;
import org.geogebra.common.kernel.Matrix.CoordMatrixUtil;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.algos.AlgoDependentFunction;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.algos.SymbolicParameters;
import org.geogebra.common.kernel.algos.SymbolicParametersAlgo;
import org.geogebra.common.kernel.algos.SymbolicParametersBotanaAlgo;
import org.geogebra.common.kernel.algos.TangentAlgo;
import org.geogebra.common.kernel.arithmetic.Equation;
import org.geogebra.common.kernel.arithmetic.EquationValue;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.Function;
import org.geogebra.common.kernel.arithmetic.FunctionVariable;
import org.geogebra.common.kernel.arithmetic.Functional;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.arithmetic.PolyFunction;
import org.geogebra.common.kernel.arithmetic.ValueType;
import org.geogebra.common.kernel.commands.Commands;
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.prover.NoSymbolicParametersException;
import org.geogebra.common.kernel.prover.polynomial.PPolynomial;
import org.geogebra.common.kernel.prover.polynomial.PVariable;
import org.geogebra.common.plugin.GeoClass;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.util.ExtendedBoolean;
import org.geogebra.common.util.MyMath;
/**
* Geometrical representation of line
*
* @author Markus
*
*/
public class GeoLine extends GeoVec3D implements Path, Translateable,
PointRotateable, Mirrorable, Dilateable, GeoLineND, MatrixTransformable,
GeoFunctionable, Transformable, Functional, SymbolicParametersAlgo,
SymbolicParametersBotanaAlgo, EquationValue {
// modes
/** implicit equation */
public static final int EQUATION_IMPLICIT = 0;
/** explicit equation */
public static final int EQUATION_EXPLICIT = 1;
/** parametric equation */
public static final int PARAMETRIC = 2;
/** non-canonical implicit equation */
public static final int EQUATION_IMPLICIT_NON_CANONICAL = 3;
/** general form **/
public static final int EQUATION_GENERAL = 4;
/** user input **/
public static final int EQUATION_USER = 5;
private boolean showUndefinedInAlgebraView = false;
private String parameter = "\u03bb";
/** start point */
public GeoPoint startPoint;
/** end point */
public GeoPoint endPoint;
// enable negative sign of first coefficient in implicit equations
private static boolean KEEP_LEADING_SIGN = true;
private static final String[] vars = { "x", "y" };
private PVariable[] botanaVars; // only for an axis or a fixed slope line
/**
* Creates new line
*
* @param c
* construction
*/
public GeoLine(Construction c) {
super(c);
setConstructionDefaults();
}
/**
* Creates new line
*
* @param c
* construction
* @param mode
* string mode (GeoLine.EQUATION_*)
*/
public GeoLine(Construction c, int mode) {
this(c);
setMode(mode);
}
/**
* Creates new GeoLine
*
* @param cons
* construction
* @param a
* x-coefficient
* @param b
* y-coefficient
* @param c
* z-coefficient
*/
public GeoLine(Construction cons, double a, double b, double c) {
super(cons, a, b, c); // GeoVec3D constructor
setConstructionDefaults();
}
/**
* Copy constructor
*
* @param line
* line to copy
*/
public GeoLine(GeoLine line) {
this(line.cons);
set(line);
}
@Override
public GeoClass getGeoClassType() {
return GeoClass.LINE;
}
@Override
public GeoLine copy() {
return new GeoLine(this);
}
@Override
final public void setCoords(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
setDefinition(null);
// Application.debug("x="+x+", y="+y+", z="+z);
}
/**
* set the line to pass through (pointX, pointY)
*
* @param pointX
* x coord
* @param pointY
* y coord
*/
@Override
final public void setLineThrough(double pointX, double pointY) {
setCoords(x, y, -((x * pointX) + (y * pointY)));
}
@Override
final public void setCoords(GeoVec3D v) {
setCoords(v.x, v.y, v.z);
/*
* x = v.x; y = v.y; z = v.z;
*/
}
/**
* returns true if P lies on this line
*
* @param p
* point
* @param eps
* precision
* @return true if P lies on this line
*/
public boolean isIntersectionPointIncident(GeoPoint p, double eps) {
return isOnFullLine(p, eps);
}
/**
* States wheter P lies on this line or not.
*
* @return true iff P lies on this line
* @param P
* point
* @param eps
* precision (ratio of allowed error and |x|+|y|)
*/
public final boolean isOnFullLine(GeoPoint P, double eps) {
if (!P.isDefined()) {
return false;
}
double simplelength = Math.abs(x) + Math.abs(y);
if (P.isInfinite()) {
return Math.abs(x * P.x + y * P.y) < eps * simplelength;
}
// STANDARD CASE: finite point
return Math.abs(x * P.inhomX + y * P.inhomY + z) < eps * simplelength;
}
@Override
public final boolean isOnFullLine(Coords Pnd, double eps) {
Coords P = Pnd.getCoordsIn2DView();
return isOnFullLine2D(P, eps);
}
/**
* @param P
* 2D coords of the point
* @param eps
* precision
* @return whether point lies on this line within given precision
*/
public final boolean isOnFullLine2D(Coords P, double eps) {
double simplelength = Math.abs(x) + Math.abs(y);
if (Kernel.isZero(P.getZ())) { // infinite point
return Math.abs(x * P.getX() + y * P.getY()) < eps * simplelength;
}
// STANDARD CASE: finite point
return Math.abs(
x * P.getX() / P.getZ() + y * P.getY() / P.getZ() + z) < eps
* simplelength;
}
/**
* Returns whether this point lies on this line, segment or ray.
*/
@Override
final public boolean isOnPath(GeoPointND PI, double eps) {
GeoPoint P = (GeoPoint) PI;
if (P.getPath() == this) {
return true;
}
// check if P lies on line first
if (!isOnFullLine(P, eps)) {
return false;
}
// for a line we are done here: the point is on the line
// for rays and segments we need to continue
GeoClass classType = getGeoClassType();
if (classType.equals(GeoClass.LINE)) {
return true;
}
// idea: calculate path parameter and check
// if it is in [0, 1] for a segment or greater than 0 for a ray
// remember the old point coordinates
double px = P.x, py = P.y, pz = P.z;
PathParameter tempParam = getTempPathParameter();
PathParameter pp = P.getPathParameter();
tempParam.set(pp);
// make sure we use point changed for a line to get parameters on
// the entire line when this is a segment or ray
doPointChanged(P);
boolean result;
switch (classType) {
case SEGMENT:
// segment: parameter in [0,1]
result = pp.t >= -eps && pp.t <= 1 + eps;
break;
case RAY:
// ray: parameter > 0
result = pp.t >= -eps;
break;
default:
// line: any parameter
result = true;
}
// restore old values
P.x = px;
P.y = py;
P.z = pz;
pp.set(tempParam);
return result;
}
@Override
public boolean isOnPath(Coords Pnd, double eps) {
Coords P2d = Pnd.getCoordsIn2DView();
return isOnFullLine2D(P2d, eps);
}
/**
* Returns whether three lines are concurrent
*
* @param inputLine1
* first input
* @param inputLine2
* second input
* @param inputLine3
* third input
* @return if the inputs are concurrent
*/
final public static boolean concurrent(GeoLine inputLine1,
GeoLine inputLine2, GeoLine inputLine3) {
double det = inputLine1.getX() * inputLine2.getY() * inputLine3.getZ()
+ inputLine2.getX() * inputLine3.getY() * inputLine1.getZ()
+ inputLine3.getX() * inputLine1.getY() * inputLine2.getZ()
- inputLine3.getX() * inputLine2.getY() * inputLine1.getZ()
- inputLine2.getX() * inputLine1.getY() * inputLine3.getZ()
- inputLine1.getX() * inputLine3.getY() * inputLine2.getZ();
return Kernel.isZero(det);
}
@Override
public boolean respectLimitedPath(Coords coords, double eps) {
return true;
}
/**
* return a possible parameter for the point P (return the parameter for the
* projection of P on the path)
*
* @param coords
* point whose possible parameter we need
* @return a possible parameter for the point P
*/
public double getPossibleParameter(Coords coords) {
PathParameter tempParam = getTempPathParameter();
// make sure we use point changed for a line to get parameters on
// the entire line when this is a segment or ray
doPointChanged(coords, tempParam);
return tempParam.t;
}
private PathParameter tempPP;
/**
* @return temporary path parameter
*/
protected PathParameter getTempPathParameter() {
if (tempPP == null) {
tempPP = new PathParameter();
}
return tempPP;
}
/**
* @param g
* line
* @return true if this line and g are parallel
*/
final public boolean isParallel(GeoLine g) {
return Kernel.isEqual(g.x * y, g.y * x);
}
/**
* @param g
* line
* @return true if this line and g are parallel (signed)
*/
final public boolean isSameDirection(GeoLine g) {
// check x and g.x have the same sign
// also y and g.y
return (g.x * x >= 0) && (g.y * y >= 0) && isParallel(g);
}
/**
* @param g
* line
* @return true if this line and g are perpendicular
*/
final public boolean isPerpendicular(GeoLine g) {
return Kernel.isEqual(g.x * x, -g.y * y);
}
/**
* Calculates the euclidian distance between this GeoLine and (px, py).
*/
@Override
public double distance(GeoPoint p) {
return distance(p.inhomX, p.inhomY);
}
/**
* Calculates the euclidian distance between this GeoLine and (x0, y0).
*
* @param x0
* x coord
* @param y0
* y coord
* @return distance
*/
public double distance(double x0, double y0) {
return Math.abs((x * x0 + y * y0 + z) / MyMath.length(x, y));
}
/**
* Calculates the euclidian distance between this GeoLine and GeoPoint P.
* Here the inhomogenouse coords of p are calculated and p.inhomX, p.inhomY
* are not used.
*
* @param p
* point
* @return distance between this line and a point
*/
final public double distanceHom(GeoPoint p) {
return Math
.abs((x * p.x / p.z + y * p.y / p.z + z) / MyMath.length(x, y));
}
/**
*
* @param p
* coords to which we compute the distance
* @return the euclidian distance between this GeoLine and 2D point p.
*/
final public double distanceHom(Coords p) {
return Math.abs((x * p.getX() / p.getZ() + y * p.getY() / p.getZ() + z)
/ MyMath.length(x, y));
}
/**
* Calculates the distance between this GeoLine and GeoLine g.
*
* @param g
* line
* @return distance between lines
*/
final public double distance(GeoLine g) {
// parallel
if (Kernel.isZero(g.x * y - g.y * x)) {
// get a point (px, py) of g and calc distance
double px, py;
if (Math.abs(g.x) > Math.abs(g.y)) {
px = -g.z / g.x;
py = 0.0d;
} else {
px = 0.0d;
py = -g.z / g.y;
}
return Math.abs((x * px + y * py + z) / MyMath.length(x, y));
}
return 0.0;
}
/**
* @param out
* vector to store direction
*/
final public void getDirection(GeoVec3D out) {
out.setCoords(y, -x, 0.0d);
}
/**
* Writes coords of direction vector to array dir.
*
* @param dir
* array of length 2
*/
final public void getDirection(double[] dir) {
dir[0] = y;
dir[1] = -x;
}
/**
* Set array p to (x,y) coords of a point on this line
*
* @param p
* array for pint coordinates
*/
final public void getInhomPointOnLine(double[] p) {
// point defined by parent algorithm
if (startPoint != null && startPoint.isFinite()) {
p[0] = startPoint.inhomX;
p[1] = startPoint.inhomY;
}
// point on axis
else {
if (Math.abs(x) > Math.abs(y)) {
p[0] = -z / x;
p[1] = 0.0d;
} else {
p[0] = 0.0d;
p[1] = -z / y;
}
}
}
/**
* Sets point p p to coords of some point on this line
*
* @param p
* point to be moved to this path
*/
final public void getPointOnLine(GeoPoint p) {
// point defined by parent algorithm
if (startPoint != null && startPoint.isFinite()) {
p.setCoords(startPoint);
}
// point on axis
else {
if (Math.abs(x) > Math.abs(y)) {
p.setCoords(-z / x, 0.0, 1.0);
} else {
p.setCoords(0.0, -z / y, 1.0);
}
}
}
/**
* Set standard start point (closest to (0,0)). Needed for path parameter to
* work correctly.
*
* @return start point
*/
@Override
public final GeoPointND setStandardStartPoint() {
if (startPoint == null) {
startPoint = new GeoPoint(cons);
startPoint.addIncidence(this, true);
}
// this way the behaviour of pathChanged and pointChanged remain
// the same as if there weren't a startPoint
// so the dependent path parameters (probably) needn't be changed
if (x != 0 || y != 0) {
startPoint.setCoords(-z * x / (x * x + y * y),
-z * y / (x * x + y * y), 1.0);
} else {
// this case probably won't happen, just for completeness
startPoint.setCoords(0.0, 0.0, 1.0);
}
// alternative method
// if (Math.abs(x) > Math.abs(y)) {
// startPoint.setCoords(-z / x, 0.0, 1.0);
// } else {
// startPoint.setCoords(0.0, -z / y, 1.0);
// }
return startPoint;
}
/**
* @param P
* start point
*/
public final void setStartPoint(GeoPoint P) {
if (startPoint == P) {
return;
}
startPoint = P;
if (P != null) {
P.addIncidence(this, true);
}
}
@Override
public final void setStartPoint(GeoPointND P) {
if (P instanceof GeoPoint) {
setStartPoint((GeoPoint) P);
}
if (startPoint == null) {
startPoint = new GeoPoint(cons);
}
startPoint.set(P);
}
/**
* @param Q
* end point
*/
public final void setEndPoint(GeoPoint Q) {
if (endPoint == Q) {
return;
}
endPoint = Q;
if (Q != null) {
Q.addIncidence(this, true);
}
}
/**
* Retuns first defining point of this line or null.
*/
@Override
final public GeoPoint getStartPoint() {
return startPoint;
}
/**
* Retuns second point of this line or null.
*/
@Override
final public GeoPoint getEndPoint() {
return endPoint;
}
@Override
public boolean isDefined() {
return (!(Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z))
&& !(Kernel.isZero(x) && Kernel.isZero(y)));
}
@Override
protected boolean showInEuclidianView() {
// defined
return isDefined();
}
@Override
public boolean showInAlgebraView() {
// independent or defined
// return isIndependent() || isDefined();
return isDefined() || showUndefinedInAlgebraView;
}
/**
* Set whether this line should be visible in AV when undefined
*
* @param flag
* true to show undefined
*/
public void showUndefinedInAlgebraView(boolean flag) {
showUndefinedInAlgebraView = flag;
}
@Override
public void set(GeoElementND geo) {
super.set(geo);
GeoLine l = (GeoLine) geo;
parameter = l.parameter;
toStringMode = l.toStringMode;
reuseDefinition(geo);
}
/**
* Yields true if the coefficients of this line are linear dependent on
* those of line g.
*/
// Michael Borcherds 2008-04-30
@Override
public boolean isEqual(GeoElementND geo) {
if (!geo.isDefined() || !isDefined()) {
return false;
}
// support c==f for Line, Function
if (geo.isGeoFunction()) {
PolyFunction poly = ((GeoFunction) geo).getFunction()
.expandToPolyFunction(
((GeoFunction) geo).getFunctionExpression(), false,
true);
if (poly == null) {
// (probably) not a polynomial
return false;
}
int degree = poly.getDegree();
if (degree > 1) {
// not linear
return false;
}
double[] coeffs = poly.getCoeffs();
if (degree == 0) {
if (Kernel.isEqual(x, 0) && Kernel.isEqual(-z / y, coeffs[0])) {
return true;
}
} else {
// f(x_var) = -x/y x_var - z/y
if (Kernel.isEqual(-x / y, coeffs[1])
&& Kernel.isEqual(-z / y, coeffs[0])) {
return true;
}
}
return false;
}
// return false if it's a different type, otherwise use equals() method
if (geo.isGeoRay() || geo.isGeoSegment()) {
return false;
}
if (geo.isGeoLine()) {
return linDep((GeoLine) geo);
}
return false;
}
/**
* yields true if this line is defined as a tangent of conic c
*
* @param c
* conic
* @return true iff defined as tangent of given conic
*/
final public boolean isDefinedTangent(GeoConic c) {
boolean isTangent = false;
Object ob = getParentAlgorithm();
if (ob instanceof TangentAlgo) {
GeoElement[] input = ((AlgoElement) ob).getInput();
for (int i = 0; i < input.length; i++) {
if (input[i] == c) {
isTangent = true;
break;
}
}
}
return isTangent;
}
/**
* yields true if this line is defined as a asymptote of conic c
*
* @param c
* conic
* @return true iff defined as a asymptote of conic c
*/
final public boolean isDefinedAsymptote(GeoConic c) {
boolean isAsymptote = false;
AlgoElement ob = getParentAlgorithm();
if (ob != null && ob.getClassName().equals(Commands.Asymptote)) {
GeoElement[] input = ob.getInput();
for (int i = 0; i < input.length; i++) {
if (input[i] == c) {
isAsymptote = true;
break;
}
}
}
return isAsymptote;
}
/***********************************************************
* MOVEMENTS
***********************************************************/
/**
* translate by vector v
*/
@Override
public void translate(Coords v) {
z -= x * v.getX() + y * v.getY();
}
@Override
final public boolean isTranslateable() {
return true;
}
/**
* dilate from S by r
*/
@Override
public void dilate(NumberValue rval, Coords S) {
double r = rval.getDouble();
double temp = (r - 1);
z = temp * (x * S.getX() + y * S.getY()) + r * z;
x *= r;
y *= r;
z *= r;
}
/**
* rotate this line by angle phi around (0,0)
*/
@Override
public void rotate(NumberValue phiVal) {
rotateXY(phiVal);
}
/**
* rotate this line by angle phi around Q
*/
@Override
public void rotate(NumberValue phiVal, GeoPointND point) {
Coords Q = point.getInhomCoords();
double phi = phiVal.getDouble();
double cos = Math.cos(phi);
double sin = Math.sin(phi);
double qx = Q.getX();
double qy = Q.getY();
double x0 = x * cos - y * sin;
double y0 = x * sin + y * cos;
z = z + (x * qx + y * qy) * (1.0 - cos) + (y * qx - x * qy) * sin;
x = x0;
y = y0;
}
/**
* mirror this line at point Q
*/
@Override
public void mirror(Coords Q) {
double qx = x * Q.getX();
double qy = y * Q.getY();
z = z + 2.0 * (qx + qy);
x = -x;
y = -y;
}
/**
* mirror this point at line g
*/
@Override
public void mirror(GeoLineND g1) {
GeoLine g = (GeoLine) g1;
// Y = S(phi).(X - Q) + Q
// where Q is a point on g, S(phi) is the mirror transform
// and phi/2 is the line's slope angle
// get arbitrary point of line
double qx, qy;
if (Math.abs(g.getX()) > Math.abs(g.getY())) {
qx = -g.getZ() / g.getX();
qy = 0.0d;
} else {
qx = 0.0d;
qy = -g.getZ() / g.getY();
}
double phi = 2.0 * Math.atan2(-g.getX(), g.getY());
double cos = Math.cos(phi);
double sin = Math.sin(phi);
double x0 = x * cos + y * sin;
double y0 = x * sin - y * cos;
double xqx = x * qx;
double yqy = y * qy;
z += (xqx + yqy) + (yqy - xqx) * cos - (x * qy + y * qx) * sin;
x = x0;
y = y0;
// change orientation
x = -x;
y = -y;
z = -z;
}
/***********************************************************/
/**
* Switch to parametric mode and set parameter name
*
* @param parameter
* name
*/
@Override
final public void setToParametric(String parameter) {
setMode(GeoLine.PARAMETRIC);
if (parameter != null && parameter.length() > 0) {
this.parameter = parameter;
}
}
/** change equation mode to explicit */
@Override
final public void setToExplicit() {
setMode(EQUATION_EXPLICIT);
}
/** set equation mode to implicit */
@Override
final public void setToImplicit() {
setMode(EQUATION_IMPLICIT);
}
@Override
final public void setToUser() {
setMode(EQUATION_USER);
}
@Override
final public void setMode(int mode) {
switch (mode) {
case PARAMETRIC:
toStringMode = PARAMETRIC;
break;
case EQUATION_EXPLICIT:
toStringMode = EQUATION_EXPLICIT;
break;
case EQUATION_IMPLICIT_NON_CANONICAL:
toStringMode = EQUATION_IMPLICIT_NON_CANONICAL;
break;
case EQUATION_GENERAL:
toStringMode = EQUATION_GENERAL;
break;
case EQUATION_USER:
toStringMode = EQUATION_USER;
break;
default:
toStringMode = EQUATION_IMPLICIT;
}
}
/** output depends on mode: PARAMETRIC or EQUATION */
@Override
public String toString(StringTemplate tpl) {
StringBuilder sbToStr = getSbToString();
sbToStr.setLength(0);
sbToStr.append(label);
sbToStr.append(": ");
sbToStr.append(buildValueString(tpl).toString());
return sbToStr.toString();
}
private StringBuilder sbToString;
private StringBuilder getSbToString() {
if (sbToString == null) {
sbToString = new StringBuilder(50);
}
return sbToString;
}
@Override
public String toValueString(StringTemplate tpl) {
return buildValueString(tpl).toString();
}
@Override
public String toStringMinimal(StringTemplate tpl) {
StringBuilder sbToStr = getSbToString();
sbToStr.setLength(0);
getXMLtagsMinimal(sbToStr, tpl);
return sbToStr.toString();
}
private StringBuilder buildValueString(StringTemplate tpl) {
if (tpl.hasCASType()) {
if (getDefinition() != null) {
StringBuilder sb = getSbBuildValueString();
sb.append(getDefinition().toValueString(tpl));
return sb;
}
double[] numbers = new double[3];
numbers[0] = x;
numbers[1] = y;
numbers[2] = z;
double gcd = Kernel.gcd(numbers);
StringBuilder sb = getSbBuildValueString();
sb.append("(");
if (gcd != 1 && !Kernel.isZero(gcd)) {
sb.append(kernel.format(x / gcd, tpl));
} else {
sb.append(kernel.format(x, tpl));
}
sb.append(")*");
sb.append(tpl.printVariableName("x"));
sb.append("+(");
if (gcd != 1 && !Kernel.isZero(gcd)) {
sb.append(kernel.format(y / gcd, tpl));
} else {
sb.append(kernel.format(y, tpl));
}
sb.append(")*");
sb.append(tpl.printVariableName("y"));
sb.append('=');
if (gcd != 1 && !Kernel.isZero(gcd)) {
sb.append(kernel.format(-z / gcd, tpl));
} else {
sb.append(kernel.format(-z, tpl));
}
return sb;
}
if (!kernel.getApplication().getSettings().getCasSettings()
.isEnabled()) {
toStringMode = GeoLine.EQUATION_USER;
}
double[] P = new double[2];
double[] g = new double[3];
char op = '=';
if (!isDefined()) {
return new StringBuilder(
(toStringMode == PARAMETRIC) ? "X = (?, ?)" : "y = ?");
}
switch (toStringMode) {
case EQUATION_EXPLICIT: // /EQUATION
g[0] = x;
g[1] = y;
g[2] = z;
return kernel.buildExplicitEquation(g, vars, op, tpl, true);
case PARAMETRIC:
getInhomPointOnLine(P); // point
StringBuilder sbBuildValueStr = getSbBuildValueString();
GeoCasCell casCell = getCorrespondingCasCell();
if (casCell == null || !casCell.isAssignmentVariableDefined()) {
sbBuildValueStr.append("X = ");
}
sbBuildValueStr.append("(");
sbBuildValueStr.append(kernel.format(P[0], tpl));
sbBuildValueStr.append(", ");
sbBuildValueStr.append(kernel.format(P[1], tpl));
sbBuildValueStr.append(") + ");
sbBuildValueStr.append(parameter);
sbBuildValueStr.append(" (");
sbBuildValueStr.append(kernel.format(y, tpl));
sbBuildValueStr.append(", ");
sbBuildValueStr.append(kernel.format(-x, tpl));
sbBuildValueStr.append(")");
return sbBuildValueStr;
case EQUATION_IMPLICIT_NON_CANONICAL:
case EQUATION_GENERAL:
g[0] = x;
g[1] = y;
g[2] = z;
if (Kernel.isZero(x) || Kernel.isZero(y)) {
return kernel.buildExplicitEquation(g, vars, op, tpl,
EQUATION_IMPLICIT_NON_CANONICAL == toStringMode);
}
return kernel.buildImplicitEquation(g, vars, KEEP_LEADING_SIGN,
false, false, op, tpl,
EQUATION_IMPLICIT_NON_CANONICAL == toStringMode);
case EQUATION_USER:
if (getDefinition() != null) {
return new StringBuilder(getDefinition().toValueString(tpl));
}
return buildImplicitEquation(g, tpl, op);
default: // EQUATION_IMPLICIT
return buildImplicitEquation(g, tpl, op);
}
}
private StringBuilder buildImplicitEquation(double[] g, StringTemplate tpl,
char op) {
g[0] = x;
g[1] = y;
g[2] = z;
if (Kernel.isZero(x) || Kernel.isZero(y)) {
return kernel.buildExplicitEquation(g, vars, op, tpl, true);
}
if (kernel.getAlgebraProcessor().getDisableGcd()) {
return kernel.buildImplicitEquation(g, vars, KEEP_LEADING_SIGN,
false, false, op, tpl, true);
}
return kernel.buildImplicitEquation(g, vars, KEEP_LEADING_SIGN, true,
false, op, tpl, true);
}
private StringBuilder sbBuildValueString = new StringBuilder(50);
private StringBuilder getSbBuildValueString() {
if (sbBuildValueString == null) {
sbBuildValueString = new StringBuilder();
} else {
// needed for GGB-719
sbBuildValueString.setLength(0);
}
return sbBuildValueString;
}
/**
* left hand side as String : ax + by + c
*
* @param sb
* string builder
* @param tpl
* string template
*/
final public void toStringLHS(StringBuilder sb, StringTemplate tpl) {
double[] g = new double[3];
if (isDefined()) {
g[0] = x;
g[1] = y;
g[2] = z;
sb.append(kernel.buildLHS(g, vars, KEEP_LEADING_SIGN, true, false,
tpl));
return;
}
sb.append(sbToStringLHS);
}
private static StringBuilder sbToStringLHS = new StringBuilder("\u221E");
/**
* returns all class-specific xml tags for saveXML GeoGebra File Format
*/
@Override
protected void getXMLtags(StringBuilder sb) {
super.getXMLtags(sb);
// line thickness and type
getLineStyleXML(sb);
// prametric, explicit or implicit mode
switch (toStringMode) {
case GeoLine.PARAMETRIC:
sb.append("\t<eqnStyle style=\"parametric\" parameter=\"");
sb.append(parameter);
sb.append("\"/>\n");
break;
case GeoLine.EQUATION_EXPLICIT:
Equation.appendType(sb, "explicit");
break;
case GeoLine.EQUATION_GENERAL:
Equation.appendType(sb, "general");
break;
case GeoLine.EQUATION_USER:
Equation.appendType(sb, "user");
break;
case GeoLine.EQUATION_IMPLICIT_NON_CANONICAL:
// don't want anything here
break;
default:
Equation.appendType(sb, "implicit");
}
}
/*
* Path interface
*/
@Override
public boolean isClosedPath() {
return false;
}
@Override
public void pointChanged(GeoPointND P) {
doPointChanged(P);
}
private void doPointChanged(GeoPointND P) {
Coords coords = P.getCoordsInD2();
PathParameter pp = P.getPathParameter();
doPointChanged(coords, pp);
P.setCoords2D(coords.getX(), coords.getY(), coords.getZ());
P.updateCoordsFrom2D(false, null);
}
/**
* @param coords
* changed point
* @param pp
* path parameter of P
*/
public void doPointChanged(Coords coords, PathParameter pp) {
// project P on line
double px = coords.getX() / coords.getZ();
double py = coords.getY() / coords.getZ();
// 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;
coords.setX(px);
coords.setY(py);
coords.setZ(1);
// set path parameter
double spx = 0;
double spy = 0;
double spz = 1;
if (startPoint != null) {
spx = startPoint.x;
spy = startPoint.y;
spz = startPoint.z;
} else {
if (x != 0 && y != 0) {
spx = -z * x / (x * x + y * y);
spy = -z * y / (x * x + y * y);
} else if (x != 0) {
spx = -z / x;
} else if (y != 0) {
spy = -z / y;
}
}
if (Math.abs(x) <= Math.abs(y)) {
pp.t = (spz * px - spx) / (y * spz);
} else {
pp.t = (spy - spz * py) / (x * spz);
}
}
@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;
}
Coords coords = P.getCoordsInD2();
PathParameter pp = P.getPathParameter();
pathChanged(coords, pp);
P.setCoords2D(coords.getX(), coords.getY(), coords.getZ());
P.updateCoordsFrom2D(false, null);
}
/**
* This path changed => change P to lie on this path
*
* @param P
* coords of point on path
* @param pp
* path parameter of that point
*/
public void pathChanged(Coords P, PathParameter pp) {
// calc point for given parameter
if (startPoint != null) {
P.setX(startPoint.inhomX + pp.t * y);
P.setY(startPoint.inhomY - pp.t * x);
P.setZ(1.0);
} else {
double inhomX = 0;
double inhomY = 0;
if (x != 0 && y != 0) {
inhomX = -z * x / (x * x + y * y);
inhomY = -z * y / (x * x + y * y);
} else if (x != 0) {
inhomX = -z / x;
} else if (y != 0) {
inhomY = -z / y;
}
P.setX(inhomX + pp.t * y);
P.setY(inhomY - pp.t * x);
P.setZ(1.0);
}
}
@Override
public boolean isPath() {
return true;
}
@Override
public boolean isGeoLine() {
return true;
}
/**
* Returns the smallest possible parameter value for this path (may be
* Double.NEGATIVE_INFINITY)
*
* @return smallest possible parameter value for this path
*/
@Override
public double getMinParameter() {
return Double.NEGATIVE_INFINITY;
}
/**
* Returns the largest possible parameter value for this path (may be
* Double.POSITIVE_INFINITY)
*
* @return largest possible parameter value for this path
*/
@Override
public double getMaxParameter() {
return Double.POSITIVE_INFINITY;
}
@Override
public PathMover createPathMover() {
return new PathMoverLine();
}
private class PathMoverLine extends PathMoverGeneric {
// private GeoPoint moverStartPoint;
public PathMoverLine() {
super(GeoLine.this);
}
@Override
public void init(GeoPointND p, int minSteps) {
// we need a start point for pathChanged() to work correctly
// with our path parameters
if (startPoint == null) {
// moverStartPoint = new GeoPoint(cons);
setStandardStartPoint();
}
// if (moverStartPoint != null) {
// moverStartPoint.setCoords(p);
// point p is on the line and we use it's location
// as the startpoint, thus p needs to get path parameter 0
// PathParameter pp = p.getPathParameter();
// pp.t = 0;
// }
super.init(p, minSteps);
// // we need a point on the line:
// // p is a point on the line ;-)
// moverStartPoint.setCoords(p);
// PathParameter pp = p.getPathParameter();
// pp.t = 0;
// start_param = 0;
//
// min_param = -1 + PathMover.OPEN_BORDER_OFFSET;
// max_param = 1 - PathMover.OPEN_BORDER_OFFSET;
//
// param_extent = max_param - min_param;
// max_step_width = param_extent / MIN_STEPS;
// posOrientation = true;
//
// resetStartParameter();
}
// protected void calcPoint(GeoPoint p) {
// PathParameter pp = p.getPathParameter();
// pp.t = PathMoverGeneric.infFunction(curr_param);
// p.x = moverStartPoint.inhomX + pp.t * y;
// p.y = moverStartPoint.inhomY - pp.t * x;
// p.z = 1.0;
// p.updateCoords();
// }
//
// public boolean hasNext() {
// // check if we pass the start parameter 0:
// // i.e. check if the sign will change from
// // last_param to the next parameter curr_param
// double next_param = curr_param + step_width;
// if (posOrientation)
// return !(curr_param < 0 && next_param >= 0);
// else
// return !(curr_param > 0 && next_param <= 0);
// }
}
/*
* public void add(GeoLine line) { x += line.x; y += line.y; z += line.z; }
*
* public void subtract(GeoLine line) { x -= line.x; y -= line.y; z -=
* line.z; }
*
* public void multiply(GeoLine line) { x *= line.x; y *= line.y; z *=
* line.z; }
*
* public void divide(GeoLine line) { x /= line.x; y /= line.y; z /= line.z;
* }
*/
@Override
public void setZero() {
setCoords(0, 1, 0);
}
/**
* TODO never used ?
*
* @return ":"
*/
@Override
public String getAssignmentOperator() {
return ": ";
}
@Override
public void matrixTransform(double p, double q, double r, double s) {
double x1, y1;
if (Kernel.isZero(y)) {
x1 = s;
y1 = -q;
setCoords(x1 * x, y1 * x, -q * r * z + s * p * z);
} else {
x1 = r * y - s * x;
y1 = q * x - p * y;
setCoords(x1 * y, y1 * y, q * z * x1 + s * z * y1);
}
}
/**
* Creates a GeoFunction of the form f(x) = thisNumber needed for
* SumSquaredErrors[FitLine[]]
*
* @return constant function
*/
@Override
public GeoFunction getGeoFunction() {
GeoFunction ret;
FunctionVariable fv = new FunctionVariable(kernel);
ExpressionNode xCoord = new ExpressionNode(kernel, this,
Operation.XCOORD, null);
ExpressionNode yCoord = new ExpressionNode(kernel, this,
Operation.YCOORD, null);
ExpressionNode zCoord = new ExpressionNode(kernel, this,
Operation.ZCOORD, null);
// f(x_var) = -x/y x_var - z/y
ExpressionNode temp = new ExpressionNode(kernel, xCoord,
Operation.DIVIDE, yCoord);
temp = new ExpressionNode(kernel, new MyDouble(kernel, -1.0),
Operation.MULTIPLY, temp);
temp = new ExpressionNode(kernel, temp, Operation.MULTIPLY, fv);
temp = new ExpressionNode(kernel, temp, Operation.MINUS,
new ExpressionNode(kernel, zCoord, Operation.DIVIDE, yCoord));
// f(x_var) = -x/y x_var - z/y
/*
* ExpressionNode temp = new ExpressionNode(kernel, new MyDouble(kernel,
* -x / y), Operation.MULTIPLY, fv);
*
* temp = new ExpressionNode(kernel, temp, Operation.PLUS, new
* MyDouble(kernel, -z / y) );
*/
Function fun = new Function(temp, fv);
// we get a dependent function if this line has a label or is dependent
if (isLabelSet() || !isIndependent()) {
ret = new AlgoDependentFunction(cons, fun, false).getFunction();
} else {
ret = new GeoFunction(cons);
ret.setFunction(fun);
}
return ret;
}
@Override
public boolean isGeoFunctionable() {
return true;
}
@Override
public boolean isMatrixTransformable() {
return true;
}
/**
* @param con
* conic to store result
*/
public void toGeoConic(GeoConic con) {
con.fromLine(this);
}
@Override
public double value(double x_var) {
if (Kernel.isZero(y)) {
return Double.NaN;
}
return (-x * x_var - z) / y;
}
// ////////////////////////////////////
// 3D stuff
// ////////////////////////////////////
@Override
public boolean hasDrawable3D() {
return true;
}
@Override
public Coords getLabelPosition() {
return getPointInD(3, 0.5).getInhomCoordsInSameDimension();
}
@Override
public Coords getPointInD(int dimension, double lambda) {
return getStartCoordsInD(dimension).copy()
.addInsideMul(getDirectionInD(dimension), lambda);
}
/**
* returns inhom coords in dimension
*
* @param dimension
* @return
*/
private Coords getStartCoordsInD(int dimension) {
Coords startCoords;
// TODO merge with getPointOnLine
// point defined by parent algorithm
if (startPoint != null && startPoint.isFinite()) {
// startCoords=startPoint.getInhomCoordsInD(dimension);
startCoords = startPoint.getCoordsInD(dimension);
}
// point on axis
else {
startCoords = new Coords(dimension + 1);
if (Math.abs(x) > Math.abs(y)) {
startCoords.setX(-z / x);
} else {
startCoords.setY(-z / y);
}
startCoords.set(dimension + 1, 1); // last homogeneous coord
}
return startCoords;
}
private Coords getDirectionInD(int dimension) {
Coords direction = new Coords(dimension + 1);
direction.setX(y);
direction.setY(-x);
return direction;
}
@Override
public Coords getMainDirection() {
return getDirectionInD(3);
}
@Override
public Coords getDirectionForEquation() {
return getDirectionInD(3);
}
@Override
public Coords getCartesianEquationVector(CoordMatrix m) {
if (m == null) {
return new Coords(x, y, z);
}
Coords o = getStartInhomCoords();
Coords d = getEndInhomCoords().sub(o);
return CoordMatrixUtil.lineEquationVector(o, d, m);
}
@Override
public Coords getStartInhomCoords() {
if (startPoint != null && startPoint.isFinite()) {
return startPoint.getInhomCoordsInD3();
}
return getStartCoordsInD(3);
}
@Override
public Coords getEndInhomCoords() {
if (getEndPoint() != null) {
return getEndPoint().getInhomCoordsInD3();
}
return getPointInD(3, 1).getInhomCoordsInSameDimension();
}
@Override
final public boolean isCasEvaluableObject() {
return true;
}
@Override
public Coords getDirectionInD3() {
if (getEndPoint() == null) {
return new Coords(-y, x, 0, 0);
}
return getEndInhomCoords().sub(getStartInhomCoords());
}
@Override
public void matrixTransform(double a00, double a01, double a02, double a10,
double a11, double a12, double a20, double a21, double a22) {
double[][] b = MyMath.adjoint(a00, a01, a02, a10, a11, a12, a20, a21,
a22);
double x1 = b[0][0] * x + b[0][1] * y + b[0][2] * z;
double y1 = b[1][0] * x + b[1][1] * y + b[1][2] * z;
double z1 = b[2][0] * x + b[2][1] * y + b[2][2] * z;
setCoords(x1, y1, z1);
}
// ///////
// / for incidence checking
// ///////
// //////////////////////////////////
// FROM GEOCONIC
// //////////////////////////////////
/** list of points on this line */
protected ArrayList<GeoPoint> pointsOnLine;
/**
* Returns a list of points that this line passes through. May return null.
*
* @return list of points that this line passes through.
*/
public final ArrayList<GeoPoint> getPointsOnLine() {
return pointsOnLine;
}
/**
* Adds a point to the list of points that this line passes through.
*
* @param p
* point tobe added
*/
@Override
public final void addPointOnLine(GeoPointND p) {
if (pointsOnLine == null) {
pointsOnLine = new ArrayList<GeoPoint>();
}
if (!pointsOnLine.contains(p)) {
pointsOnLine.add((GeoPoint) p);
}
}
/**
* Removes a point from the list of points that this line passes through.
*
* @param p
* Point to be removed
*/
@Override
public final void removePointOnLine(GeoPointND p) {
if (pointsOnLine != null) {
pointsOnLine.remove(p);
}
}
@Override
public void doRemove() {
if (getStartPoint() != null) {
getStartPoint().removeIncidence(this);
}
if (getEndPoint() != null) {
getEndPoint().removeIncidence(this);
}
if (pointsOnLine != null) {
for (int i = 0; i < pointsOnLine.size(); ++i) {
GeoPoint p = pointsOnLine.get(i);
p.removeIncidence(this);
}
}
super.doRemove();
}
@Override
public Function getFunction() {
return getGeoFunction().getFunction();
}
@Override
public GeoFunction getGeoDerivative(int order, boolean fast) {
return getGeoFunction().getGeoDerivative(order, fast);
}
@Override
public SymbolicParameters getSymbolicParameters() {
if (algoParent != null
&& (algoParent instanceof SymbolicParametersAlgo)) {
return new SymbolicParameters((SymbolicParametersAlgo) algoParent);
}
return null;
}
@Override
public void getFreeVariables(HashSet<PVariable> variables)
throws NoSymbolicParametersException {
if (algoParent instanceof SymbolicParametersAlgo) {
((SymbolicParametersAlgo) algoParent).getFreeVariables(variables);
return;
}
throw new NoSymbolicParametersException();
}
@Override
public int[] getDegrees() throws NoSymbolicParametersException {
if (algoParent instanceof SymbolicParametersAlgo) {
return ((SymbolicParametersAlgo) algoParent).getDegrees();
}
throw new NoSymbolicParametersException();
}
@Override
public BigInteger[] getExactCoordinates(
final HashMap<PVariable, BigInteger> values)
throws NoSymbolicParametersException {
if (algoParent instanceof SymbolicParametersAlgo) {
return ((SymbolicParametersAlgo) algoParent)
.getExactCoordinates(values);
}
return null;
}
@Override
public PPolynomial[] getPolynomials() throws NoSymbolicParametersException {
if (algoParent instanceof SymbolicParametersAlgo) {
return ((SymbolicParametersAlgo) algoParent).getPolynomials();
}
throw new NoSymbolicParametersException();
}
/**
* Botana's theorem proving subsystem can handle axes and fixed slope lines
* if a locus equation is requested. Otherwise (in strict/general theorem
* proving) they are unsupported (because they are not synthetic).
*
* @return the line has fixed slope (e.g. an axis or defined by an equation)
*/
public boolean hasFixedSlope() {
if (this instanceof GeoAxis) {
return true;
}
if (this.getParentAlgorithm() == null) {
return true;
}
return false;
}
@Override
public PVariable[] getBotanaVars(GeoElementND geo)
throws NoSymbolicParametersException {
if (algoParent instanceof SymbolicParametersBotanaAlgo) {
return ((SymbolicParametersBotanaAlgo) algoParent)
.getBotanaVars(this);
}
if (hasFixedSlope()) {
if (botanaVars == null) {
botanaVars = new PVariable[4];
botanaVars[0] = new PVariable(kernel, true);
botanaVars[1] = new PVariable(kernel, true);
botanaVars[2] = new PVariable(kernel, true);
botanaVars[3] = new PVariable(kernel, true);
// we substitute them in AlgoLocusEquation, not here
}
return botanaVars;
}
return null;
}
@Override
public PPolynomial[] getBotanaPolynomials(GeoElementND geo)
throws NoSymbolicParametersException {
if (algoParent instanceof SymbolicParametersBotanaAlgo) {
return ((SymbolicParametersBotanaAlgo) algoParent)
.getBotanaPolynomials(this);
}
if (hasFixedSlope()) {
// we construct the polynomials in AlgoLocusEquation, not here
return null;
}
throw new NoSymbolicParametersException();
}
@Override
public double distance(GeoLineND g) {
return distance((GeoLine) g);
}
/**
* Whether the parameter is within acceptable range
*
* @param param
* path parameter
*/
@Override
public boolean respectLimitedPath(double param) {
return true;
}
/**
* normalize coeffients so that Intersect[ (x - 1.62010081566832)^2 + (y +
* 31.674457260881873)^2 = 0.028900000000021 , 0.000158120368003x +
* 0.000144840828995y = -0.004331583710062 ] works
*
* @param ret
* output array
*
* @return normalized coefficients x,y,z
*/
public double[] getnormalizedCoefficients(double[] ret) {
ret[0] = x;
ret[1] = y;
ret[2] = z;
if (Kernel.isZero(x) && Kernel.isZero(y) && Kernel.isZero(z)) {
return ret;
}
if (Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z)) {
return ret;
}
if (Double.isInfinite(x) || Double.isInfinite(y)
|| Double.isInfinite(z)) {
return ret;
}
while (Math.abs(ret[0]) < 0.5 && Math.abs(ret[1]) < 0.5
&& Math.abs(ret[2]) < 0.5) {
ret[0] *= 2;
ret[1] *= 2;
ret[2] *= 2;
}
while (Math.abs(ret[0]) > 1 && Math.abs(ret[1]) > 1
&& Math.abs(ret[2]) > 1) {
ret[0] /= 2;
ret[1] /= 2;
ret[2] /= 2;
}
return ret;
}
@Override
public boolean hasLineOpacity() {
return true;
}
@Override
final public HitType getLastHitType() {
return HitType.ON_BOUNDARY;
}
@Override
public boolean isParametric() {
return getMode() == GeoLine.PARAMETRIC;
}
@Override
public ValueType getValueType() {
return getMode() == GeoLine.PARAMETRIC ? ValueType.PARAMETRIC2D
: ValueType.EQUATION;
}
@Override
public ExtendedBoolean isCongruent(GeoElement geo) {
return ExtendedBoolean.newExtendedBoolean(geo.isGeoLine());
}
@Override
public char getLabelDelimiter() {
return ':';
}
@Override
public Coords getOrigin() {
return getStartPoint().getCoordsInD3();
}
@Override
public Equation getEquation() {
FunctionVariable fvx = new FunctionVariable(kernel, "x");
FunctionVariable fvy = new FunctionVariable(kernel, "y");
ExpressionNode lhs = new ExpressionNode(kernel, this.x).multiply(fvx);
lhs = lhs.plus(new ExpressionNode(kernel, this.y).multiply(fvy));
return new Equation(kernel, lhs,
new ExpressionNode(kernel, this.z).multiply(-1));
}
/**
* used by GeoSegment/Ray/3D to set start/end points
*
* @param cons
* cons
* @param my
* my point
* @param other
* point from other geo
* @return what my start/end point should be
*/
public static GeoPointND updatePoint(Construction cons, GeoPointND my,
GeoPointND other) {
if (my == null) {
if (other == null) {
return null;
}
return (GeoPointND) other.copyInternal(cons);
}
if (other != null) {
ExpressionNode oldDef = my.getDefinition();
my.set(other);
if (!my.isIndependent()) {
my.setDefinition(oldDef);
}
}
return my;
}
@Override
public void setToGeneral() {
this.toStringMode = GeoLine.EQUATION_GENERAL;
}
}