/*
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.ArrayList;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.MyPoint;
import org.geogebra.common.kernel.Path;
import org.geogebra.common.kernel.PathMover;
import org.geogebra.common.kernel.PathMoverLocus;
import org.geogebra.common.kernel.PathParameter;
import org.geogebra.common.kernel.SegmentType;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.algos.AlgoLocusSliderInterface;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.kernel.kernelND.GeoSegmentND;
import org.geogebra.common.plugin.GeoClass;
/**
* Locus of points
*
* @author Markus
* @param <T>
* 2D or 3D point type
*/
public abstract class GeoLocusND<T extends MyPoint> extends GeoElement
implements Path, Traceable, GeoLocusNDInterface {
/** maximal number of runs through the path when computing */
public static final int MAX_PATH_RUNS = 10;
private boolean defined;
/** coords of points on locus */
protected ArrayList<T> myPointList;
private ArrayList<T> poitsWithoutControl;
/**
* Creates new locus
*
* @param c
* construction
*/
public GeoLocusND(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
myPointList = new ArrayList<T>(500);
}
@Override
public GeoElement copy() {
GeoLocusND<T> ret = newGeoLocus();
ret.set(this);
return ret;
}
/**
*
* @return new GeoLocus of same type
*/
abstract protected GeoLocusND<T> newGeoLocus();
@SuppressWarnings("unchecked")
@Override
public void set(GeoElementND geo) {
if (geo instanceof GeoLocusND) {
GeoLocusND<T> locus = (GeoLocusND<T>) geo;
defined = locus.defined;
myPointList.clear();
for (MyPoint pt : locus.myPointList) {
myPointList.add((T) pt.copy());
}
}
}
/**
* Number of valid points in x and y arrays.
*
* @return number of valid points in x and y arrays.
*/
final public int getPointLength() {
return myPointList.size();
}
/**
* Clears list of points defining this locus
*/
public void clearPoints() {
myPointList.clear();
}
/**
* @return list of points that define this locus
*/
@Override
public ArrayList<T> getPoints() {
return myPointList;
}
/**
* Reset list of points for XML
*/
public void resetPointsWithoutControl() {
poitsWithoutControl = null;
}
/**
* @return points without control points
*/
@SuppressWarnings("unchecked")
public ArrayList<T> getPointsWithoutControl() {
if (poitsWithoutControl == null) {
poitsWithoutControl = new ArrayList<T>();
for (MyPoint t : myPointList) {
if (t.getSegmentType() != SegmentType.CONTROL) {
poitsWithoutControl.add((T) t.copy());
}
}
}
return poitsWithoutControl;
}
@Override
public String toString(StringTemplate tpl) {
sbToString.setLength(0);
sbToString.append(label);
sbToString.append(" = ");
sbToString.append(getDefinition(tpl));
return sbToString.toString();
}
private StringBuilder sbToString = new StringBuilder(80);
@Override
public boolean showInAlgebraView() {
return true;
}
@Override
public GeoClass getGeoClassType() {
return GeoClass.LOCUS;
}
/**
* returns all class-specific xml tags for getXML
*/
@Override
protected void getXMLtags(StringBuilder sb) {
super.getXMLtags(sb);
getLineStyleXML(sb);
}
@Override
public boolean isDefined() {
return defined;
}
/**
* @param flag
* true to make this locus defined
*/
public void setDefined(boolean flag) {
defined = flag;
}
@Override
public void setUndefined() {
defined = false;
}
@Override
public String toValueString(StringTemplate tpl) {
return getDefinition(tpl);
}
@Override
protected boolean showInEuclidianView() {
return isDefined();
}
@Override
public boolean isGeoLocus() {
return true;
}
@Override
public double getMaxParameter() {
return myPointList.size() - 1;
}
@Override
public double getMinParameter() {
return 0;
}
@Override
public boolean isClosedPath() {
if (myPointList.size() > 0) {
MyPoint first = myPointList.get(0);
MyPoint last = myPointList.get(myPointList.size() - 1);
return first.isEqual(last);
}
return false;
}
@Override
public boolean isOnPath(GeoPointND P, double eps) {
setChangingPoint(P);
MyPoint closestPoint = getClosestPoint();
if (closestPoint != null) {
return closestPointDist < eps;
}
return false;
}
/**
* set infos for current changing point
*
* @param P
* point
*/
abstract protected void setChangingPoint(GeoPointND P);
/**
*
* @param segment
* segment
* @return closest parameter on the segment from the changing point
*/
abstract protected double getChangingPointParameter(GeoSegmentND segment);
/**
* @return closest point to changing point
*/
protected MyPoint getClosestPoint() {
getClosestLine();
boolean temp = cons.isSuppressLabelsActive();
cons.setSuppressLabelCreation(true);
GeoSegmentND closestSegment = newGeoSegment();
cons.setSuppressLabelCreation(temp);
if (closestPointIndex == -1) {
return null;
}
MyPoint locusPoint = myPointList.get(closestPointIndex);
MyPoint locusPoint2 = myPointList.get(closestPointIndex + 1);
closestSegment.setCoords(locusPoint, locusPoint2);
closestPointParameter = getChangingPointParameter(closestSegment);
if (closestPointParameter < 0) {
closestPointParameter = 0;
} else if (closestPointParameter > 1) {
closestPointParameter = 1;
}
return locusPoint.barycenter(closestPointParameter, locusPoint2);
}
/**
*
* @return new GeoSegment
*/
abstract protected GeoSegmentND newGeoSegment();
/**
*
* @param segment
* segment
* @return distance from current point infos to segment
*/
abstract protected double changingPointDistance(GeoSegmentND segment);
/**
* Returns the point of this locus that is closest to current point infos.
*/
private void getClosestLine() {
int size = myPointList.size();
if (size == 0) {
return;
}
// search for closest point on path
// MyPoint closestPoint = null;
closestPointDist = Double.MAX_VALUE;
closestPointIndex = -1;
// make a segment and points to reuse
GeoSegmentND segment = newGeoSegment();
// search for closest point
for (int i = 0; i < size - 1; i++) {
MyPoint locusPoint = myPointList.get(i);
MyPoint locusPoint2 = myPointList.get(i + 1);
// not a line, just a move (eg Voronoi Diagram)
if (locusPoint2.getSegmentType() == SegmentType.MOVE_TO) {
continue;
}
// line thro' 2 points
segment.setCoords(locusPoint, locusPoint2);
double dist = changingPointDistance(segment);
if (dist < closestPointDist) {
closestPointDist = dist;
closestPointIndex = i;
}
}
}
private double closestPointDist;
/**
* index of point closest to changingPoint
*/
protected int closestPointIndex;
/**
* parameter of point closest to changingPoint
*/
protected double closestPointParameter;
private boolean trace;
@Override
public void pathChanged(GeoPointND P) {
// if kernel doesn't use path/region parameters, do as if point changed
// its coords
// #4405 if segment number changed during file loading (EV viewport)
// also don't use the new path update method
if (!getKernel().usePathAndRegionParameters(P)
|| cons.isFileLoading()) {
pointChanged(P);
return;
}
// find closest point on changed path to P
if (getParentAlgorithm() instanceof AlgoLocusSliderInterface) {
pointChanged(P);
return;
}
// new method
// keep point on same segment, the same proportion along it
// better for loci with very few segments eg from ShortestDistance[ ]
PathParameter pp = P.getPathParameter();
int n = (int) Math.floor(pp.t);
double t = pp.t - n; // between 0 and 1
// check n and n+1 are in a sensible range
// might occur if locus has changed no of segments/points
if (n >= myPointList.size() || n < 0) {
n = (n < 0) ? 0 : myPointList.size() - 1;
}
MyPoint locusPoint = myPointList.get(n);
MyPoint locusPoint2 = myPointList.get((n + 1) % myPointList.size());
P.set(1 - t, t, locusPoint, locusPoint2);
}
/**
* @param P
* point
* @param pp
* path parameter
*/
public void pathChanged(Coords P, PathParameter pp) {
int n = (int) Math.floor(pp.t);
double t = pp.t - n; // between 0 and 1
// check n and n+1 are in a sensible range
// might occur if locus has changed no of segments/points
if (n >= myPointList.size() || n < 0) {
n = (n < 0) ? 0 : myPointList.size() - 1;
}
MyPoint locusPoint = myPointList.get(n);
MyPoint locusPoint2 = myPointList.get((n + 1) % myPointList.size());
P.set(1 - t, t, locusPoint, locusPoint2);
}
@Override
public boolean isPath() {
return true;
}
// Michael Borcherds 2008-04-30
@Override
final public boolean isEqual(GeoElementND geo) {
// return false if it's a different type, otherwise use equals() method
return false;
// TODO?
// if (geo.isGeoLocus()) return xxx else return false;
}
/**
* Returns whether the value (e.g. equation) should be shown as part of the
* label description
*/
@Override
final public boolean isLabelValueShowable() {
return true;
}
@Override
final public boolean isLabelShowable() {
return true;
}
/**
* @param al
* list of points that definr this locus
*/
public void setPoints(ArrayList<T> al) {
myPointList = al;
}
@Override
final public boolean isAuxiliaryObjectByDefault() {
return true;
}
@Override
public void setTrace(boolean trace) {
this.trace = trace;
}
@Override
public boolean getTrace() {
return trace;
}
@Override
public boolean isTraceable() {
return true;
}
@Override
public boolean isFillable() {
return true;
}
@Override
public boolean isInverseFillable() {
return true;
}
@Override
final public PathMover createPathMover() {
return new PathMoverLocus<T>(this);
}
@Override
public boolean hasDrawable3D() {
return true;
}
@Override
public GeoLocusND<? extends MyPoint> getLocus() {
return this;
}
}