package org.geogebra.common.kernel.implicit;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.arithmetic.Equation;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.FunctionNVar;
import org.geogebra.common.kernel.arithmetic.FunctionVariable;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoLine;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.plugin.Operation;
/**
* Algorithm for computation of tangent curve
*
*/
public class AlgoImplicitPolyTangentCurve extends AlgoElement implements
AlgoTangentHelper {
private GeoImplicit poly;
private GeoPointND point;
private GeoImplicit tangentPoly;
private boolean pointOnPath;
/**
* @param c
* construction
* @param poly
* polynomial
* @param point
* point
* @param pointOnPath
* whether point is on path by definition
*/
public AlgoImplicitPolyTangentCurve(Construction c, GeoImplicit poly,
GeoPointND point, boolean pointOnPath) {
super(c, false);
this.poly = poly;
this.point = point;
tangentPoly = (GeoImplicit) poly.copy();
tangentPoly.preventPathCreation();
this.pointOnPath = pointOnPath;
setInputOutput();
compute();
// tangentPoly.setLabel("tgt");
}
@Override
public void compute() {
/*
* calculate tangent curve: dF/dx * x_p + dF/dy * y_p + u_{n-1} +
* 2*u_{n-2} + ... + n*u_0 where u_i are the terms of poly with total
* degree of i.
*/
double x = point.getInhomX();
double y = point.getInhomY();
tangentPoly.setDefined();
if (poly instanceof GeoImplicitCurve
&& poly.getCoeff() == null) {
GeoImplicitCurve inputCurve = ((GeoImplicitCurve) poly);
FunctionNVar f1 = inputCurve.getExpression();
FunctionVariable vx = f1.getFunctionVariables()[0];
FunctionVariable vy = f1.getFunctionVariables()[1];
// build expression Fx*(x-x0)+Fy*(y-y0)
ExpressionNode x1 = new ExpressionNode(kernel, vx, Operation.MINUS,
new MyDouble(kernel, x));
ExpressionNode y1 = new ExpressionNode(kernel, vy, Operation.MINUS,
new MyDouble(kernel, y));
x1 = x1.multiply(inputCurve.getDerivativeX().getExpression());
y1 = y1.multiply(inputCurve.getDerivativeY().getExpression());
tangentPoly.fromEquation(new Equation(kernel, x1.plus(y1),
new MyDouble(kernel, 0)), null);
((GeoImplicitCurve) tangentPoly).updatePath();
return;
}
double[][] coeff = poly.getCoeff();
double[][] newCoeff = new double[coeff.length][];
int maxDeg = poly.getDeg();
for (int i = 0; i < coeff.length; i++) {
newCoeff[i] = new double[coeff[i].length];
for (int j = 0; j < coeff[i].length; j++) {
newCoeff[i][j] = (maxDeg - (i + j)) * coeff[i][j];
if (i + 1 < coeff.length && j < coeff[i + 1].length) {
newCoeff[i][j] += x * (i + 1) * coeff[i + 1][j];
}
if (j + 1 < coeff[i].length) {
newCoeff[i][j] += y * (j + 1) * coeff[i][j + 1];
}
// helper = helper.plus(vx.wrap().power(i)
// .multiply(vy.wrap().power(j)).multiply(newCoeff[i][j]));
}
}
tangentPoly.setCoeff(PolynomialUtils.coeffMinDeg(newCoeff));
tangentPoly.setDefined();
}
@Override
protected void setInputOutput() {
input = new GeoElement[] { poly.toGeoElement(), (GeoElement) point };
setOutputLength(1);
setOutput(0, tangentPoly.toGeoElement());
setDependencies();
}
@Override
public Commands getClassName() {
return Commands.Tangent;
}
/**
* @return resulting tangent curve
*/
@Override
public GeoImplicit getTangentCurve() {
return tangentPoly;
}
@Override
public GeoElement getVec() {
return point.toGeoElement();
}
@Override
public boolean vecDefined() {
return point.isDefined() && Kernel.isZero(point.getInhomZ());
}
@Override
public void getTangents(GeoPoint[] ip, OutputHandler<GeoLine> tangents) {
int n = 0;
if (point != null && poly.isOnPath(point, Kernel.STANDARD_PRECISION)) {
tangents.adjustOutputSize(n + 1);
double dfdx = this.poly.derivativeX(point.getInhomX(),
point.getInhomY());
double dfdy = this.poly.derivativeY(point.getInhomX(),
point.getInhomY());
if (!Kernel.isEqual(dfdx, 0, 1E-5)
|| !Kernel.isEqual(dfdy, 0, 1E-5)) {
tangents.getElement(n).setCoords(dfdx, dfdy,
-dfdx * point.getInhomX() - dfdy * point.getInhomY());
n++;
}
}
if (pointOnPath) {
return;
}
for (int i = 0; i < ip.length; i++) {
if (Kernel.isEqual(ip[i].inhomX, point.getInhomX(), 1E-2)
&& Kernel.isEqual(ip[i].inhomY, point.getInhomY(), 1E-2)) {
continue;
}
// normal vector does not exist, therefore tangent is not defined
// We need to check if F1 :=dF/dx and F2 :=dF/dy are both zero when
// eval at ip[i]
// The error of F1 is dF1/dx * err(x) + dF1/dy * err(y), where
// err(x) and err(y) satisfies
// | (dF/dx) err(x) + (dF/dy) err(y) | < EPSILON
// So |dF/dx|<= |dF1/dx * err(x) + dF1/dy * err(y)| <= Max(dF1/dx /
// dF/dx, dF1/dy / dF/dy) * EPSILON
// A convenient necessary condition of this is (dF/dx)^2 <= |dF1/dx|
// * EPSILON.
// Not very reasonably, now we use (dF/dx)^2 <= EPSILON only, to
// avoid evaluation of dF1/dx
// TODO: have a more reasonable choice; also we use standard
// precision rather than working precision (might not be a problem)
if (Kernel.isEqual(0,
this.poly.derivativeX(ip[i].inhomX, ip[i].inhomY),
Kernel.STANDARD_PRECISION_SQRT)
&& Kernel.isEqual(0,
this.poly.derivativeY(ip[i].inhomX, ip[i].inhomY),
Kernel.STANDARD_PRECISION_SQRT)) {
continue;
}
tangents.adjustOutputSize(n + 1);
tangents.getElement(n).setCoords(
ip[i].getY() - this.point.getInhomY(),
this.point.getInhomX() - ip[i].getX(),
ip[i].getX() * this.point.getInhomY()
- this.point.getInhomX() * ip[i].getY());
ip[i].addIncidence(tangents.getElement(n), false);
n++;
}
}
@Override
public GeoPointND getTangentPoint(GeoElement geo, GeoLine line) {
if (geo == poly && pointOnPath) {
return point;
}
// for (int i = 0; i < this.tangents.size(); i++) {
// if (tangents.getElement(i) == line) {
// return R;
// }
// }
return null;
}
}