package org.geogebra.common.kernel.advanced;
import java.util.ArrayList;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.MyPoint;
import org.geogebra.common.kernel.SegmentType;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.algos.AlgoNumeratorDenominatorFun;
import org.geogebra.common.kernel.arithmetic.Evaluate2Var;
import org.geogebra.common.kernel.arithmetic.FunctionalNVar;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoLocus;
import org.geogebra.common.kernel.geos.GeoNumeric;
/**
*
* eg SlopeField[ x/y ] eg SlopeField[ x/y, 20 ] eg SlopeField[ x/y, 20, 0.8 ]
* eg SlopeField[ x/y, 20, 0.8, 0, 0, 5, 5 ]
*
* @author michael
*
*/
public class AlgoSlopeField extends AlgoElement {
private Evaluate2Var func; // input
private GeoNumeric n, lengthRatio, minX, minY, maxX, maxY;
// private GeoList g; // output
private GeoLocus locus; // output
@SuppressWarnings("javadoc")
ArrayList<MyPoint> al;
private AlgoNumeratorDenominatorFun numAlgo;
private AlgoNumeratorDenominatorFun denAlgo;
private FunctionalNVar num, den;
private boolean quotient;
private EuclidianView mainView;
/**
* @param cons
* cons
* @param label
* label
* @param func
* fucntion
* @param n
* length of grid
* @param lengthRatio
* between 0 and 1
* @param minX
* minX
* @param minY
* minY
* @param maxX
* maxX
* @param maxY
* maxY
*/
public AlgoSlopeField(Construction cons, String label, Evaluate2Var func,
GeoNumeric n, GeoNumeric lengthRatio, GeoNumeric minX,
GeoNumeric minY, GeoNumeric maxX, GeoNumeric maxY) {
super(cons);
this.func = func;
this.n = n;
this.lengthRatio = lengthRatio;
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
numAlgo = new AlgoNumeratorDenominatorFun(cons, func,
Commands.Numerator);
denAlgo = new AlgoNumeratorDenominatorFun(cons, func,
Commands.Denominator);
cons.removeFromConstructionList(numAlgo);
cons.removeFromConstructionList(denAlgo);
num = (FunctionalNVar) numAlgo.getGeoElements()[0];
den = (FunctionalNVar) denAlgo.getGeoElements()[0];
quotient = num.isDefined() && den.isDefined();
if (!quotient) {
cons.removeFromAlgorithmList(numAlgo);
cons.removeFromAlgorithmList(denAlgo);
}
// g = new GeoList(cons);
locus = new GeoLocus(cons);
setInputOutput(); // for AlgoElement
compute();
// g.setLabel(label);
locus.setLabel(label);
cons.registerEuclidianViewCE(this);
}
@Override
public Commands getClassName() {
return Commands.SlopeField;
}
// for AlgoElement
@Override
protected void setInputOutput() {
int noOfInputs = 1;
if (n != null) {
noOfInputs++;
}
if (lengthRatio != null) {
noOfInputs++;
}
if (minX != null) {
noOfInputs++;
}
if (minY != null) {
noOfInputs++;
}
if (maxX != null) {
noOfInputs++;
}
if (maxY != null) {
noOfInputs++;
}
input = new GeoElement[noOfInputs];
int i = 0;
input[i++] = (GeoElement) func;
if (n != null) {
input[i++] = n;
}
if (lengthRatio != null) {
input[i++] = lengthRatio;
}
if (minX != null) {
input[i++] = minX;
}
if (minY != null) {
input[i++] = minY;
}
if (maxX != null) {
input[i++] = maxX;
}
if (maxY != null) {
input[i++] = maxY;
}
super.setOutputLength(1);
super.setOutput(0, locus);
setDependencies(); // done by AlgoElement
}
/**
* @return locus
*/
public GeoLocus getResult() {
return locus;
}
@Override
public final void compute() {
if (!((GeoElement) func).isDefined()) {
locus.setUndefined();
return;
}
if (al == null) {
al = new ArrayList<MyPoint>();
} else {
al.clear();
}
mainView = null;
double xmax = -Double.MAX_VALUE;
double ymin = Double.MAX_VALUE;
double xmin = Double.MAX_VALUE;
double ymax = -Double.MAX_VALUE;
if (minX != null) {
xmax = maxX.getDouble();
ymax = maxY.getDouble();
xmin = minX.getDouble();
ymin = minY.getDouble();
mainView = kernel.getApplication().getEuclidianView1();
if (kernel.getApplication().hasEuclidianView2(1)
&& kernel.getApplication().getEuclidianView2(1)
.isVisibleInThisView(locus)
&& !mainView.isVisibleInThisView(locus)) {
mainView = kernel.getApplication().getEuclidianView2(1);
}
} else {
// make sure it covers all of EV1 & EV2 if appropriate
EuclidianView view = kernel.getApplication().getEuclidianView1();
if (view.isVisibleInThisView(locus)) {
mainView = view;
xmax = Math.max(xmax,
view.toRealWorldCoordX((view.getWidth())));
ymax = Math.max(ymax, view.toRealWorldCoordY(0));
xmin = Math.min(xmin, view.toRealWorldCoordX(0));
ymin = Math.min(ymin,
view.toRealWorldCoordY((view.getHeight())));
}
if (kernel.getApplication().hasEuclidianView2(1)) {
EuclidianView view2 = kernel.getApplication()
.getEuclidianView2(1);
if (view2.isVisibleInThisView(locus)) {
if (mainView == null) {
mainView = view2;
}
xmax = Math.max(xmax,
view2.toRealWorldCoordX((view.getWidth())));
ymax = Math.max(ymax, view2.toRealWorldCoordY(0));
xmin = Math.min(xmin, view2.toRealWorldCoordX(0));
ymin = Math.min(ymin,
view2.toRealWorldCoordY((view.getHeight())));
}
}
}
// if it's visible in at least one view, calculate visible portion
if (xmax > -Double.MAX_VALUE) {
int nD = (int) (n == null ? 39 : n.getDouble() - 1);
if (nD < 2 || nD > 100) {
nD = 39;
}
double xStep = (xmax - xmin) / nD;
double yStep = (ymax - ymin) / nD;
double length = (lengthRatio == null ? 0.5
: lengthRatio.getDouble());
if (length < 0 || length > 1 || Double.isInfinite(length)
|| Double.isNaN(length)) {
length = 0.5;
}
length = Math.min(xStep, yStep * mainView.getScaleRatio()) * length
* 0.5;
// double yLength = yStep * length * 0.5;
// AbstractApplication.debug(xStep+" "+yStep+" "+step);
for (double xx = xmin; xx < xmax + xStep / 2; xx += xStep) {
for (double yy = ymin; yy < ymax + yStep / 2; yy += yStep) {
// double[] input1 = { xx, yy };
// double gradient = func.evaluate(input1);
// AbstractApplication.debug(num.isDefined()+"
// "+den.isDefined());
if (num.isDefined() && den.isDefined()) {
// quotient function like x / y
// make sure eg SlopeField[(2 - y) / 2] works
double numD = num.evaluate(xx, yy);
double denD = den.evaluate(xx, yy);
if (Kernel.isZero(denD)) {
if (Kernel.isZero(numD)) {
// just a dot
al.add(new MyPoint(xx, yy, SegmentType.MOVE_TO));
al.add(new MyPoint(xx, yy, SegmentType.LINE_TO));
} else {
// vertical line
drawLine(0, 1, length, xx, yy);
}
} else {
// standard case
double gradient = numD / denD;
drawLine(1, gradient, length, xx, yy);
}
} else {
// non-quotient function like x y
double gradient;
gradient = func.evaluate(xx, yy);
drawLine(1, gradient, length, xx, yy);
}
}
}
}
locus.setPoints(al);
locus.setDefined(true);
}
private void drawLine(double dx0, double dy0, double length, double xx,
double yy) {
/*
* double theta = Math.atan(gradient); double dx = Math.cos(theta);
* double dy = Math.sin(theta);
*/
double dyScaled = dy0 * mainView.getScaleRatio();
double coeff = Math.sqrt(dx0 * dx0 + dyScaled * dyScaled);
double dx = dx0 * length / coeff;
double dy = dy0 * length / coeff;
al.add(new MyPoint(xx - dx, yy - dy, SegmentType.MOVE_TO));
al.add(new MyPoint(xx + dx, yy + dy, SegmentType.LINE_TO));
}
@Override
public void remove() {
if (removed) {
return;
}
super.remove();
((GeoElement) func).removeAlgorithm(numAlgo);
((GeoElement) func).removeAlgorithm(denAlgo);
}
}