/*
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.algos;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.HashSet;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.FixedPathRegionAlgo;
import org.geogebra.common.kernel.Path;
import org.geogebra.common.kernel.PathNormalizer;
import org.geogebra.common.kernel.PathParameter;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.GeoConic;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoLine;
import org.geogebra.common.kernel.geos.GeoNumberValue;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.geos.GeoSegment;
import org.geogebra.common.kernel.kernelND.GeoElementND;
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;
public class AlgoPointOnPath extends AlgoElement
implements FixedPathRegionAlgo, SymbolicParametersAlgo,
SymbolicParametersBotanaAlgo {
private Path path; // input
/** output */
protected GeoPointND P;
private GeoNumberValue param;
private PPolynomial[] polynomials;
private PPolynomial[] botanaPolynomials;
private PVariable variable;
private PVariable[] botanaVars;
public AlgoPointOnPath(Construction cons, Path path,
GeoNumberValue param) {
this(cons, path, 0, 0, 0, param);
}
public AlgoPointOnPath(Construction cons, Path path, double x, double y,
double z, GeoNumberValue param) {
super(cons);
this.path = path;
// create point on path and compute current location
createPoint(path, x, y, z);
this.param = param;
setInputOutput(); // for AlgoElement
compute();
addIncidence();
}
public AlgoPointOnPath(Construction cons, Path path, double x, double y) {
this(cons, path, x, y, 0, true);
}
public AlgoPointOnPath(Construction cons, Path path, double x, double y,
double z, boolean addIncidence) {
super(cons, addIncidence);
this.path = path;
// create point on path and compute current location
createPoint(path, x, y, z);
setInputOutput(); // for AlgoElement
if (addIncidence) {
addIncidence();
} else {
P.setEuclidianVisible(false);
}
}
/**
* @author Tam
*
* for special cases of e.g. AlgoIntersectLineConic
*/
private void addIncidence() {
P.addIncidence((GeoElement) path, false);
}
protected void createPoint(Path path, double x, double y, double z) {
P = new GeoPoint(cons);
P.setPath(path);
P.setCoords(x, y, 1.0);
}
@Override
public Commands getClassName() {
return Commands.Point;
}
@Override
public int getRelatedModeID() {
return EuclidianConstants.MODE_POINT;
}
// for AlgoElement
@Override
protected void setInputOutput() {
if (param == null) {
input = new GeoElement[1];
input[0] = path.toGeoElement();
} else {
input = new GeoElement[2];
input[0] = path.toGeoElement();
input[1] = param.toGeoElement();
}
setOutputLength(1);
setOutput(0, (GeoElement) P);
setDependencies(); // done by AlgoElement
}
public GeoPointND getP() {
return P;
}
public Path getPath() {
return path;
}
@Override
public final void compute() {
if (param != null) {
PathParameter pp = P.getPathParameter();
// Application.debug(param.getDouble()+" "+path.getMinParameter()+"
// "+path.getMaxParameter());
pp.setT(PathNormalizer.toParentPathParameter(param.getDouble(),
path.getMinParameter(), path.getMaxParameter()));
// Application.debug(pp.t);
}
if (input[0].isDefined()) {
path.pathChanged(P);
P.updateCoords();
} else {
P.setUndefined();
}
}
@Override
final public String toString(StringTemplate tpl) {
// Michael Borcherds 2008-03-30
// simplified to allow better Chinese translation
return getLoc().getPlain("PointOnA", input[0].getLabel(tpl));
}
@Override
public boolean isChangeable(GeoElement out) {
return param == null;
}
@Override
public SymbolicParameters getSymbolicParameters() {
return new SymbolicParameters(this);
}
@Override
public void getFreeVariables(HashSet<PVariable> variables)
throws NoSymbolicParametersException {
if (input[0] instanceof GeoSegment) {
throw new NoSymbolicParametersException();
}
if (input[0] instanceof GeoLine) {
((SymbolicParametersAlgo) input[0]).getFreeVariables(variables);
if (variable == null) {
variable = new PVariable((GeoElement) P);
}
variables.add(variable);
return;
}
throw new NoSymbolicParametersException();
}
@Override
public int[] getDegrees() throws NoSymbolicParametersException {
if (input[0] instanceof GeoSegment) {
throw new NoSymbolicParametersException();
}
if (input[0] instanceof GeoLine) {
int[] degreesLine = ((SymbolicParametersAlgo) input[0])
.getDegrees();
int[] result = new int[3];
result[0] = degreesLine[2] + 1;
result[1] = degreesLine[2] + 1;
result[2] = Math.max(degreesLine[0] + 1, degreesLine[1] + 1);
return result;
}
throw new NoSymbolicParametersException();
}
@Override
public BigInteger[] getExactCoordinates(
HashMap<PVariable, BigInteger> values)
throws NoSymbolicParametersException {
if (input[0] instanceof GeoSegment) {
throw new NoSymbolicParametersException();
}
if (input[0] instanceof GeoLine && variable != null) {
BigInteger[] exactCoordinates = new BigInteger[3];
BigInteger[] line = ((SymbolicParametersAlgo) input[0])
.getExactCoordinates(values);
if (line[2].equals(BigInteger.ZERO)) {
/*
* this line is going through the origin, we simply substitute
*/
exactCoordinates[0] = line[1].multiply(values.get(variable));
exactCoordinates[1] = line[0].multiply(values.get(variable));
exactCoordinates[2] = BigInteger.ONE;
} else {
/*
* using Simon's original code otherwise, it doesn't seem to
* handle the previous case properly
*/
exactCoordinates[0] = line[2].multiply(values.get(variable));
exactCoordinates[1] = line[2].multiply(
BigInteger.ONE.subtract(values.get(variable)));
exactCoordinates[2] = line[0]
.multiply(values.get(variable).negate())
.add(line[1].multiply(
values.get(variable).subtract(BigInteger.ONE)));
/* maybe there is a way to unify the two cases, TODO */
}
return exactCoordinates;
}
return null;
}
@Override
public PPolynomial[] getPolynomials() throws NoSymbolicParametersException {
if (polynomials != null) {
return polynomials;
}
if (input[0] instanceof GeoSegment) {
throw new NoSymbolicParametersException();
}
if (path instanceof GeoLine) {
if (variable == null) {
variable = new PVariable((GeoElement) P);
}
polynomials = new PPolynomial[3];
PPolynomial[] line = ((SymbolicParametersAlgo) input[0])
.getPolynomials();
polynomials[0] = line[2].multiply(new PPolynomial(variable));
polynomials[1] = line[2].multiply(
(new PPolynomial(1)).subtract(new PPolynomial(variable)));
polynomials[2] = line[0]
.multiply((new PPolynomial(variable)).negate())
.add(line[1].multiply((new PPolynomial(variable))
.subtract(new PPolynomial(1))));
return polynomials;
}
throw new NoSymbolicParametersException();
}
@Override
public PPolynomial[] getBotanaPolynomials(GeoElementND geo)
throws NoSymbolicParametersException {
if (botanaPolynomials != null) {
return botanaPolynomials;
}
if (input[0] != null && input[0] instanceof GeoLine) {
if (botanaVars == null) {
botanaVars = new PVariable[2];
botanaVars[0] = new PVariable(kernel, true);
botanaVars[1] = new PVariable(kernel);
}
PVariable[] fv = ((SymbolicParametersBotanaAlgo) input[0])
.getBotanaVars(input[0]); // 4 variables
botanaPolynomials = new PPolynomial[1];
botanaPolynomials[0] = PPolynomial.collinear(fv[0], fv[1], fv[2],
fv[3], botanaVars[0], botanaVars[1]);
return botanaPolynomials;
}
if (input[0] != null && input[0] instanceof GeoConic) {
if (((GeoConic) input[0]).isCircle()) {
if (botanaVars == null) {
botanaVars = new PVariable[2];
botanaVars[0] = new PVariable(kernel, true);
botanaVars[1] = new PVariable(kernel);
}
PVariable[] fv = ((SymbolicParametersBotanaAlgo) input[0])
.getBotanaVars(input[0]); // 4 variables
botanaPolynomials = new PPolynomial[1];
// If this new point is D, and ABC is already a triangle with
// the circumcenter O,
// then here we must claim that e.g. AO=OD:
botanaPolynomials[0] = PPolynomial.equidistant(fv[2], fv[3],
fv[0], fv[1], botanaVars[0], botanaVars[1]);
return botanaPolynomials;
}
if (((GeoConic) input[0]).isParabola()) {
if (botanaVars == null) {
botanaVars = new PVariable[4];
// point P on parabola
botanaVars[0] = new PVariable(kernel, true);
botanaVars[1] = new PVariable(kernel);
// T- projection of P on AB
botanaVars[2] = new PVariable(kernel);
botanaVars[3] = new PVariable(kernel);
}
PVariable[] vparabola = ((SymbolicParametersBotanaAlgo) input[0])
.getBotanaVars(input[0]);
botanaPolynomials = new PPolynomial[3];
// FP = PT
botanaPolynomials[0] = PPolynomial.equidistant(vparabola[8],
vparabola[9], botanaVars[0], botanaVars[1],
botanaVars[2], botanaVars[3]);
// A,T,B collinear
botanaPolynomials[1] = PPolynomial.collinear(vparabola[4],
vparabola[5], botanaVars[2], botanaVars[3],
vparabola[6], vparabola[7]);
// PT orthogonal AB
botanaPolynomials[2] = PPolynomial.perpendicular(botanaVars[0],
botanaVars[1], botanaVars[2], botanaVars[3],
vparabola[4], vparabola[5], vparabola[6], vparabola[7]);
return botanaPolynomials;
}
if (((GeoConic) input[0]).isEllipse()
|| ((GeoConic) input[0]).isHyperbola()) {
if (botanaVars == null) {
botanaVars = new PVariable[4];
// P - point on ellipse/hyperbola
botanaVars[0] = new PVariable(kernel, true);
botanaVars[1] = new PVariable(kernel);
// distances between point on ellipse/hyperbola
// and foci points
botanaVars[2] = new PVariable(kernel);
botanaVars[3] = new PVariable(kernel);
}
PVariable[] vellipse = ((SymbolicParametersBotanaAlgo) input[0])
.getBotanaVars(input[0]);
if (input[0]
.getParentAlgorithm() instanceof AlgoConicFivePoints) {
botanaPolynomials = new PPolynomial[2];
botanaPolynomials[0] = new PPolynomial(vellipse[0])
.subtract(new PPolynomial(botanaVars[0]));
botanaPolynomials[1] = new PPolynomial(vellipse[1])
.subtract(new PPolynomial(botanaVars[1]));
return botanaPolynomials;
}
botanaPolynomials = new PPolynomial[3];
PPolynomial e_1 = new PPolynomial(botanaVars[2]);
PPolynomial e_2 = new PPolynomial(botanaVars[3]);
PPolynomial d1 = new PPolynomial(vellipse[2]);
PPolynomial d2 = new PPolynomial(vellipse[3]);
// d1+d2 = e1'+e2'
botanaPolynomials[0] = d1.add(d2).subtract(e_1).subtract(e_2);
// e1'^2=Polynomial.sqrDistance(a1,a2,p1,p2)
botanaPolynomials[1] = PPolynomial.sqrDistance(botanaVars[0],
botanaVars[1], vellipse[6], vellipse[7])
.subtract(e_1.multiply(e_1));
// e2'^2=Polynomial.sqrDistance(b1,b2,p1,p2)
botanaPolynomials[2] = PPolynomial.sqrDistance(botanaVars[0],
botanaVars[1], vellipse[8], vellipse[9])
.subtract(e_2.multiply(e_2));
return botanaPolynomials;
}
}
throw new NoSymbolicParametersException();
}
@Override
public PVariable[] getBotanaVars(GeoElementND geo) {
return botanaVars;
}
public boolean isLocusEquable() {
return true;
}
}