package org.geogebra.common.kernel.algos;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.commons.math3.analysis.UnivariateFunction;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.EquationSolverInterface;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.arithmetic.Function;
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.GeoPoint;
import org.geogebra.common.kernel.geos.GeoPoly;
import org.geogebra.common.kernel.geos.GeoSegment;
import org.geogebra.common.kernel.geos.GeoVec3D;
public class AlgoIntersectPolynomialPolyLine extends AlgoIntersect {
protected GeoFunction func;
protected GeoPoly poly;
protected boolean polyClosed;
protected boolean hasLabels;
protected OutputHandler<GeoPoint> outputPoints;
protected int numOfOutputPoints, polyPointCount, segCountOfPoly;
protected ArrayList<Coords> intersectCoords;
private GeoPoint[] tempSegEndPoints;
private GeoSegment tempSeg;
protected EquationSolverInterface eqnSolver;
private final Solution solution = new Solution();
private Function diffFunction;
private GeoPoint tempPoint;
/**
* constructor with labels
*
* @param cons
* @param labels
* @param func
* @param poly
*/
public AlgoIntersectPolynomialPolyLine(Construction cons, String[] labels,
GeoFunction func, GeoPoly poly, boolean polyClosed) {
this(cons, func, poly, polyClosed);
if (!cons.isSuppressLabelsActive()) {
setLabels(labels);
hasLabels = true;
}
update();
}
/**
* Common constructor
*
* @param cons
* @param func
* @param poly
*/
public AlgoIntersectPolynomialPolyLine(Construction cons, GeoFunction func,
GeoPoly poly, boolean polyClosed) {
super(cons);
this.func = func;
this.poly = poly;
this.polyClosed = polyClosed;
initElements();
setInputOutput();
setDependencies();
compute();
}
private void initElements() {
numOfOutputPoints = 0;
polyPointCount = (getPoly().getPoints()).length;
segCountOfPoly = isPolyClosed() ? polyPointCount : polyPointCount - 1;
tempSegEndPoints = new GeoPoint[2];
for (int i = 0; i < tempSegEndPoints.length; i++) {
tempSegEndPoints[i] = new GeoPoint(getConstruction());
}
tempSeg = new GeoSegment(getConstruction());
diffFunction = new Function(kernel);
tempPoint = new GeoPoint(getConstruction());
eqnSolver = cons.getKernel().getEquationSolver();
solution.resetRoots();
outputPoints = this.createOutputPoints();
}
private void setLabels(String[] labels) {
if (labels != null && labels.length == 1 && outputPoints.size() > 1
&& labels[0] != null && !labels[0].equals("")) {
outputPoints.setIndexLabels(labels[0]);
} else {
outputPoints.setLabels(labels);
}
}
// intersection of f and line
private void computePolyLineIntersection(GeoSegment seg,
ArrayList<Coords> intrsctCrds) {
if (func.isDefined() && seg.isDefined()) {
// check for vertical line a*x + c = 0: intersection at x=-c/a
if (Kernel.isZero(seg.y)) {
solution.setSingleRoot(-seg.z / seg.x);
}
// standard case
else {
// get difference f - line
Function.difference(func.getFunction(), seg, diffFunction);
calcRoots(diffFunction, 0);
}
// check if the intersection points really are on the line
// this is important for segments and rays
// following must be done for both vertical and standard
for (int i = 0; i < solution.curRealRoots; i++) {
tempPoint.setCoords(solution.curRoots[i],
func.value(solution.curRoots[i]), 1.0);
if (seg.isOnPath(tempPoint, Kernel.MIN_PRECISION)) {
intrsctCrds.add(tempPoint.getCoords());
numOfOutputPoints++;
}
}
} else {
solution.curRealRoots = 0;
}
}
// add first number of doubles in roots to current roots
/**
* Calculates the roots of the given function resp. its derivative, stores
* them in solution.curRoots and sets solution.curRealRoots to the number of
* real roots found.
*
* @param derivDegree
* degree of derivative to compute roots from
*/
public final void calcRoots(Function fun, int derivDegree) {
UnivariateFunction evalFunction = AlgoRootsPolynomial
.calcRootsMultiple(fun, derivDegree, solution, eqnSolver);
if (solution.curRealRoots > 1) {
// sort roots and eliminate duplicate ones
Arrays.sort(solution.curRoots, 0, solution.curRealRoots);
// eliminate duplicate roots
double maxRoot = solution.curRoots[0];
int maxIndex = 0;
for (int i = 1; i < solution.curRealRoots; i++) {
if ((solution.curRoots[i] - maxRoot) > Kernel.MIN_PRECISION) {
maxRoot = solution.curRoots[i];
maxIndex++;
solution.curRoots[maxIndex] = maxRoot;
}
}
solution.curRealRoots = maxIndex + 1;
}
// for first or second derivative we only
// want roots where the signs changed
// i.e. we only want extrema and inflection points
if (derivDegree > 0) {
solution.ensureSignChanged(evalFunction, DELTA);
}
}
private static final double DELTA = Kernel.MIN_PRECISION * 10;
// remove roots where the sign of the function's values did not change
/**
*
* @return handler for output points
*/
protected OutputHandler<GeoPoint> createOutputPoints() {
return new OutputHandler<GeoPoint>(new elementFactory<GeoPoint>() {
@Override
public GeoPoint newElement() {
GeoPoint p = new GeoPoint(cons);
p.setCoords(0, 0, 1);
p.setParentAlgorithm(AlgoIntersectPolynomialPolyLine.this);
return p;
}
});
}
@Override
public GeoPoint[] getIntersectionPoints() {
GeoPoint[] iPoint = new GeoPoint[2];
return outputPoints.getOutput(iPoint);
}
@Override
protected GeoPoint[] getLastDefinedIntersectionPoints() {
// TODO Auto-generated method stub
return null;
}
@Override
protected void setInputOutput() {
input = new GeoElement[2];
input[0] = this.func;
input[1] = (GeoElement) this.poly;
}
@Override
public void compute() {
numOfOutputPoints = 0;
intersectCoords = new ArrayList<Coords>();
// calculate intersectpaths between poly and conic
for (int index = 0; index < segCountOfPoly; index++) {
solution.resetRoots();
tempSegEndPoints[0] = getPoly().getPoint(index);
tempSegEndPoints[1] = getPoly()
.getPoint((index + 1) % polyPointCount);
GeoVec3D.lineThroughPoints(tempSegEndPoints[0], tempSegEndPoints[1],
tempSeg);
tempSeg.setPoints(tempSegEndPoints[0], tempSegEndPoints[1]);
tempSeg.calcLength();
computePolyLineIntersection(tempSeg, intersectCoords);
}
if (numOfOutputPoints > 0) {
outputPoints.adjustOutputSize(numOfOutputPoints, false);
for (int i = 0; i < numOfOutputPoints; i++) {
outputPoints.getElement(i).setCoords(intersectCoords.get(i),
true);
}
} else {
outputPoints.adjustOutputSize(1, false);
outputPoints.getElement(0).setUndefined();
}
if (hasLabels) {
outputPoints.updateLabels();
}
}
/**
* getter of input poly
*
* @return GeoPoly
*/
public GeoPoly getPoly() {
return this.poly;
}
/**
* @return whether the poly is closed or not. true means a polyline, false
* means a boundary polygon
*/
public boolean isPolyClosed() {
return polyClosed;
}
@Override
public GetCommand getClassName() {
return Commands.Intersect;
}
@Override
public int getRelatedModeID() {
return EuclidianConstants.MODE_INTERSECT;
}
}