/*
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.algos;
import java.util.Iterator;
import java.util.TreeSet;
import org.geogebra.common.awt.GPoint2D;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Macro;
import org.geogebra.common.kernel.MacroKernel;
import org.geogebra.common.kernel.MyPoint;
import org.geogebra.common.kernel.Path;
import org.geogebra.common.kernel.PathMover;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
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.GeoLocusND;
import org.geogebra.common.kernel.implicit.GeoImplicit;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.main.App;
import org.geogebra.common.util.debug.Log;
/**
* locus line for Q dependent on P
*/
public abstract class AlgoLocusND<T extends MyPoint> extends AlgoElement {
// TODO: update locus algorithm
// * locus of Q=(x(B), a) with a= integral[f(x), 0, x(B)] and B is point on
// x-axis freezes GeoGebra
// MAX_TIME handling does not solve this yet
//
/** maximum time for the computation of one locus point in millis **/
public static final int MAX_TIME_FOR_ONE_STEP = 500;
public int MIN_STEPS_INSTANCE = PathMover.MIN_STEPS;
protected static final int MAX_X_PIXEL_DIST = 5;
private static int MAX_Y_PIXEL_DIST = 5;
protected GeoPointND movingPoint, locusPoint; // input
protected GeoLocusND<T> locus; // output
// for efficient dependency handling
private GeoElement[] efficientInput, standardInput;
private Path path; // path of P
private PathMover pathMover;
protected int pointCount;
// copies of P and Q in a macro kernel
protected GeoPointND copyP, copyQ, startPPos, startQPos;
protected double lastX, lastY;
protected double[] maxXdist, maxYdist, xmin = new double[3],
xmax = new double[3], ymin = new double[3], ymax = new double[3],
farXmin = new double[3], farXmax = new double[3],
farYmin = new double[3], farYmax = new double[3];
// private Line2D.Double tempLine = new Line2D.Double();
private boolean continuous;
protected boolean[] lastFarAway = { false, false, false };
private boolean foundDefined;
private boolean maxTimeExceeded;
private Construction macroCons;
private MacroKernel macroKernel;
// private AlgorithmSet macroConsAlgoSet;
// list with all original elements used for the macro construction
private TreeSet<ConstructionElement> locusConsOrigElements;
private TreeSet<GeoElement> Qin;
private int views = 1;
// private Updater updater;
// Constructor called from AlgoLocusList
/**
* @param cons
* construction
* @param Q
* locus point
* @param P
* moving point
* @param min_steps
* number of steps
* @param registerCE
* whether to listen to zooming
*/
public AlgoLocusND(Construction cons, GeoPointND Q, GeoPointND P,
int min_steps, boolean registerCE) {
super(cons, registerCE);
createMaxDistances();
MIN_STEPS_INSTANCE = min_steps;
this.movingPoint = P;
this.locusPoint = Q;
path = P.getPath();
pathMover = path.createPathMover();
createStartPos(cons);
// we may need locus in init when something goes wrong
locus = newGeoLocus(cons);
init();
updateScreenBorders();
setInputOutput(); // for AlgoElement
if (registerCE) {
cons.registerEuclidianViewCE(this);
}
compute();
// we may have created a starting point for the path now
// make sure that the movingPoint in the main construction
// uses the correct path parameter for it
path.pointChanged(P);
}
/**
* create max distances arrays
*/
protected void createMaxDistances() {
maxXdist = new double[3];
maxYdist = new double[3];
}
/**
* create start pos points
*
* @param cons1
* construction
*/
abstract protected void createStartPos(Construction cons1);
/**
*
* @param cons1
* construction
* @return new GeoLocus
*/
abstract protected GeoLocusND<T> newGeoLocus(Construction cons1);
/**
* @param cons
* construction
* @param label
* output label
* @param Q
* locus point
* @param P
* moving point
*/
public AlgoLocusND(Construction cons, String label, GeoPointND Q,
GeoPointND P) {
this(cons, Q, P, PathMover.MIN_STEPS, true);
locus.setLabel(label);
}
// private void printMacroConsStatus() {
// System.out.print("MOVER POINT: " + Pcopy);
// System.out.print(", pp.t: " + Pcopy.getPathParameter().t);
// System.out.println(", locus point: " + Qcopy);
//
// TreeSet geos = macroCons.getGeoSetLabelOrder();
// Iterator it = geos.iterator();
// while (it.hasNext()) {
// GeoElement geo = (GeoElement) it.next();
// System.out.println(" " + geo);
// }
// }
@Override
public Commands getClassName() {
return Commands.Locus;
}
@Override
public int getRelatedModeID() {
return EuclidianConstants.MODE_LOCUS;
}
/**
* Returns the dependent point
*
* @return dependent point Q
*/
public GeoPointND getQ() {
return locusPoint;
}
/**
* A way more descriptive name for the getter.
*
* @return dependent point Q
*/
public GeoPointND getLocusPoint() {
return locusPoint;
}
/**
* @return moving point P.
*/
public GeoPointND getMovingPoint() {
return movingPoint;
}
private void init() {
// copy the construction
Qin = ((GeoElement) locusPoint).getAllPredecessors(); // all parents of
// Q
// get intersection of all children of P and all parents of Q
locusConsOrigElements = new TreeSet<ConstructionElement>();
TreeSet<Long> usedAlgoIds = new TreeSet<Long>();
Iterator<GeoElement> it = Qin.iterator();
while (it.hasNext()) {
GeoElement parent = it.next();
if (parent.isLabelSet()
&& parent.isChildOf(movingPoint)) {
// note: locusConsOrigElements will contain AlgoElement and
// GeoElement objects
Macro.addDependentElement(parent, locusConsOrigElements,
usedAlgoIds);
}
}
// ensure that P and Q have labels set
// Note: we have to undo this at the end of this method !!!
boolean isLabeledP = movingPoint.isLabelSet();
if (!isLabeledP) {
((GeoElement) movingPoint).setLabelSimple(
((GeoElement) movingPoint).getDefaultLabel());
((GeoElement) movingPoint).setLabelSet(true);
}
boolean isLabeledQ = locusPoint.isLabelSet();
if (!isLabeledQ) {
((GeoElement) locusPoint).setLabelSimple(
((GeoElement) locusPoint).getDefaultLabel());
((GeoElement) locusPoint).setLabelSet(true);
}
// add moving point on line
// locusConsOrigElements.add(movingPoint);
// instead, add the moving point by adding incidences of it to the path
// at the same time (see buildLocusMacroConstruction)
// note: this will add the parent algo of the moving point, which is
// AlgoPointOnPath...
// In theory, this is not harmful as locusConsOrigElements is just used
// in AlgoLocus,
// and macroCons.updateConstruction is only called one time, after
// resetMacroConstruction
Macro.addDependentElement((GeoElement) movingPoint,
locusConsOrigElements, usedAlgoIds);
// add locus creating point and its algorithm to locusConsOrigElements
Macro.addDependentAlgo(locusPoint.getParentAlgorithm(),
locusConsOrigElements, usedAlgoIds);
// create macro construction
buildLocusMacroConstruction(locusConsOrigElements);
// if we used temp labels remove them again
if (!isLabeledP) {
((GeoElement) movingPoint).setLabelSet(false);
}
if (!isLabeledQ) {
((GeoElement) locusPoint).setLabelSet(false);
}
}
// for AlgoElement
@Override
protected void setInputOutput() {
// it is inefficient to have Q and P as input
// let's take all independent parents of Q
// and the path as input
TreeSet<GeoElement> inSet = new TreeSet<GeoElement>();
inSet.add(path.toGeoElement());
// we need all independent parents of Q PLUS
// all parents of Q that are points on a path
Iterator<GeoElement> it = Qin.iterator();
while (it.hasNext()) {
GeoElement geo = it.next();
if (geo.isChangeable()) {
inSet.add(geo);
}
}
// remove P from input set!
inSet.remove(movingPoint);
efficientInput = new GeoElement[inSet.size()];
it = inSet.iterator();
int i = 0;
while (it.hasNext()) {
efficientInput[i] = it.next();
i++;
}
// the standardInput array should be used for
// the dependency graph
standardInput = new GeoElement[2];
standardInput[0] = (GeoElement) locusPoint;
standardInput[1] = (GeoElement) movingPoint;
setOutputLength(1);
setOutput(0, locus);
// handle dependencies
setEfficientDependencies(standardInput, efficientInput);
}
/**
* Returns locus
*
* @return locus
*/
public GeoLocusND<T> getLocus() {
return locus;
}
private void buildLocusMacroConstruction(
TreeSet<ConstructionElement> locusConsElements) {
// build macro construction
macroKernel = kernel.newMacroKernel();
macroKernel.setGlobalVariableLookup(true);
// tell the macro construction about reserved names:
// these names will not be looked up in the parent
// construction
Iterator<ConstructionElement> it = locusConsElements.iterator();
while (it.hasNext()) {
ConstructionElement ce = it.next();
if (ce.isGeoElement()) {
GeoElement geo = (GeoElement) ce;
macroKernel.addReservedLabel(
geo.getLabel(StringTemplate.defaultTemplate));
}
}
try {
// get XML for macro construction of P -> Q
String locusConsXML = Macro.buildMacroXML(kernel, locusConsElements)
.toString();
macroKernel.loadXML(locusConsXML);
// get the copies of P and Q from the macro kernel
copyP = (GeoPointND) macroKernel
.lookupLabel(((GeoElement) movingPoint).getLabelSimple());
((GeoElement) copyP).setFixed(false);
copyP.setPath(movingPoint.getPath());
// alternative way to add the incidence of the path to Pcopy
// see init()
// Pcopy.addIncidence((GeoElement)path);
// AlgoIntersectLineConic.resetPossibleSpecialCase();//not
// implemented
copyQ = (GeoPointND) macroKernel
.lookupLabel(((GeoElement) locusPoint).getLabelSimple());
macroCons = macroKernel.getConstruction();
/*
* // make sure that the references to e.g. start/end point of a
* segment are not // changed later on. This is achieved by setting
* isMacroOutput to true it = macroCons.getGeoElementsIterator();
* while (it.hasNext()) { GeoElement geo = (GeoElement) it.next();
* geo.isAlgoMacroOutput = true; } Pcopy.isAlgoMacroOutput = false;
*/
} catch (Exception e) {
e.printStackTrace();
locus.setUndefined();
macroCons = null;
}
// //Application.debug("P: " + P + ", kernel class: " +
// P.kernel.getClass());
// Application.debug("Pcopy: " + Pcopy + ", kernel class: " +
// Pcopy.kernel.getClass());
// //Application.debug("P == Pcopy: " + (P == Pcopy));
// //Application.debug("Q: " + Q + ", kernel class: " +
// Q.kernel.getClass());
// Application.debug("Qcopy: " + Qcopy + ", kernel class: " +
// Qcopy.kernel.getClass());
// //Application.debug("Q == Qcopy: " + (Q == Qcopy));
}
/**
* Set all elements in locusConsElements to the current values of the main
* construction
*/
private void resetMacroConstruction() {
Iterator<ConstructionElement> it = locusConsOrigElements.iterator();
while (it.hasNext()) {
ConstructionElement ce = it.next();
if (ce.isGeoElement()) {
GeoElement geoOrig = (GeoElement) ce;
// do not copy functions, their expressions already
// include references to the correct other geos
if (!geoOrig.isGeoFunction()) {
GeoElement geoCopy = macroCons
.lookupLabel(geoOrig.getLabelSimple());
if (geoCopy != null) {
try {
ExpressionNode en = geoCopy.getDefinition();
geoCopy.set(geoOrig);
geoCopy.setDefinition(en);
geoCopy.update();
} catch (Exception e) {
Log.debug(
"AlgoLocus: error in resetMacroConstruction(): "
+ e.getMessage());
}
}
}
}
}
}
// compute locus line
@Override
public final void compute() {
if (!movingPoint.isDefined() || macroCons == null
|| !isPathIterable(path.toGeoElement())) {
locus.setUndefined();
return;
}
updateScreenBordersIfNecessary();
locus.clearPoints();
clearCache();
pointCount = 0;
lastX = Double.MAX_VALUE;
lastY = Double.MAX_VALUE;
maxTimeExceeded = false;
foundDefined = false;
boolean prevQcopyDefined = false;
int max_runs;
// continuous kernel?
continuous = kernel.isContinuous();
macroKernel.setContinuous(continuous);
// update macro construction with current values of global vars
resetMacroConstruction();
macroCons.updateConstruction();
// lines: start from startpoint to avoid inf. problems.
// Otherwise go from endpoint to endpoint
if (!MyDouble.isFinite(path.getMinParameter())
&& 0 < path.getMaxParameter()) {
copyP.getPathParameter().setT(0);
} else {
copyP.getPathParameter().setT(path.getMinParameter());
}
pathMover.init(copyP, MIN_STEPS_INSTANCE);
if (continuous) {
// continous constructions may need several parameter run throughs
// to draw all parts of the locus
max_runs = GeoLocusND.MAX_PATH_RUNS;
} else {
max_runs = 1;
}
// update Pcopy to compute Qcopy
pcopyUpdateCascade();
prevQcopyDefined = copyQ.isDefined() && !copyQ.isInfinite();
// move Pcopy along the path
// do this until Qcopy comes back to its start position
// for continuous constructions
// this may require several runs of Pcopy along the whole path
int runs = 1;
int MAX_LOOPS = 2 * PathMover.MAX_POINTS * views;
int whileLoops = 0;
do {
boolean finishedRun = false;
while (!finishedRun && !maxTimeExceeded
&& pointCount <= PathMover.MAX_POINTS * views
&& whileLoops <= MAX_LOOPS) {
whileLoops++;
// lineTo may be false due to a parameter jump
// i.e. param in [0,1] gets bigger than 1 and thus jumps to 0
boolean parameterJump = !pathMover.getNext(copyP);
boolean stepChanged = false;
// update construction
pcopyUpdateCascade();
// Qcopy DEFINED
if (copyQ.isDefined() && !copyQ.isInfinite()) {
// STANDARD CASE: no parameter jump
if (!parameterJump) {
// make steps smaller until distance ok to connect with
// last point
while (copyQ.isDefined() && !copyQ.isInfinite()
&& !distanceOK(copyQ) && !maxTimeExceeded) {
// go back and try smaller step
boolean smallerStep = pathMover.smallerStep();
if (!smallerStep) {
break;
}
stepChanged = true;
pathMover.stepBack();
pathMover.getNext(copyP);
// update construction
pcopyUpdateCascade();
}
if (copyQ.isDefined() && !copyQ.isInfinite()) {
// draw point
insertPoint(copyQ, distanceSmall(copyQ, true));
prevQcopyDefined = true;
}
}
// PARAMETER jump: !lineTo
else {
// draw point
insertPoint(copyQ, distanceSmall(copyQ, true));
prevQcopyDefined = true;
}
}
// Qcopy NOT DEFINED
else {
// check if we moved from defined to undefined case:
// step back and try with smaller step
if (prevQcopyDefined && !parameterJump) {
pathMover.stepBack();
// set smallest step
if (!pathMover.smallerStep()) {
prevQcopyDefined = false;
} else {
stepChanged = true;
}
}
// add better undefined case support for continuous curves
// maybe change orientation of path mover
}
// if we didn't decrease the step width increase it
if (!stepChanged) {
pathMover.biggerStep();
}
// end of run: the next step would pass the start position
if (!pathMover.hasNext()) {
if (distanceSmall(startQPos, true)) {
// draw line back to first point when it's close enough
insertPoint(startQPos, true);
finishedRun = true;
} else {
// decrease step until another step is possible
boolean check = true;
while (check) {
check = !pathMover.hasNext()
&& pathMover.smallerStep();
}
// no smaller step possible: run finished
if (!pathMover.hasNext()) {
finishedRun = true;
}
}
}
}
// calculating the steps took too long, so we stopped somewhere
if (maxTimeExceeded) {
Log.error("AlgoLocus: max time exceeded");
return;
}
// make sure that Pcopy is back at startPos now
// look at Qcopy at startPos
((GeoElement) copyP).set(startPPos);
pcopyUpdateCascade();
if (differentFromLast(copyQ)) {
insertPoint(copyQ, distanceSmall(copyQ, true));
}
// Application.debug("run: " + runs);
// Application.debug("pointCount: " + pointCount);
// Application.debug(" startPos: " + QstartPos);
// Application.debug(" Qcopy: " + Qcopy);
// we are finished with all runs
// if we got back to the start position of Qcopy
// AND if the direction of moving along the path
// is positive like in the beginning
if (pathMover.hasPositiveOrientation()) {
boolean equal = areEqual(startQPos, copyQ);
if (equal) {
break;
}
}
pathMover.resetStartParameter();
runs++;
} while (runs < max_runs);
// set defined/undefined
locus.setDefined(foundDefined);
// System.out.println(" first point: " +
// locus.getMyPointList().get(0));
// ArrayList list = locus.getMyPointList();
// for (int i=list.size()-10; i < list.size()-1; i++) {
// System.out.println(" point: " + list.get(i));
// }
// System.out.println(" last point: " +
// locus.getMyPointList().get(pointCount-1));
// Application.debug("LOCUS COMPUTE updateCascades: " + countUpdates +
// ", cache used: " + useCache);
}
/**
*
* @param point
* point
* @return true if point is not equal to last coords
*/
abstract protected boolean differentFromLast(GeoPointND point);
/**
*
* @param p1
* first point
* @param p2
* second point
* @return true if p1 and p2 has same coords for min precision
*/
abstract protected boolean areEqual(GeoPointND p1, GeoPointND p2);
private static boolean isPathIterable(GeoElement geoElement) {
if (geoElement.isGeoImplicitPoly()) {
return ((GeoImplicit) geoElement).isOnScreen();
}
return geoElement.isDefined();
}
/**
* Calls Pcopy.updateCascade() to compute Qcopy. For non-continous
* constructions caching of previous paramater positions is used.
*/
private void pcopyUpdateCascade() {
if (continuous) {
// CONTINOUS construction
// don't use caching for continuous constructions:
// the same position of Pcopy can have different results for Qcopy
copyP.updateCascade();
} else {
// NON-CONTINOUS construction
// check if the path parameter's resulting Qcopy is already in cache
double param = copyP.getPathParameter().t;
GPoint2D cachedPoint = getCachedPoint(param);
if (cachedPoint == null) {
// measure time needed for update of construction
long startTime = System.currentTimeMillis();
// result not in cache: update Pcopy to compute Qcopy
copyP.updateCascade();
long updateTime = System.currentTimeMillis() - startTime;
// if it takes too much time to calculate a single step, we stop
if (updateTime > MAX_TIME_FOR_ONE_STEP) {
Log.debug("AlgoLocus: max time exceeded " + updateTime);
maxTimeExceeded = true;
}
// cache value of Qcopy
putCachedPoint(param, copyQ);
} else {
// use cached result to set Qcopy
ExpressionNode qDef = copyQ.getDefinition();
copyQ.setCoords(cachedPoint.getX(), cachedPoint.getY(), 1.0);
copyQ.setDefinition(qDef);
}
}
// if (Qcopy.isDefined() && !Qcopy.isInfinite()) {
// if (!foundDefined)
// System.out.print(locus.label + " FIRST DEFINED param: " +
// Pcopy.getPathParameter().t);
// else
// System.out.print(locus.label + " param: " +
// Pcopy.getPathParameter().t);
// System.out.println(", Qcopy: " + Qcopy);
// } else {
// System.out.print(locus.label + " param: " +
// Pcopy.getPathParameter().t);
// System.out.println(", Qcopy: NOT DEFINED");
// }
// check found defined
if (!foundDefined && copyQ.isDefined() && !copyQ.isInfinite()) {
pathMover.init(copyP, MIN_STEPS_INSTANCE);
((GeoElement) startPPos).set(copyP);
((GeoElement) startQPos).set(copyQ);
foundDefined = true;
// insert first point
insertPoint(copyQ, false);
}
}
private void clearCache() {
for (int i = 0; i < paramCache.length; i++) {
paramCache[i] = Double.NaN;
if (qcopyCache[i] == null) {
qcopyCache[i] = newCache();
}
}
}
private GPoint2D getCachedPoint(double param) {
// search for cached parameter
for (int i = 0; i < paramCache.length; i++) {
if (param == paramCache[i]) {
return qcopyCache[i];
}
}
return null;
}
private void putCachedPoint(double param, GeoPointND Qcopy0) {
cacheIndex++;
if (cacheIndex >= paramCache.length) {
cacheIndex = 0;
}
paramCache[cacheIndex] = param;
setQCopyCache(qcopyCache[cacheIndex], Qcopy0);
}
// small cache of 3 last parameters and Qcopy positions
private double[] paramCache = new double[3];
private T[] qcopyCache = createQCopyCache(3);
private int cacheIndex = 0;
/**
*
* @param length
* number of elements
* @return new q copy cache
*/
abstract protected T[] createQCopyCache(int length);
/**
* set point's coords to copy
*
* @param copy
* copy
* @param point
* point
*/
abstract protected void setQCopyCache(T copy, GeoPointND point);
/**
*
* @return new instance for cache
*/
abstract protected T newCache();
/**
* insert point
*
* @param point
* point
* @param lineTo
* if line to
*/
abstract protected void insertPoint(GeoPointND point, boolean lineTo);
/**
*
* @param point
* point
* @return if the point is far away
*/
abstract protected boolean isFarAway(GeoPointND point, int i);
/**
*
* @param Q
* point
* @param i
* view index
* @return if distance ok for the point in this rectangle
*/
abstract protected boolean distanceOK(GeoPointND Q, int i);
private boolean distanceOK(GeoPointND Q) {
boolean[] distanceOK = { false, false, false };
for (int i = 0; i < distanceOK.length; i++) {
if (lastFarAway[i] && isFarAway(Q, i)) {
distanceOK[i] = distanceOK(Q, i);
} else {
distanceOK[i] = distanceSmall(Q, false);
}
}
for (int i = 0; i < distanceOK.length; i++) {
if (!distanceOK[i]) {
return false;
}
}
return true;
}
/**
*
* @param Q
* point
* @param orInsteadOfAnd
* @return true if distance is small to last point
*/
abstract protected boolean distanceSmall(GeoPointND Q,
boolean orInsteadOfAnd);
protected boolean[] visibleEV = { false, false, false };
/**
* @param i
* 1 for EV1, 2 for EV2, 3 for 3D
* @return whether the locus is visible
*/
boolean isVisibleInEV(int i) {
switch (i) {
case 1:
if (!locus.isVisibleInView(App.VIEW_EUCLIDIAN)) {
return false;
}
if (!kernel.getApplication().getEuclidianView1().isShowing()) {
return false;
}
return true;
case 2:
if (!locus.isVisibleInView(App.VIEW_EUCLIDIAN2)) {
return false;
}
if (!kernel.getApplication().hasEuclidianView2(1)) {
return false;
}
return true;
case 3:
if (!locus.isVisibleInView3D()) {
return false;
}
if (kernel.getApplication().isEuclidianView3Dinited()) {
return kernel.getApplication().getEuclidianView3D().isShowing();
}
}
return false;
}
void updateScreenBordersIfNecessary() {
for (int i = 0; i < visibleEV.length; i++) {
if (isVisibleInEV(i + 1) != visibleEV[i]) {
updateScreenBorders();
return;
}
}
}
boolean updateScreenBorders(int i) {
boolean changed = xmax[i] != kernel.getXmax(i);
xmax[i] = kernel.getXmax(i);
changed = changed || xmin[i] != kernel.getXmin(i);
xmin[i] = kernel.getXmin(i);
changed = changed || ymax[i] != kernel.getYmax(i);
ymax[i] = kernel.getYmax(i);
changed = changed || ymin[i] != kernel.getYmin(i);
ymin[i] = kernel.getYmin(i);
setMaxDistances(i);
// near to screen rectangle
return changed;
// Log.debug(viewIndex+" -- "+xmin[i]+","+ymin[i]+" --
// "+xmax[i]+","+ymax[i]);
}
protected void setMaxDistances(int i) {
maxXdist[i] = MAX_X_PIXEL_DIST / kernel.getXscale(i); // widthRW / 100;
maxYdist[i] = MAX_Y_PIXEL_DIST / kernel.getYscale(i); // heightRW / 100;
// we take a bit more than the screen
// itself so that we don't loose locus
// lines too often
// that leave and reenter the screen
double widthRW = xmax[i] - xmin[i];
double heightRW = ymax[i] - ymin[i];
farXmin[i] = xmin[i] - widthRW / 2;
farXmax[i] = xmax[i] + widthRW / 2;
farYmin[i] = ymin[i] - heightRW / 2;
farYmax[i] = ymax[i] + heightRW / 2;
}
boolean updateScreenBorders() {
for (int i = 0; i < visibleEV.length; i++) {
visibleEV[i] = isVisibleInEV(i + 1);
}
if (visibleEV[0] && visibleEV[1]) {
views = 2;
} else {
views = 1;
}
if (visibleEV[2]) {
views++;
}
boolean changed = false;
for (int i = 0; i < visibleEV.length
&& i < kernel.getXmaxLength(); i++) {
if (visibleEV[i]) {
changed = changed || updateScreenBorders(i);
}
}
return changed;
}
@Override
public boolean euclidianViewUpdate() {
boolean changed = updateScreenBorders();
if (changed || !locus.getAlgoUpdateSet().isEmpty()) {
update();
}
return false;
}
}