/*
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.
*/
/*
* Created on 03.12.2004
*/
package org.geogebra.common.geogebra3D.kernel3D.geos;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.PathMover;
import org.geogebra.common.kernel.PathMoverGeneric;
import org.geogebra.common.kernel.PathParameter;
import org.geogebra.common.kernel.RegionParameters;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.Transform;
import org.geogebra.common.kernel.Matrix.CoordNearest;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.algos.AlgoConicPartCircumcircle;
import org.geogebra.common.kernel.algos.AlgoConicPartConicPoints;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoNumberValue;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.geos.LimitedPath;
import org.geogebra.common.kernel.kernelND.GeoConicNDConstants;
import org.geogebra.common.kernel.kernelND.GeoConicPartND;
import org.geogebra.common.kernel.kernelND.GeoConicPartParameters;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.plugin.GeoClass;
/**
* GeoCirclePart for
*
* @author Markus Hohenwarter
*
*/
public class GeoConicPart3D extends GeoConic3D
implements GeoConicPartND, GeoNumberValue, LimitedPath {
// parameters (e.g. angles) for arc
private GeoConicPartParameters parameters;
/**
* GeoCirclePart is constructed by AlgoCirclePart...
*
* @param c
* construction
* @param type
* CONIC_PART_ARC or CONIC_PART_SECTOR
*/
public GeoConicPart3D(Construction c, int type) {
super(c);
parameters = new GeoConicPartParameters(this, type);
setConstructionDefaults();
}
/**
* Copy constructor
*
* @param conic
* conic to copy
*/
public GeoConicPart3D(GeoConicPartND conic) {
this(((GeoElement) conic).cons, conic.getConicPartType());
set((GeoElement) conic);
}
@Override
public GeoClass getGeoClassType() {
return GeoClass.CONICPART;
}
@Override
public String getTypeString() {
switch (parameters.conicPartType) {
case CONIC_PART_ARC:
return "Arc";
case CONIC_PART_SECTOR:
return "Sector";
default:
return super.getTypeString();
}
}
@Override
public GeoElement copyInternal(Construction construction) {
GeoConicPart3D ret = new GeoConicPart3D(construction,
parameters.conicPartType);
ret.set(this);
return ret;
}
@Override
public void set(GeoElementND geo) {
super.set(geo);
if (!geo.isGeoConicPart()) {
return;
}
GeoConicPartND cp = (GeoConicPartND) geo;
// class specific attributes
parameters.set(cp.getParameters());
}
@Override
public void setVisualStyle(GeoElement geo) {
super.setVisualStyle(geo);
if (geo.isGeoConicPart()) {
GeoConicPartND cp = (GeoConicPartND) geo;
parameters.allowOutlyingIntersections = cp
.getParameters().allowOutlyingIntersections;
}
}
@Override
public GeoConicPartParameters getParameters() {
return parameters;
}
/**
* Sector or arc
*
* @return CONIC_PART_ARC or CONIC_PART_SECTOR
*/
@Override
final public int getConicPartType() {
if (parameters == null) { // for default settings
return GeoConicNDConstants.CONIC_PART_ARC;
}
return parameters.conicPartType;
}
/**
* @return start parameter
*/
@Override
final public double getParameterStart() {
return parameters.paramStart;
}
/**
* @return end parameter
*/
@Override
final public double getParameterEnd() {
return parameters.paramEnd;
}
/**
* @return end parameter - start parameter
*/
@Override
final public double getParameterExtent() {
return parameters.paramExtent;
}
/**
* @return start parameter
*/
@Override
final public boolean positiveOrientation() {
return parameters.posOrientation;
}
/**
* Returns wheter c is equal to this conic part
*/
// Michael Borcherds 2008-05-01
@Override
final public boolean isEqual(GeoElementND geo) {
if (!geo.isGeoConicPart()) {
return false;
}
GeoConicPart3D other = (GeoConicPart3D) geo;
return parameters.isEqual(other.parameters) && super.isEqual(other);
}
/**
* Sets parameters and calculates this object's value. For type
* CONIC_PART_ARC the value is the length, for CONIC_PART_SECTOR the value
* is an area. This method should only be called by the parent algorithm
*
* @param start
* start param
* @param end
* end param
* @param positiveOrientation
* true for positive orientation
*/
@Override
final public void setParameters(double start, double end,
boolean positiveOrientation) {
parameters.setParameters(super.isDefined(), start, end,
positiveOrientation);
}
@Override
public void setParametersToSinglePoint() {
parameters.value = 0;
parameters.setValueDefined(true);
}
@Override
final public boolean isDefined() {
return parameters.isValueDefined();
}
@Override
public void setUndefined() {
parameters.setValueDefined(false);
}
/**
* Returns arc length / area as appropriate
*
* @return arc length / area as appropriate
*/
final public double getValue() {
return parameters.getValue();
}
/**
* Returns arc length
*
* @return arc length
*/
@Override
final public double getArcLength() {
return parameters.getArcLength();
}
/**
* Returns the area
*
* @return area
*/
@Override
final public double getArea() {
return parameters.getArea();
}
@Override
final public String toString(StringTemplate tpl) {
sbToString.setLength(0);
sbToString.append(label);
sbToString.append(" = ");
sbToString.append(toValueString(tpl));
return sbToString.toString();
}
private StringBuilder sbToString = new StringBuilder(50);
@Override
final public String toValueString(StringTemplate tpl) {
if (parameters.isValueDefined()) {
return kernel.format(parameters.value, tpl);
}
return kernel.format(Double.NaN, tpl);
}
@Override
public boolean allowOutlyingIntersections() {
return parameters.allowOutlyingIntersections;
}
@Override
public void setAllowOutlyingIntersections(boolean flag) {
parameters.allowOutlyingIntersections = flag;
}
@Override
public boolean keepsTypeOnGeometricTransform() {
return parameters.keepTypeOnGeometricTransform;
}
@Override
public void setKeepTypeOnGeometricTransform(boolean flag) {
parameters.keepTypeOnGeometricTransform = flag;
}
/*
* TODO
*
* @Override final public boolean isLimitedPath() { return true; }
*/
@Override
public boolean isIntersectionPointIncident(GeoPoint pt, double eps) {
if (parameters.allowOutlyingIntersections) {
return isOnFullConic(pt, eps);
}
return isOnPath(pt, eps);
}
/**
* states wheter P lies on this conic part or not
*/
@Override
public boolean isOnPath(GeoPointND PI, double precision) {
double eps = precision;
GeoPoint P = (GeoPoint) PI;
if (P.getPath() == this) {
return true;
}
// check if P lies on conic first
if (!isOnFullConic(P, eps)) {
return false;
}
// idea: calculate path parameter and check
// if it is in [0, 1]
// remember the old values
double px = P.x, py = P.y, pz = P.z;
PathParameter tempParam = getTempPathParameter();
PathParameter pPP = P.getPathParameter();
tempParam.set(pPP);
switch (type) {
case CONIC_CIRCLE:
case CONIC_ELLIPSE:
parameters.setEllipseParameter(P.getCoordsInD2(getCoordSys()),
P.getPathParameter());
break;
// degenerate case: two rays or one segment
case CONIC_PARALLEL_LINES:
if (parameters.posOrientation) {
// segment
lines[0].pointChanged(P);
} else {
// two rays: no point should lie on them
P.getPathParameter().t = -1;
}
break;
default:
pPP.t = -1;
// Application.debug("GeoConicPart.isIncident: unsupported conic
// part for conic type: "
// + type);
}
// adapt eps for very large circles (almost line)
if (halfAxes[0] > 100) {
eps = Math.max(Kernel.MAX_PRECISION, eps / halfAxes[0]);
}
boolean result = pPP.t >= -eps && pPP.t <= 1 + eps;
// restore old values
P.x = px;
P.y = py;
P.z = pz;
pPP.set(tempParam);
return result;
}
private PathParameter tempPP;
private PathParameter getTempPathParameter() {
if (tempPP == null) {
tempPP = new PathParameter();
}
return tempPP;
}
/*
* Path Interface implementation
*/
@Override
public boolean isClosedPath() {
return false;
}
@Override
public void pointChanged(Coords P, PathParameter pp) {
pp.setPathType(type);
switch (type) {
case CONIC_CIRCLE:
case CONIC_ELLIPSE:
parameters.setEllipseParameter(P, pp);
parameters.clipEllipseParameter(P, pp);
break;
// degenerate case: two rays or one segment
case CONIC_PARALLEL_LINES:
if (parameters.posOrientation) {
// segment
lines[0].doPointChanged(P, pp);
// make sure we don't get outside [0,1]
if (pp.t < 0) {
pp.t = 0;
pathChanged(P, pp);
} else if (pp.t > 1) {
pp.t = 1;
pathChanged(P, pp);
}
} else {
// two rays
// we take point at infinty
/*
* P.x = -lines[0].y; P.y = lines[0].x; P.z = 0.0;
*/
P.setX(-lines[0].y);
P.setY(lines[0].x);
P.setZ(0);
}
break;
default:
pp.t = Double.NaN;
// Application.debug("GeoConicPart.pointChanged(): unsupported conic
// part for conic type: "
// + type);
}
}
@Override
protected void pathChanged(Coords P, PathParameter pp) {
if (!parameters.isValueDefined()) {
P.setX(Double.NaN);
return;
}
if (pp.getPathType() != type || Double.isNaN(pp.t)) {
pointChanged(P, pp);
return;
}
if (pp.t < 0.0) {
pp.t = 0;
} else if (pp.t > 1.0) {
pp.t = 1;
}
// handle conic types
switch (type) {
case CONIC_CIRCLE:
case CONIC_ELLIPSE:
// if type of path changed (other conic) then we
// have to recalc the parameter with pointChanged()
if (pp.getPathType() != type) {
pointChanged(P, pp);
return;
}
// calc Point on conic using this parameter (in eigenvector space)
double t = parameters.posOrientation ? pp.t : 1.0 - pp.t;
double angle = parameters.paramStart + t * parameters.paramExtent;
P.setX(halfAxes[0] * Math.cos(angle));
P.setY(halfAxes[1] * Math.sin(angle));
P.setZ(1);
coordsEVtoRW(P);
break;
case CONIC_PARALLEL_LINES:
if (parameters.posOrientation) { // segment
// if type of path changed (other conic) then we
// have to recalc the parameter with pointChanged()
if (pp.getPathType() != type) {
pointChanged(P, pp);
} else {
lines[0].pathChanged(P, pp);
}
} else {
// two rays
// we take point at infinty
P.setX(-lines[0].y);
P.setY(lines[0].x);
P.setZ(0);
}
break;
default:
// Application.debug("GeoConicPart.pathChanged(): unsupported conic
// part for conic type: "
// + type);
}
}
/**
* Returns the smallest possible parameter value for this path (may be
* Double.NEGATIVE_INFINITY)
*
*/
@Override
public double getMinParameter() {
switch (type) {
case CONIC_CIRCLE:
case CONIC_ELLIPSE:
return 0;
// degenerate case: two rays or one segment
case CONIC_PARALLEL_LINES:
if (parameters.posOrientation) {
// segment
return 0;
}
// two rays
return Double.NEGATIVE_INFINITY;
default:
return Double.NaN;
}
}
/**
* Returns the largest possible parameter value for this path (may be
* Double.POSITIVE_INFINITY)
*
*/
@Override
public double getMaxParameter() {
switch (type) {
case CONIC_CIRCLE:
case CONIC_ELLIPSE:
return 1;
// degenerate case: two rays or one segment
case CONIC_PARALLEL_LINES:
if (parameters.posOrientation) {
// segment
return 1;
}
// two rays
return Double.POSITIVE_INFINITY;
default:
return Double.NaN;
}
}
@Override
public PathMover createPathMover() {
return new PathMoverGeneric(this);
}
/**
* returns all class-specific xml tags for saveXML
*/
@Override
protected void getXMLtags(StringBuilder sb) {
super.getXMLtags(sb);
parameters.getXMLtags(sb);
}
/**
* interface NumberValue
*/
@Override
public MyDouble getNumber() {
return new MyDouble(kernel, getValue());
}
@Override
final public double getDouble() {
return getValue();
}
@Override
public boolean isNumberValue() {
return true;
}
@Override
public boolean isGeoConicPart() {
return true;
}
/*
* TODO public GeoElement[] createTransformedObject(Transform t, String
* transformedLabel) { if (parameters.keepTypeOnGeometricTransform) {
* algoParent = getParentAlgorithm(); }
*
* int conic_part_type = parameters.conic_part_type;
*
* // CREATE CONIC PART if (algoParent instanceof AlgoConicPartCircle) { //
* transform points AlgoConicPartCircle algo = (AlgoConicPartCircle)
* algoParent; GeoPointND[] points = { algo.getCenter(),
* algo.getStartPoint(), algo.getEndPoint() };
*
* // create circle with center through startPoint AlgoCircleTwoPoints
* algoCircle = new AlgoCircleTwoPoints(cons, (GeoPoint) points[0],
* (GeoPoint) points[1]); cons.removeFromConstructionList(algoCircle);
* GeoConic circle = algoCircle.getCircle();
*
* // transform points and circle points = t.transformPoints(points);
* GeoConicND transformedCircle = t .getTransformedConic(circle);
* cons.removeFromConstructionList(transformedCircle .getParentAlgorithm());
*
* // create a new arc from the transformed circle using startPoint and //
* endPoint AlgoConicPartConicPoints algoResult = new
* AlgoConicPartConicPoints( cons, transformedLabel, transformedCircle,
* (GeoPoint) points[1], (GeoPoint) points[2], conic_part_type);
* GeoConicPart3D conicPart = algoResult.getConicPart();
* conicPart.setVisualStyleForTransformations(this); GeoElement[] geos = {
* conicPart, (GeoElement) points[0], (GeoElement) points[2], (GeoElement)
* points[1] };
*
* return geos; } else if (algoParent instanceof AlgoConicPartCircumcircle)
* { GeoPointND[] points = { (GeoPoint) algoParent.input[0], (GeoPoint)
* algoParent.input[1], (GeoPoint) algoParent.input[2] }; points =
* t.transformPoints(points);
*
* AlgoConicPartCircumcircle algo = new AlgoConicPartCircumcircle( cons,
* transformedLabel, (GeoPoint) points[0], (GeoPoint) points[1], (GeoPoint)
* points[2], conic_part_type); GeoConicPart3D res = algo.getConicPart();
* res.setLabel(transformedLabel);
* res.setVisualStyleForTransformations(this); GeoElement[] geos = { res,
* (GeoElement) points[1], (GeoElement) points[2], (GeoElement) points[0] };
* return geos; } else if (algoParent instanceof
* AlgoConicPartConicParameters) { AlgoConicPartConicParameters algo =
* (AlgoConicPartConicParameters) algoParent;
*
* GeoConicND transformedConic = t .getTransformedConic(algo.conic);
* cons.removeFromConstructionList(transformedConic .getParentAlgorithm());
*
* algo = new AlgoConicPartConicParameters(cons, transformedLabel,
* transformedConic, algo.startParam, algo.endParam, conic_part_type);
* GeoElement ret = algo.getConicPart();
* ret.setVisualStyleForTransformations(this); GeoElement[] geos = { ret };
* return geos; } else if (algoParent instanceof AlgoConicPartConicPoints) {
* AlgoConicPartConicPoints algo = (AlgoConicPartConicPoints) algoParent;
* GeoPointND[] points = { algo.getStartPoint(), algo.getEndPoint() };
* points = t.transformPoints(points); GeoConicND orgConic =
* algo.getConic();
*
* GeoConicND transformedConic = t .getTransformedConic(orgConic);
* cons.removeFromConstructionList(transformedConic .getParentAlgorithm());
*
* algo = new AlgoConicPartConicPoints(cons, transformedLabel,
* transformedConic, (GeoPoint) points[0], (GeoPoint) points[1],
* conic_part_type); GeoConicPart3D conicPart = algo.getConicPart();
* conicPart.setVisualStyleForTransformations(this); GeoElement[] geos = {
* conicPart, (GeoPoint) points[0], (GeoPoint) points[1] }; return geos; }
* else if (algoParent instanceof AlgoSemicircle) { AlgoElement algo =
* algoParent; GeoPointND[] points = { ((AlgoSemicircle) algo).getA(),
* ((AlgoSemicircle) algo).getB() }; points = t.transformPoints(points);
*
* GeoConic semCirc; if (t instanceof TransformMirror &&
* t.changesOrientation()) { semCirc =
* kernel.getAlgoDispatcher().Semicircle(transformedLabel, (GeoPoint)
* points[1], (GeoPoint) points[0]); } else if (t.isSimilar()) { semCirc =
* kernel.getAlgoDispatcher().Semicircle(transformedLabel, (GeoPoint)
* points[0], (GeoPoint) points[1]); } else {
*
* GeoConic orgConic = ((AlgoSemicircle) algo).getConic(); GeoConicND
* transformedConic = t .getTransformedConic(orgConic);
* (cons).removeFromConstructionList(transformedConic
* .getParentAlgorithm()); if (t.changesOrientation()) { algo = new
* AlgoConicPartConicPoints(cons, transformedLabel, transformedConic,
* (GeoPoint) points[0], (GeoPoint) points[1], conic_part_type); } else algo
* = new AlgoConicPartConicPoints(cons, transformedLabel, transformedConic,
* (GeoPoint) points[1], (GeoPoint) points[0], conic_part_type);
* GeoConicPart3D conicPart = ((AlgoConicPartConicPoints) algo)
* .getConicPart(); conicPart.setVisualStyleForTransformations(this);
* GeoElement[] geos = { conicPart, (GeoElement) points[0], (GeoElement)
* points[1] }; return geos; }
* semCirc.setVisualStyleForTransformations(this); GeoElement[] geos = {
* semCirc, (GeoElement) points[0], (GeoElement) points[1] }; return geos; }
* else { // create CONIC GeoConicND transformedConic =
* t.getTransformedConic(this); transformedConic.setLabel(transformedLabel);
* GeoElement[] ret = { transformedConic }; return ret; } }
*/
@Override
final public GeoElement copy() {
return new GeoConicPart3D(this);
}
@Override
public boolean isInRegion(double x0, double y0) {
if (!super.isInRegion(x0, y0)) {
return false;
}
return parameters.isInRegion(x0, y0);
}
private Coords tmpCoords = new Coords(3);
private double[] tmpParameters = new double[2];
@Override
protected void moveBackToRegion(GeoPointND pi, RegionParameters rp) {
Coords coords = pi.getCoordsInD2(getCoordSys());
PathParameter pp = pi.getPathParameter();
// try to find the nearest point in the conic part
CoordNearest nearestPoint = new CoordNearest(coords);
// check points of the conic part
Coords midPoint = getMidpoint2D();
if (getConicPartType() == CONIC_PART_SECTOR) {
nearestPoint.check(midPoint);
}
Coords ev0 = new Coords(3);
ev0.set(getEigenvec(0));
Coords ev1 = new Coords(3);
ev1.set(getEigenvec(1));
Coords firstPoint = midPoint.copy()
.addInsideMul(ev0,
getHalfAxis(0) * Math.cos(parameters.paramStart))
.addInsideMul(ev1,
getHalfAxis(1) * Math.sin(parameters.paramStart));
nearestPoint.check(firstPoint);
Coords secondPoint = midPoint.copy()
.addInsideMul(ev0,
getHalfAxis(0) * Math.cos(parameters.paramEnd))
.addInsideMul(ev1,
getHalfAxis(1) * Math.sin(parameters.paramEnd));
nearestPoint.check(secondPoint);
// check project points on segments edges
if (getConicPartType() == CONIC_PART_SECTOR) {
coords.projectLineSub(midPoint, firstPoint, tmpCoords,
tmpParameters);
if (tmpParameters[0] > 0 && tmpParameters[0] < 1) {
// the
// projected
// point is
// on the
// segment
nearestPoint.check(tmpCoords);
}
coords.projectLineSub(midPoint, secondPoint, tmpCoords,
tmpParameters);
if (tmpParameters[0] > 0 && tmpParameters[0] < 1) {
// the
// projected
// point is
// on the
// segment
nearestPoint.check(tmpCoords);
}
} else {
coords.projectLineSub(firstPoint, secondPoint, tmpCoords,
tmpParameters);
if (tmpParameters[0] > 0 && tmpParameters[0] < 1) {
// the
// projected
// point is
// on the
// segment
nearestPoint.check(tmpCoords);
}
}
// may calc the nearest point of the global conic
if (!super.isInRegion(coords.getX(), coords.getY())) {
Coords pointConic = coords.copyVector();
pointChanged(pointConic, pp);
nearestPoint.check(pointConic);
rp.setIsOnPath(true);
}
// take nearest point above all
coords = nearestPoint.get();
pi.setCoords2D(coords.getX(), coords.getY(), 1);
pi.updateCoordsFrom2D(false, getCoordSys());
pi.updateCoords();
}
@Override
public void regionChanged(GeoPointND PI) {
// if kernel doesn't use path/region parameters, do as if point changed
// its coords
if (!getKernel().usePathAndRegionParameters(PI)
|| PI.getRegionParameters().isNaN()) {
pointChangedForRegion(PI);
return;
}
super.regionChanged(PI);
PI.updateCoords2D();
if (!isInRegion(PI)) {
pointChanged(PI);
}
}
@Override
public boolean hasDrawable3D() {
return true;
}
@Override
public boolean isAllEndpointsLabelsSet() {
AlgoElement algo = this.getParentAlgorithm();
if (algo == null) {
return false;
}
if (algo instanceof AlgoConicPartConicPoints) {
return ((AlgoConicPartConicPoints) algo).getStartPoint()
.isLabelSet()
&& ((AlgoConicPartConicPoints) algo).getEndPoint()
.isLabelSet();
}
if (algo instanceof AlgoConicPartCircumcircle) {
return algo.getInput()[0].isLabelSet()
&& algo.getInput()[1].isLabelSet()
&& algo.getInput()[2].isLabelSet();
}
return false;
}
/**
* @param param
* path parameter from 0 to 1
* @return point with this parameter
*/
public GeoPoint getPointParam(double param) {
GeoPoint ret = new GeoPoint(cons);
this.pathChanged(ret);
ret.getPathParameter().setT(param);
this.pathChanged(ret);
ret.updateCoords();
return ret;
}
/**
* super method
*
* @param P
* moved point
* @param pp
* path parameter
*/
@Override
public void superPointChanged(Coords P, PathParameter pp) {
super.pointChanged(P, pp, true);
}
@Override
public Coords getOrigin3D(int i) {
return getCoordSys().getPoint(lines[i].startPoint.inhomX,
lines[i].startPoint.inhomY);
}
@Override
public Coords getSegmentEnd3D() {
return getCoordSys().getPoint(lines[0].endPoint.inhomX,
lines[0].endPoint.inhomY);
}
@Override
public GeoElement[] createTransformedObject(Transform t,
String transformedLabel) {
// TODO: this way we discard the path limitation
return new GeoElement[] { t.getTransformedConic(this) };
}
@Override
public boolean isInverseFillable() {
return false;
}
}