/*
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.geos;
import java.util.TreeMap;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.MatrixTransformable;
import org.geogebra.common.kernel.Region;
import org.geogebra.common.kernel.RegionParameters;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.Matrix.Coords3;
import org.geogebra.common.kernel.Matrix.CoordsDouble3;
import org.geogebra.common.kernel.algos.AlgoMacroInterface;
import org.geogebra.common.kernel.arithmetic.Equation;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.ExpressionValue;
import org.geogebra.common.kernel.arithmetic.FunctionExpander;
import org.geogebra.common.kernel.arithmetic.FunctionNVar;
import org.geogebra.common.kernel.arithmetic.FunctionVariable;
import org.geogebra.common.kernel.arithmetic.FunctionalNVar;
import org.geogebra.common.kernel.arithmetic.IneqTree;
import org.geogebra.common.kernel.arithmetic.Inequality;
import org.geogebra.common.kernel.arithmetic.Inequality.IneqType;
import org.geogebra.common.kernel.arithmetic.MyArbitraryConstant;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.kernel.arithmetic.MyList;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.arithmetic.ValidExpression;
import org.geogebra.common.kernel.arithmetic.ValueType;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.kernelND.GeoLineND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.kernel.kernelND.SurfaceEvaluable;
import org.geogebra.common.main.MyError;
import org.geogebra.common.plugin.GeoClass;
import org.geogebra.common.util.StringUtil;
/**
* Explicit function in multiple variables, e.g. f(a, b, c) := a^2 + b - 3c.
* This is actually a wrapper class for FunctionNVar in
* geogebra.kernel.arithmetic. In arithmetic trees (ExpressionNode) it evaluates
* to a FunctionNVar.
*
* @author Markus Hohenwarter
*/
public class GeoFunctionNVar extends GeoElement
implements FunctionalNVar, CasEvaluableFunction, Region, Transformable,
Translateable, MatrixTransformable, Dilateable, PointRotateable,
Mirrorable, SurfaceEvaluable {
private static final double STRICT_INEQ_OFFSET = 4 * Kernel.MIN_PRECISION;
private static final int SEARCH_SAMPLES = 70;
private FunctionNVar fun;
/** derivative functions */
private FunctionNVar[] fun1;
// private List<Inequality> ineqs;
private Boolean isInequality;
private boolean isDefined = true;
/** intervals for plotting, may be null (then interval is R) */
private double[] from, to;
/**
* Creates new GeoFunction
*
* @param c
* construction
*/
public GeoFunctionNVar(Construction c) {
super(c);
// moved from GeoElement's constructor
// must be called from the subclass, see
// http://benpryor.com/blog/2008/01/02/dont-call-subclass-methods-from-a-superclass-constructor/
setConstructionDefaults(); // init visual settings
}
/**
* @param c
* construction
* @param defaults
* true to set defaults right away
*/
public GeoFunctionNVar(Construction c, boolean defaults) {
super(c);
if (defaults) {
setConstructionDefaults();
}
}
/**
* Creates new GeoFunction from Function
*
* @param c
* construction
* @param f
* function to be wrapped
* @param simplifyInt
* whether integers should be simplified eg 2*2 replaced by 4
*/
public GeoFunctionNVar(Construction c, FunctionNVar f,
boolean simplifyInt) {
this(c, false);
setFunction(f);
fun.initFunction(simplifyInt);
isInequality = fun.initIneqs(this.getFunctionExpression(), this);
setConstructionDefaults();
}
/**
* Creates labeled GeoFunction from Function
*
* @param c
* construction
* @param f
* function to be wrapped
*/
public GeoFunctionNVar(Construction c, FunctionNVar f) {
this(c, f, true);
}
/**
* @param autoLabel
* whether label was set by
* @return whether function contains only valid variables
*/
public boolean validate(boolean autoLabel) {
if (!cons.isFileLoading()) {
if (getFunctionExpression().containsFreeFunctionVariableOtherThan(
getFunctionVariables())) {
return false;
}
}
return true;
}
@Override
public String getTypeString() {
return (isInequality != null && isInequality) ? "Inequality"
: "MultivariableFunction";
}
@Override
public GeoClass getGeoClassType() {
return GeoClass.FUNCTION_NVAR;
}
/**
* copy constructor
*
* @param f
* source function
*/
public GeoFunctionNVar(GeoFunctionNVar f) {
this(f.cons);
set(f);
}
@Override
public GeoElement copy() {
return new GeoFunctionNVar(this);
}
@Override
public void set(GeoElementND geo) {
// reset derivatives
fun1 = null;
if (geo instanceof GeoNumeric) {
fun.setExpression(geo.wrap());
return;
}
FunctionalNVar geoFun = (FunctionalNVar) geo;
if (geo == null || geoFun.getFunction() == null) {
fun = null;
isDefined = false;
return;
}
isDefined = geo.isDefined();
FunctionVariable[] oldVars = fun == null ? null
: fun.getFunctionVariables();
fun = new FunctionNVar(geoFun.getFunction(), kernel);
fun.fillVariables(oldVars);
// macro OUTPUT
if (geo.getConstruction() != cons && isAlgoMacroOutput()) {
// this object is an output object of AlgoMacro
// we need to check the references to all geos in its function's
// expression
if (!geo.isIndependent()) {
AlgoMacroInterface algoMacro = (AlgoMacroInterface) getParentAlgorithm();
algoMacro.initFunction(this.fun);
}
}
isInequality = fun.initIneqs(this.getFunctionExpression(), this);
}
/**
* @param f
* new function
*/
public void setFunction(FunctionNVar f) {
fun = f;
// reset derivatives
fun1 = null;
}
private static FunctionExpander functionExpander;
@Override
public void setDerivatives() {
// check if derivatives already exist
if (fun1 != null) {
return;
}
// set derivatives
FunctionVariable[] vars = fun.getFunctionVariables();
fun1 = new FunctionNVar[vars.length];
if (functionExpander == null) {
functionExpander = new FunctionExpander();
}
ValidExpression ve = fun.deepCopy(getKernel());
ve = (ValidExpression) ve.traverse(functionExpander);
for (int i = 0; i < vars.length; i++) {
fun1[i] = new FunctionNVar(ve.derivative(vars[i], kernel).wrap(),
vars);
}
}
@Override
public void resetDerivatives() {
fun1 = null;
}
@Override
final public FunctionNVar getFunction() {
return fun;
}
/**
* @return expression of the wrapped function
*/
@Override
final public ExpressionNode getFunctionExpression() {
if (fun == null) {
return null;
}
return fun.getExpression();
}
/**
* Replaces geo and all its dependent geos in this function's expression by
* copies of their values.
*
* @param geo
* geo to be replaced
*/
@Override
public void replaceChildrenByValues(GeoElement geo) {
if (fun != null) {
fun.replaceChildrenByValues(geo);
}
}
/**
* Returns this function's value at position.
*
* @param vals
* variable values
* @return f(vals)
*/
@Override
public double evaluate(double[] vals) {
// Application.printStacktrace("");
if (fun == null || !isDefined) {
return Double.NaN;
}
return fun.evaluate(vals);
}
/**
* @param vals
* variable values
* @return value at vals
*/
public Coords evaluatePoint(double[] vals) {
// Application.printStacktrace("");
if (fun == null) {
return null;
}
return new Coords(vals[0], vals[1], fun.evaluate(vals));
}
/**
* @param x
* x
* @param y
* y
* @return value at (x,y)
*/
@Override
public double evaluate(double x, double y) {
// Application.printStacktrace("");
if (fun == null) {
return Double.NaN;
}
return fun.evaluate(x, y);
}
/**
* #4076 make sure if CAS returns "?" function is undefined so eg
* Integral[f,a,b] uses numerical method
*/
private void checkDefined() {
isDefined = fun != null;
if (fun != null && "?"
.equals(fun.toValueString(StringTemplate.defaultTemplate))) {
isDefined = false;
}
}
/**
* Sets this function by applying a GeoGebraCAS command to a function.
*
* @param ggbCasCmd
* the GeoGebraCAS command needs to include % in all places where
* the function f should be substituted, e.g. "Derivative(%,x)"
* @param f
* the function that the CAS command is applied to
*/
@Override
public void setUsingCasCommand(String ggbCasCmd, CasEvaluableFunction f,
boolean symbolic, MyArbitraryConstant arbconst) {
// reset derivatives
fun1 = null;
GeoFunctionNVar ff = (GeoFunctionNVar) f;
if (ff.isDefined()) {
fun = ff.fun.evalCasCommand(ggbCasCmd, symbolic, arbconst);
checkDefined();
} else {
isDefined = false;
}
}
@Override
public boolean isDefined() {
return isDefined && fun != null;
}
/**
* @param defined
* true to make this defined
*/
@Override
public void setDefined(boolean defined) {
isDefined = defined;
}
@Override
public void setUndefined() {
isDefined = false;
}
@Override
public boolean showInAlgebraView() {
return true;
}
@Override
protected boolean showInEuclidianView() {
if (fun != null && isInequality == null && isBooleanFunction()) {
getIneqs();
}
return isDefined() && (!isBooleanFunction() || isInequality);
}
/**
* @return function description as f(x)=...
*/
private String toXMLString(StringTemplate tpl) {
sbToString.setLength(0);
sbToString.append(label);
sbToString.append("(");
sbToString.append(getVarString(tpl));
sbToString.append(") = ");
sbToString.append(toValueString(tpl));
return sbToString.toString();
}
@Override
public String getAssignmentLHS(StringTemplate tpl) {
sbToString.setLength(0);
sbToString.append(tpl.printVariableName(label));
sbToString.append("(");
sbToString.append(getVarString(tpl));
sbToString.append(")");
return sbToString.toString();
}
/**
* @return function description as f(x,y)=... for real and e.g. f:x>4*y for
* bool
*/
@Override
public String toString(StringTemplate tpl) {
if (isLabelSet() && !isBooleanFunction()) {
return toXMLString(tpl);
}
sbToString.setLength(0);
if (isLabelSet()) {
sbToString.append(label);
sbToString.append(": ");
}
sbToString.append(toValueString(tpl));
return sbToString.toString();
}
private StringBuilder sbToString = new StringBuilder(80);
@Override
public String toValueString(StringTemplate tpl) {
if (isDefined()) {
return fun.toValueString(tpl);
}
return "?";
}
@Override
public String toSymbolicString(StringTemplate tpl) {
if (isDefined()) {
return fun.toString(tpl);
}
return "?";
}
@Override
public String toLaTeXString(boolean symbolic, StringTemplate tpl) {
if (isDefined()) {
return fun.toLaTeXString(symbolic, tpl);
}
return "?";
}
@Override
public char getLabelDelimiter() {
return isBooleanFunction() ? ':' : '=';
}
/**
* save object in xml format
*/
@Override
public final void getXML(boolean getListenersToo, StringBuilder sb) {
// an indpendent function needs to add
// its expression itself
// e.g. f(a,b) = a^2 - 3*b
if (isIndependent() && getDefaultGeoType() < 0) {
sb.append("<expression");
sb.append(" label =\"");
sb.append(label);
sb.append("\" exp=\"");
StringUtil.encodeXML(sb, toXMLString(StringTemplate.xmlTemplate));
// expression
sb.append("\"/>\n");
}
sb.append("<element");
sb.append(" type=\"functionNVar\"");
sb.append(" label=\"");
sb.append(label);
if (getDefaultGeoType() >= 0) {
sb.append("\" default=\"");
sb.append(getDefaultGeoType());
}
sb.append("\">\n");
getXMLtags(sb);
getCaptionXML(sb);
if (getListenersToo) {
getListenerTagsXML(sb);
}
// sb.append(sb);
sb.append("</element>\n");
}
@Override
final public boolean isCasEvaluableObject() {
return true;
}
@Override
public boolean isNumberValue() {
return false;
}
@Override
public boolean isBooleanFunction() {
if (fun != null) {
return fun.isBooleanFunction();
}
return false;
}
@Override
public String getVarString(StringTemplate tpl) {
return fun == null ? "" : fun.getVarString(tpl);
}
private Equation equalityChecker;
@Override
public boolean isEqual(GeoElementND geo) {
if (!(geo instanceof GeoFunctionNVar)) {
return false;
}
// try for polynomials first
// (avoid loading the CAS if at all possible)
if (equalityChecker == null) {
equalityChecker = new Equation(kernel, getFunctionExpression(),
((GeoFunctionNVar) geo).getFunctionExpression());
} else {
equalityChecker.setLHS(getFunctionExpression());
equalityChecker
.setRHS(((GeoFunctionNVar) geo).getFunctionExpression());
}
try {
// not polynomial (or some other problem) -> check in CAS
equalityChecker.initEquation();
} catch (MyError e) {
return isDifferenceZeroInCAS(geo);
}
if (!equalityChecker.isPolynomial()) {
// not polynomial -> check in CAS
return isDifferenceZeroInCAS(geo);
}
ExpressionValue[][] coeffs = equalityChecker.getNormalForm().getCoeff();
for (int i = 0; i < coeffs.length; i++) {
for (int j = 0; j < coeffs[i].length; j++) {
ExpressionValue coeff = coeffs[i][j];
// null -> no term
if (coeff != null) {
double coeffVal = coeff.evaluateDouble();
// Log.debug("coeff is for " + i + " " + j + " is " +
// coeffVal);
if (!Kernel.isZero(coeffVal)) {
// one coefficient different -> definitely not equal
// polynomials
return false;
}
}
}
}
// poly && all coefficients zero
return true;
}
/**
* Returns a representation of geo in currently used CAS syntax. For
* example, "a*x^2 + b*y"
*/
@Override
public String getCASString(StringTemplate tpl, boolean symbolic) {
return fun.getExpression().getCASstring(tpl, symbolic);
}
/*
* public String getLabelForAssignment() { StringBuilder sb = new
* StringBuilder(); sb.append(getLabel()); sb.append("(" );
* sb.append(fun.getVarString(kernel.getStringTemplate())); sb.append(")");
* return sb.toString(); }
*/
// ///////////////////////////////////////
// INTERVALS
// ///////////////////////////////////////
/**
* return Double.NaN if none has been set
*
* @param index
* of parameter
* @return min parameter
*/
@Override
public double getMinParameter(int index) {
if (from == null) {
return Double.NaN;
}
return from[index];
}
/**
* return Double.NaN if none has been set
*
* @param index
* of parameter
* @return max parameter
*/
@Override
public double getMaxParameter(int index) {
if (to == null) {
return Double.NaN;
}
return to[index];
}
/**
* Sets the start and end parameters values of this function.
*
* @param from
* start param
* @param to
* end param
*/
public void setInterval(double[] from, double[] to) {
this.from = from;
this.to = to;
}
// ///////////////////////////////////////
// For 3D
// ///////////////////////////////////////
private double[] tmp = new double[2];
private double evaluateForDrawSurface() {
if (isBooleanFunction()) {
if (fun.evaluateBoolean(tmp)) {
return 0;
}
return Double.NaN;
}
if (from != null && to != null) {
if (tmp[0] < this.from[0] || tmp[0] > this.to[0]) {
return Double.NaN;
}
if (tmp[1] < this.from[1] || tmp[1] > this.to[1]) {
return Double.NaN;
}
}
return fun.evaluate(tmp);
}
@Override
public void evaluatePoint(double u, double v, Coords3 p) {
tmp[0] = u;
tmp[1] = v;
p.set(u, v, evaluateForDrawSurface());
}
/**
*
* @return number of vars
*/
public int getVarNumber() {
return fun == null ? 0 : fun.getVarNumber();
}
// will be drawn as a surface if can be interpreted as (x,y)->z function
// or implicit f(x,y,z)=0 function
@Override
public boolean hasDrawable3D() {
return getVarNumber() == 2 || getVarNumber() == 3;
}
@Override
public Coords getLabelPosition() {
return Coords.O; // TODO
}
/** to be able to fill it with an alpha value */
@Override
public boolean isFillable() {
if (fun == null) {
return true;
}
return hasDrawable3D();
}
@Override
public boolean hasFillType() {
return isInequality();
}
@Override
public boolean isInverseFillable() {
return isInequality();
}
/**
* Reset all inequalities (slow, involves parser)
*/
public void resetIneqs() {
isInequality = fun.initIneqs(getFunctionExpression(), this);
}
/**
* @return the ineqs
*/
@Override
public IneqTree getIneqs() {
if (fun.getIneqs() == null) {
isInequality = fun.initIneqs(fun.getExpression(), this);
}
return fun.getIneqs();
}
@Override
public void update(boolean drag) {
if (fun != null && fun.isBooleanFunction()) {
isInequality = fun.updateIneqs();
}
super.update(drag);
}
@Override
public boolean isRegion() {
return isBooleanFunction() || isRegion3D();
}
@Override
public boolean isInRegion(GeoPointND P) {
if (isBooleanFunction()) {
P.updateCoords2D();
return isInRegion(P.getX2D(), P.getY2D());
}
// 2 var function
Coords coords = P.getInhomCoordsInD3();
tmp[0] = coords.getX();
tmp[1] = coords.getY();
double z = fun.evaluate(tmp);
return Kernel.isEqual(coords.getZ(), z);
}
@Override
public boolean isInRegion(double x0, double y0) {
return fun.evaluateBoolean(new double[] { x0, y0 });
}
private GeoPoint helper;
@Override
public void pointChangedForRegion(GeoPointND P) {
if (isBooleanFunction()) {
if (!((GeoElement) P).isDefined()) {
return;
}
RegionParameters rp = P.getRegionParameters();
if (!isInRegion(P)) {
double bestX = rp.getT1(), bestY = rp.getT2(), myX = P.getX2D(),
myY = P.getY2D();
double bestDist = (bestY - myY) * (bestY - myY)
+ (bestX - myX) * (bestX - myX);
if (Kernel.isZero(bestDist)) { // not the best distance, since P
// is
// not in region
bestDist = Double.POSITIVE_INFINITY;
}
IneqTree ineqs = getIneqs();
int size = ineqs.getSize();
for (int i = 0; i < size; i++) {
Inequality in = ineqs.get(i);
double px = 0, py = 0;
if (in.getType() == IneqType.INEQUALITY_PARAMETRIC_Y) {
px = P.getX2D();
py = in.getFunBorder().value(px);
py += in.isAboveBorder() ? STRICT_INEQ_OFFSET
: -STRICT_INEQ_OFFSET;
} else if (in
.getType() == IneqType.INEQUALITY_PARAMETRIC_X) {
py = P.getY2D();
px = in.getFunBorder().value(py);
px += in.isAboveBorder() ? STRICT_INEQ_OFFSET
: -STRICT_INEQ_OFFSET;
} else if (in.getType() == IneqType.INEQUALITY_LINEAR) {
double a = in.getLineBorder().getX();
double b = in.getLineBorder().getY();
double c = in.getLineBorder().getZ();
px = (-a * c + b * b * P.getX2D() - a * b * P.getY2D())
/ (a * a + b * b);
py = (-b * c - a * b * P.getX2D() + a * a * P.getY2D())
/ (a * a + b * b);
py -= in.isAboveBorder() ? STRICT_INEQ_OFFSET
: -STRICT_INEQ_OFFSET;
} else if (in.getType() == IneqType.INEQUALITY_CONIC) {
if (helper == null) {
helper = new GeoPoint(cons);
}
helper.setCoordsFromPoint(P);
helper.setPath(in.getConicBorder());
in.getConicBorder().pointChanged(helper);
px = helper.getX() / helper.getZ();
py = helper.getY() / helper.getZ();
}
double myDist = (py - myY) * (py - myY)
+ (px - myX) * (px - myX);
if ((myDist < bestDist) && isInRegion(px, py)) {
bestDist = myDist;
bestX = px;
bestY = py;
}
}
if (isInRegion(bestX, bestY)) {
rp.setT1(bestX);
rp.setT2(bestY);
P.setCoords(new Coords(bestX, bestY, 0, 1), false);
} else {
tryLocateInEV(P);
}
} else {
rp.setT1(P.getX2D());
rp.setT2(P.getY2D());
}
} else {
// 2 var function
Coords coords = P.getInhomCoordsInD3();
if (hasLastHitParameters()) {
int step = 0;
double zScale = Math.sqrt(P.getZScale());
do {
stepDicho();
step++;
} while (step < DICHO_MAX_STEP
&& isTooFar(xyzf[DICHO_MID], zScale));
coords.setX(xyzf[DICHO_MID][0]);
coords.setY(xyzf[DICHO_MID][1]);
coords.setZ(xyzf[DICHO_MID][3]);
} else {
tmp[0] = coords.getX();
tmp[1] = coords.getY();
double z = fun.evaluate(tmp);
coords.setZ(z);
}
RegionParameters rp = P.getRegionParameters();
rp.setT1(coords.getX());
rp.setT2(coords.getY());
Coords n = new Coords(4);
evaluateNormal(coords.getX(), coords.getY(), n);
rp.setNormal(n);
P.setCoords(coords, false);
P.updateCoords();
resetLastHitParameters();
}
}
@Override
public boolean isRegion3D() {
return getVarNumber() == 2 && !isBooleanFunction();
}
private boolean hasLastHitParameters = false;
private double[][] xyzf;
/**
*
* @return xyzf arrays for dichotomy
*/
public double[][] getXYZF() {
if (xyzf == null) {
xyzf = new double[3][];
xyzf[DICHO_FIRST] = new double[4];
xyzf[DICHO_LAST] = new double[4];
xyzf[DICHO_MID] = new double[4];
}
return xyzf;
}
/**
* reset last hitted parameters
*/
public void resetLastHitParameters() {
hasLastHitParameters = false;
}
/**
* set last hitted parameters
*
* @param swap
* says if we have to swap first/last
*
*/
public void setLastHitParameters(boolean swap) {
if (swap) {
double[] xyzfTmp = xyzf[DICHO_FIRST];
xyzf[DICHO_FIRST] = xyzf[DICHO_LAST];
xyzf[DICHO_LAST] = xyzfTmp;
}
hasLastHitParameters = true;
}
private boolean hasLastHitParameters() {
return hasLastHitParameters;
}
/** helper for pointin region dichotomy */
public static final int DICHO_FIRST = 0;
/** helper for pointin region dichotomy */
public static final int DICHO_LAST = 1;
/** helper for pointin region dichotomy */
public static final int DICHO_MID = 2;
static private int DICHO_MAX_STEP = 20;
final private static boolean isTooFar(double[] xyzf, double zScale) {
return !Kernel.isEqual(xyzf[2], xyzf[3],
Kernel.STANDARD_PRECISION_SQRT / zScale);
}
/**
*
* @param xyzf
* x, y, z, f(x,y) values
* @return true if z < f
*/
public static boolean isLessZ(double[] xyzf) {
return xyzf[2] < xyzf[3];
}
/**
* set x, y, z, f(x,y) values to xyzf
*
* @param x
* x coord
* @param y
* y coord
* @param z
* z coord
* @param xyzf
* set values
*/
public void setXYZ(double x, double y, double z, double[] xyzf) {
xyzf[0] = x;
xyzf[1] = y;
xyzf[2] = z;
xyzf[3] = evaluate(xyzf);
}
/**
* do a dichotomy step
*/
public void stepDicho() {
setXYZ((xyzf[DICHO_FIRST][0] + xyzf[DICHO_LAST][0]) / 2,
(xyzf[DICHO_FIRST][1] + xyzf[DICHO_LAST][1]) / 2,
(xyzf[DICHO_FIRST][2] + xyzf[DICHO_LAST][2]) / 2,
xyzf[DICHO_MID]);
// Log.debug("\n" + (xyzf[DICHO_FIRST][3] - xyzf[DICHO_FIRST][2]) + "/"
// + (xyzf[DICHO_LAST][3] - xyzf[DICHO_LAST][2]) + " >> "
// + (xyzf[DICHO_MID][3] - xyzf[DICHO_MID][2]));
if (isLessZ(xyzf[DICHO_MID])) {
double[] swap = xyzf[DICHO_FIRST];
xyzf[DICHO_FIRST] = xyzf[DICHO_MID];
xyzf[DICHO_MID] = swap;
} else {
double[] swap = xyzf[DICHO_LAST];
xyzf[DICHO_LAST] = xyzf[DICHO_MID];
xyzf[DICHO_MID] = swap;
}
}
/**
* We seek for a point in region by desperately testing grid points in
* euclidian view. This should be called only when every algorithm fails.
*
* @param P
*/
private void tryLocateInEV(GeoPointND P) {
// EuclidianViewInterfaceSlim ev =
// kernel.getApplication().getEuclidianView();
boolean found = false;
double xmin = kernel.getViewsXMin(P);
double xmax = kernel.getViewsXMax(P);
double ymin = kernel.getViewsYMin(P);
double ymax = kernel.getViewsYMax(P);
for (int i = 0; !found && i < SEARCH_SAMPLES; i++) {
for (int j = 0; !found && j < SEARCH_SAMPLES; j++) {
double p = i / (double) SEARCH_SAMPLES;
double rx = p * xmin + (1 - p) * xmax;
double q = i / (double) SEARCH_SAMPLES;
double ry = q * ymin + (1 - q) * ymax;
if (isInRegion(rx, ry)) {
P.setCoords(new Coords(rx, ry, 0, 1), false);
// Application.debug("Desperately found"+rx+","+ry);
found = true;
}
}
}
if (!found) {
P.setUndefined();
}
}
@Override
public void regionChanged(GeoPointND P) {
pointChangedForRegion(P);
}
/**
* @return true if this function consists of valid inequalities
*/
public boolean isInequality() {
return (isInequality != null && isInequality);
}
/*
* public GgbVector evaluateNormal(double u, double v){ if (funD1 == null) {
* funD1 = new FunctionNVar[2]; for (int i=0;i<2;i++){ funD1[i] =
* fun.derivative(i, 1); } }
*
*
* GgbVector vec = new GgbVector( -funD1[0].evaluate(new double[] {u,v}),
* -funD1[1].evaluate(new double[] {u,v}), 1, 0).normalized();
*
* //Application.debug("vec=\n"+vec.toString());
*
* return vec;
*
* //return new GgbVector(0,0,1,0); }
*/
@Override
public void translate(Coords v) {
fun.translate(v.getX(), v.getY());
}
/**
* Perform 3D translation
*
* @param v
* translation vector
*/
public void translate3D(Coords v) {
fun.translate(v.getX(), v.getY(), v.getZ());
}
/**
* Returns true if the element is translateable
*
* @return true
*/
@Override
public boolean isTranslateable() {
return true;
}
@Override
public void matrixTransform(double a00, double a01, double a10,
double a11) {
double d = a00 * a11 - a01 * a10;
if (d == 0) {
setUndefined();
} else {
fun.matrixTransform(a11 / d, -a01 / d, -a10 / d, a00 / d);
}
}
@Override
public void dilate(NumberValue r, Coords S) {
fun.dilate(r, S);
}
/**
* @param r
* dilate factor
* @param S
* coordinate
*/
public void dilate3D(NumberValue r, Coords S) {
fun.dilate3D(r, S);
}
@Override
public void rotate(NumberValue phi) {
fun.rotate(phi);
}
@Override
public void rotate(NumberValue phi, GeoPointND point) {
Coords P = point.getInhomCoords();
fun.rotate(phi, P);
}
@Override
public void mirror(Coords Q) {
fun.dilate(new MyDouble(kernel, -1.0), Q);
}
/**
* @param Q
* coordinate
*/
public void mirror3D(Coords Q) {
dilate3D(new MyDouble(kernel, -1.0), Q);
}
/**
* @param g1
* line
*/
public void mirror3D(GeoLineND g1) {
if (g1 instanceof GeoLine) {
mirror(g1);
return;
}
Coords coords = g1.getDirectionInD3().normalize();
double x = coords.getX();
double y = coords.getY();
double z = coords.getZ();
matrixTransform(x * x - 1, x * y, x * z, x * y, y * y - 1, y * z, x * z,
y * z, z * z - 1);
}
@Override
public void mirror(GeoLineND g1) {
fun.mirror((GeoLine) g1);
}
@Override
public void matrixTransform(double a00, double a01, double a02, double a10,
double a11, double a12, double a20, double a21, double a22) {
fun.matrixTransform(a00, a01, a02, a10, a11, a12, a20, a21, a22);
}
@Override
public boolean isGeoFunctionNVar() {
return true;
}
@Override
public boolean isLaTeXDrawableGeo() {
return true;
}
@Override
protected void getXMLtags(StringBuilder sb) {
super.getXMLtags(sb);
// needed for inequalities
if (showLineProperties()) {
getLineStyleXML(sb);
}
// level of detail
if (hasLevelOfDetail()
&& (getLevelOfDetail() == LevelOfDetail.QUALITY)) {
sb.append("\t<levelOfDetailQuality val=\"true\"/>\n");
}
}
// /////////////////////////
// LEVEL OF DETAIL
private LevelOfDetail levelOfDetail = LevelOfDetail.SPEED;
@Override
public LevelOfDetail getLevelOfDetail() {
return levelOfDetail;
}
@Override
public void setLevelOfDetail(LevelOfDetail lod) {
levelOfDetail = lod;
}
@Override
public boolean hasLevelOfDetail() {
return isFun2Var();
}
private final boolean isInequalityOrFun2Var() {
return isInequality() || ((fun != null) && (fun.getVarNumber() == 2));
}
/**
*
* @return true if it's a function 2 var (not inequality)
*/
public final boolean isFun2Var() {
return (fun != null) && (fun.getVarNumber() == 2) && !isInequality();
}
@Override
public int getMinimumLineThickness() {
if (isInequalityOrFun2Var()) {
return 0;
}
return 1;
}
@Override
public FunctionVariable[] getFunctionVariables() {
return fun.getFunctionVariables();
}
/**
* @return function variables in list
*/
public MyList getFunctionVariableList() {
MyList ml = new MyList(kernel);
for (FunctionVariable fv : fun.getFunctionVariables()) {
ml.addListElement(fv);
}
return ml;
}
@Override
public void clearCasEvalMap(String key) {
if (fun != null) {
fun.clearCasEvalMap(key);
}
}
@Override
public String getFormulaString(StringTemplate tpl,
boolean substituteNumbers) {
String ret = "";
if (isIndependent()) {
ret = toValueString(tpl);
} else {
if (getFunction() == null) {
ret = "?";
} else {
ret = substituteNumbers ? getFunction().toValueString(tpl)
: getFunction().toString(tpl);
}
}
if ("".equals(ret)) {
ret = toOutputValueString(tpl);
}
return ret;
}
@Override
final public HitType getLastHitType() {
return HitType.ON_FILLING;
}
private Coords der1 = new Coords(1, 0, 0), der2 = new Coords(0, 1, 0),
normal = new Coords(3);
private CoordsDouble3 p1 = new CoordsDouble3(), p2 = new CoordsDouble3();
private boolean setNormalFromNeighbours(Coords3 p, double u, double v,
Coords3 n) {
evaluatePoint(u + SurfaceEvaluable.NUMERICAL_DELTA, v, p1);
if (!p1.isDefined()) {
return false;
}
evaluatePoint(u, v + SurfaceEvaluable.NUMERICAL_DELTA, p2);
if (!p2.isDefined()) {
return false;
}
der1.setZ((p1.z - p.getZd()) / SurfaceEvaluable.NUMERICAL_DELTA);
der2.setZ((p2.z - p.getZd()) / SurfaceEvaluable.NUMERICAL_DELTA);
normal.setCrossProduct(der1, der2);
n.setNormalizedIfPossible(normal);
return true;
}
@Override
public boolean evaluateNormal(Coords3 p, double u, double v, Coords3 n) {
tmp[0] = u;
tmp[1] = v;
double val = evaluateNormal(0);
if (Double.isNaN(val)) {
return setNormalFromNeighbours(p, u, v, n);
}
der1.setZ(val);
val = evaluateNormal(1);
if (Double.isNaN(val)) {
return setNormalFromNeighbours(p, u, v, n);
}
der2.setZ(val);
normal.setCrossProduct(der1, der2);
n.setNormalizedIfPossible(normal);
return true;
}
/**
* evaluate normal in (x, y) coords (for 2 var function)
*
* @param x
* x coord
* @param y
* y coord
* @param n
* normal vector
*/
public void evaluateNormal(double x, double y, Coords n) {
tmp[0] = x;
tmp[1] = y;
double val = evaluateNormal(0);
der1.setZ(val);
val = evaluateNormal(1);
der2.setZ(val);
n.setCrossProduct(der1, der2);
n.normalize();
}
private double evaluateNormal(int index) {
if (fun1 == null) {
return Double.NaN;
}
return fun1[index].evaluate(tmp);
}
@Override
public void setAllVisualPropertiesExceptEuclidianVisible(GeoElement geo,
boolean keepAdvanced) {
super.setAllVisualPropertiesExceptEuclidianVisible(geo, keepAdvanced);
if (hasLevelOfDetail() && geo.hasLevelOfDetail()) {
levelOfDetail = ((SurfaceEvaluable) geo).getLevelOfDetail();
}
}
@Override
public ValueType getValueType() {
return ValueType.FUNCTION;
}
@Override
public boolean showLineProperties() {
if (super.showLineProperties()) {
return true;
}
return isInequalityOrFun2Var();
}
@Override
public void printCASEvalMapXML(StringBuilder sb) {
fun.printCASevalMapXML(sb);
}
@Override
public void updateCASEvalMap(TreeMap<String, String> map) {
fun.updateCASEvalMap(map);
}
@Override
public boolean needToShowBothRowsInAV() {
if (GeoFunction.hideDefinitionInAlgebra(getFunctionExpression())) {
return false;
}
return super.needToShowBothRowsInAV();
}
@Override
public boolean isMoveable() {
return false;
}
}