/*
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.
*/
/*
* AlgoDistancePointLine.java
*
* Created on 30. August 2001, 21:37
*/
package org.geogebra.common.kernel.algos;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.EquationSolver;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.Path;
import org.geogebra.common.kernel.StringTemplate;
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.MyDouble;
import org.geogebra.common.kernel.arithmetic.PolyFunction;
import org.geogebra.common.kernel.arithmetic.Traversing;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoFunction;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.util.MyMath;
/**
*
* @author Markus
*/
public class AlgoDistancePointObject extends AlgoElement
implements DistanceAlgo {
private GeoPointND P; // input
private GeoElementND g; // input
private GeoNumeric dist; // output
private AlgoClosestPoint closePt;
public AlgoDistancePointObject(Construction cons, String label,
GeoPointND P, GeoElementND g) {
super(cons);
this.P = P;
this.g = g;
dist = new GeoNumeric(cons);
closePt = getKernel().getAlgoDispatcher().getNewAlgoClosestPoint(cons,
(Path) g, P);
cons.removeFromConstructionList(closePt);
setInputOutput(); // for AlgoElement
// compute length
compute();
dist.setLabel(label);
}
@Override
public Commands getClassName() {
return Commands.Distance;
}
@Override
public int getRelatedModeID() {
return EuclidianConstants.MODE_DISTANCE;
}
// for AlgoElement
@Override
protected void setInputOutput() {
input = new GeoElement[2];
input[0] = (GeoElement) P;
input[1] = g.toGeoElement();
setOutputLength(1);
setOutput(0, dist);
setDependencies(); // done by AlgoElement
}
@Override
public GeoNumeric getDistance() {
return dist;
}
GeoPointND getP() {
return P;
}
GeoElementND getg() {
return g;
}
// calc length of vector v
@Override
public final void compute() {
if (closePt != null) {
dist.setValue(closePt.getP().distance(P));
} else {
dist.setValue(g.distance(P));
}
}
@Override
final public String toString(StringTemplate tpl) {
// Michael Borcherds 2008-03-30
// simplified to allow better Chinese translation
return getLoc().getPlain("DistanceOfAandB", P.getLabel(tpl),
g.getLabel(tpl));
}
private static final double INTERVAL_START = 30;
private static final double INTERVAL_GROWTH = 2;
private static final double MAX_INTERVAL = 10000;
/**
* Other classes are invited to use this method.
*
* @param function
* Function
* @param x
* x-coord of point
* @param y
* y-coord of point
* @return val such as the point (val, function(val)) is closest to point
* (x, y)
*/
public static final double getClosestFunctionValueToPoint(Function function,
double x, double y) {
// Algorithm inspired by
// http://bact.mathcircles.org/files/Winter2011/CM2_Posters/TPham_BACTPoster.pdf
Kernel kernel = function.getKernel();
PolyFunction polyFunction = function
.expandToPolyFunction(function.getExpression(), false, true);
if (polyFunction != null) {
return closestValPoly(polyFunction, x, y, kernel);
}
// non polynomial case
FunctionVariable fVar = function.getFunctionVariable();
Function deriv = function.getDerivative(1, true);
// replace derivatives' function variable with functions'
// we need this, so our new function created below, can be evaluated
deriv.traverse(Traversing.Replacer
.getReplacer(deriv.getFunctionVariable(), fVar));
// build expression 2*(x - a) + 2(f(x) - b)f'(x) where a and b are the
// coordinates of point
ExpressionNode expr = new ExpressionNode(kernel, fVar, Operation.MINUS,
new MyDouble(kernel, x));
expr = expr.multiply(2);
ExpressionNode expr2 = new ExpressionNode(kernel,
function.getExpression(), Operation.MINUS,
new MyDouble(kernel, y));
expr2 = expr2.multiplyR(deriv.getExpression());
expr2 = expr2.multiply(2);
expr = expr.plus(expr2);
// calculate root
Function func = new Function(expr, fVar);
GeoFunction geoFunc = new GeoFunction(kernel.getConstruction(), func);
double[] roots;
double left = INTERVAL_START;
double right = INTERVAL_START;
while ((roots = AlgoRoots.findRoots(geoFunc, x - left, y + right,
(int) ((left + right) * 10))) == null
&& Kernel.isGreater(MAX_INTERVAL, left)) {
left *= INTERVAL_GROWTH;
right *= INTERVAL_GROWTH;
}
if (roots == null || roots.length == 0) {
return Double.NaN;
}
int k = 0;
double min = MyMath.distancePointFunctionAt(function, x, y, roots[0]);
for (int i = 1; i < roots.length; i++) {
double val = MyMath.distancePointFunctionAt(function, x, y,
roots[i]);
if (Kernel.isGreater(min, val)) {
min = val;
k = i;
}
}
return roots[k];
}
public static double closestValPoly(PolyFunction polyFunction, double x,
double y, Kernel kernel) {
PolyFunction polyDervi = polyFunction.getDerivative();
// calculate coeffs for 2*(x - a) + 2(f(x) - b)f'(x) where a and b
// are the coordinates of point
// expanding it gives 2x - 2a + 2*f(x)*f'(x) - 2*b*f'(x)
double[] funCoeffs = polyFunction.getCoeffs();
double[] derivCoeffs = polyDervi.getCoeffs();
int n = funCoeffs.length - 1;
int m = derivCoeffs.length - 1;
double[] eq = new double[(m + n < 1) ? 2 : m + n + 1];
// calculate 2*f(x)*f'(x)
for (int i = 0; i < eq.length; i++) { // c_i
for (int j = Math.max(0, i - m); j <= Math.min(i, n); j++) { // sum
eq[i] += 2 * funCoeffs[j] * derivCoeffs[i - j];
}
}
// add -2*b*f'(x)
for (int i = 0; i <= m; i++) {
eq[i] += (-2) * y * derivCoeffs[i];
}
// add 2x - 2a
eq[1] += 2;
eq[0] -= 2 * x;
// new polynomial coeffs in eq
// calculate the roots and find the minimum
EquationSolver solver = new EquationSolver();
int nrOfRoots = solver.polynomialRoots(eq, false);
if (nrOfRoots == 0) {
return Double.NaN;
}
int k = 0;
double min = MyMath.distancePointFunctionAt(polyFunction, x, y, eq[0]);
for (int i = 1; i < nrOfRoots; i++) {
double val = MyMath.distancePointFunctionAt(polyFunction, x, y,
eq[i]);
if (Kernel.isGreater(min, val)) {
min = val;
k = i;
}
}
return eq[k];
}
}