package org.geogebra.common.kernel.kernelND;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.PathParameter;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.integration.EllipticArcLength;
/**
* Parameters for 2D/3D GeoConicPart
*
* @author mathieu
*
*/
public class GeoConicPartParameters {
private GeoConicND conic;
/** start param */
public double paramStart;
/** end param */
public double paramEnd;
/** end - start */
public double paramExtent;
/** orientation */
public boolean posOrientation = true;
/** sector or arc */
public int conicPartType;
private double area, arcLength;
/** value */
public double value;
private boolean valueDefined;
private EllipticArcLength ellipticArcLength;
/** whether intersection outside of arc is allowed */
public boolean allowOutlyingIntersections = false;
/** whether to transform to arc or circle */
public boolean keepTypeOnGeometricTransform = true;
/**
* @param conic
* conic
* @param type
* arc type
*/
public GeoConicPartParameters(GeoConicND conic, int type) {
this.conic = conic;
conicPartType = type;
}
/**
* @param cp
* source parameters
*/
public void set(GeoConicPartParameters cp) {
paramStart = cp.paramStart;
paramEnd = cp.paramEnd;
paramExtent = cp.paramExtent;
posOrientation = cp.posOrientation;
conicPartType = cp.conicPartType;
value = cp.value;
area = cp.area;
setValueDefined(cp.isValueDefined());
keepTypeOnGeometricTransform = cp.keepTypeOnGeometricTransform;
}
/**
* @param other
* other parameters
* @return whether this and other describe same range of same conic type
*/
final public boolean isEqual(GeoConicPartParameters other) {
return posOrientation == other.posOrientation
&& conicPartType == other.conicPartType
&& Kernel.isEqual(paramStart, other.paramStart)
&& Kernel.isEqual(paramEnd, other.paramEnd);
}
/**
* @param isDefined
* whether value is defined
* @param start
* start param
* @param end
* end param
* @param positiveOrientation
* orientation
*/
final public void setParameters(boolean isDefined, double start, double end,
boolean positiveOrientation) {
double startParam = start;
double endParam = end;
setValueDefined(isDefined);
if (!isValueDefined()) {
value = Double.NaN;
return;
}
posOrientation = positiveOrientation;
if (!posOrientation) {
// internally we always use positive orientation, i.e. a <= b
// the orientation flag is important for points on this path (see
// pathChanged())
double tmp = startParam;
startParam = endParam;
endParam = tmp;
}
// handle conic types
switch (conic.getType()) {
case GeoConicNDConstants.CONIC_CIRCLE:
setExtentEllipse(startParam, endParam);
double r = conic.getHalfAxis(0);
arcLength = r * paramExtent;
if (conicPartType == GeoConicNDConstants.CONIC_PART_ARC) {
value = arcLength;
// area arc = area sector - area triangle
area = r * r * (paramExtent - Math.sin(paramExtent)) / 2.0;
} else {
value = r * r * paramExtent / 2.0; // area
area = value; // area
}
setValueDefined(!Double.isNaN(value) && !Double.isInfinite(value));
break;
case GeoConicNDConstants.CONIC_ELLIPSE:
setExtentEllipse(startParam, endParam);
if (ellipticArcLength == null) {
ellipticArcLength = new EllipticArcLength(conic);
}
if (conicPartType == GeoConicNDConstants.CONIC_PART_ARC) {
// length
value = ellipticArcLength.compute(paramStart, paramEnd);
} else {
// area
value = conic.getHalfAxis(0) * conic.getHalfAxis(1)
* paramExtent / 2.0;
}
setValueDefined(!Double.isNaN(value) && !Double.isInfinite(value));
break;
// a circular arc through 3 points may degenerate
// to a segment or two rays
case GeoConicNDConstants.CONIC_LINE:
case GeoConicNDConstants.CONIC_PARALLEL_LINES:
if (conicPartType == GeoConicNDConstants.CONIC_PART_ARC
&& posOrientation) {
// length of segment
// bugfix Michael Borcherds 2008-05-27
GeoPoint startPoint = conic.lines[0].getStartPoint();
GeoPoint endPoint = conic.lines[0].getEndPoint();
if (startPoint != null && endPoint != null) {
value = startPoint.distance(endPoint);
} else {
value = Double.POSITIVE_INFINITY;
setValueDefined(false);
break;
}
// bugfix end
} else { // sector or two rays
value = Double.POSITIVE_INFINITY; // area or length of rays
}
setValueDefined(true);
break;
default:
setValueDefined(false);
// Application.debug("GeoConicPart: unsupported conic part for conic
// type: "
// + type);
}
}
private void setExtentEllipse(double startParam, double endParam) {
paramStart = Kernel.convertToAngleValue(startParam);
paramEnd = Kernel.convertToAngleValue(endParam);
paramExtent = paramEnd - paramStart;
if (paramExtent < 0) {
paramExtent += Kernel.PI_2;
}
}
/**
* Returns arc length / area as appropriate
*
* @return arc length / area as appropriate
*/
final public double getValue() {
if (!isValueDefined()) {
return Double.NaN;
}
return value;
}
/**
* Returns arc length
*
* @return arc length
*/
final public double getArcLength() {
if (!isValueDefined()) {
return Double.NaN;
}
return arcLength;
}
/**
* Returns the area
*
* @return area
*/
final public double getArea() {
if (!isValueDefined()) {
return Double.NaN;
}
return area;
}
/**
* Set path parameter for point on ellipse
*
* @param P
* point
* @param pp
* path parameter
*/
public void setEllipseParameter(Coords P, PathParameter pp) {
// let GeoConic do the work
((GeoConicPartND) conic).superPointChanged(P, pp);
// now transform parameter t from [paramStart, paramEnd] to [0, 1]
if (pp.t < 0) {
pp.t += Kernel.PI_2;
}
double t = pp.t - paramStart;
if (t < 0) {
t += Kernel.PI_2;
}
pp.t = t / paramExtent;
}
/**
* Clip path parameter for point on ellipse
*
* @param P
* point
* @param pp
* path parameter
*/
public void clipEllipseParameter(Coords P, PathParameter pp) {
// make sure we don't get outside [0,1]
// the values of the path parameter are now
// between [0, 2pi/paramExtent]
// [0, 1] is ok.
// handle [1, 2pi/paramExtent]:
// take 0 for parameter > (1 + 2pi/paramExtent)/2
// else take 1
if (pp.t > 0.5 + Math.PI / paramExtent) {
if (posOrientation) {
pp.t = 0;
} else {
pp.t = 1;
}
conic.pathChanged(P, pp);
} else if (pp.t > 1) {
if (posOrientation) {
pp.t = 1;
} else {
pp.t = 0;
}
conic.pathChanged(P, pp);
} else if (!posOrientation) {
pp.t = 1.0 - pp.t;
}
}
/**
* Append arc settings
*
* @param sb
* builder
*/
public void getXMLtags(StringBuilder sb) {
// allowOutlyingIntersections
sb.append("\t<outlyingIntersections val=\"");
sb.append(allowOutlyingIntersections);
sb.append("\"/>\n");
// keepTypeOnGeometricTransform
sb.append("\t<keepTypeOnTransform val=\"");
sb.append(keepTypeOnGeometricTransform);
sb.append("\"/>\n");
}
/**
* @param x0
* x-ccord
* @param y0
* y-xoord
* @return whether (x,y) is in region
*/
public boolean isInRegion(double x0, double y0) {
// for sector, check if (x0,y0) is on the arc outline
if (conicPartType == GeoConicNDConstants.CONIC_PART_SECTOR) {
double arg = computeArg(x0, y0);
if (arg < 0)
{
arg += Kernel.PI_2;
// Application.debug(arg+" <? "+paramExtent);
}
return ((arg >= -Kernel.STANDARD_PRECISION)
&& (arg <= paramExtent + Kernel.STANDARD_PRECISION));
}
// for arc, check if is inside the arc : cross product with limit
// Application.debug(posOrientation);
Coords midPoint = conic.getMidpoint2D();
double r = conic.getHalfAxis(1);
double ev0x = conic.getEigenvec(0).getX();
double ev0y = conic.getEigenvec(0).getY();
double ev1x = conic.getEigenvec(1).getX();
double ev1y = conic.getEigenvec(1).getY();
double firstVecX = ev0x * Math.cos(paramStart)
+ ev1x * Math.sin(paramStart);
double firstVecY = ev0y * Math.cos(paramStart)
+ ev1y * Math.sin(paramStart);
double secondVecX = ev0x * Math.cos(paramEnd)
+ ev1x * Math.sin(paramEnd);
double secondVecY = ev0y * Math.cos(paramEnd)
+ ev1y * Math.sin(paramEnd);
double vx = (x0 - midPoint.getX()) / r - firstVecX;
double vy = (y0 - midPoint.getY()) / r - firstVecY;
double lx = secondVecX - firstVecX;
double ly = secondVecY - firstVecY;
return Kernel.isGreaterEqual(vx * ly - vy * lx, 0);
}
private double computeArg(double x0, double y0) {
Coords b = conic.getMidpoint2D();
double px = x0 - b.getX();
double py = y0 - b.getY();
// rotate by -alpha
double px2 = px * conic.getEigenvec(0).getX()
+ py * conic.getEigenvec(0).getY();
py = px * conic.getEigenvec(1).getX()
+ py * conic.getEigenvec(1).getY();
// calc parameter
// relation between the internal parameter t and the angle theta:
// t = atan(a/b tan(theta)) where tan(theta) = py / px
double arg = Math.atan2(conic.getHalfAxis(0) * py,
conic.getHalfAxis(1) * px2);
if (arg < 0) {
arg += Kernel.PI_2;
}
return arg - paramStart;
}
/**
*
* @param P
* coords in conic coord sys
* @return true if coords are on path
*/
public boolean isOnPath(Coords P) {
PathParameter pp = new PathParameter();
pp.setPathType(conic.type);
switch (conic.type) {
case GeoConicNDConstants.CONIC_CIRCLE:
case GeoConicNDConstants.CONIC_ELLIPSE:
setEllipseParameter(P, pp);
return pp.t >= 0 && pp.t <= 1;
// degenerate case: two rays or one segment
case GeoConicNDConstants.CONIC_PARALLEL_LINES:
if (posOrientation) {
// segment
conic.lines[0].doPointChanged(P, pp);
// make sure we don't get outside [0,1]
if (pp.t < 0) {
return false;
}
if (pp.t > 1) {
return false;
}
return true;
}
// two rays
return true;
}
return false;
}
/**
* @return whether value is defined
*/
public boolean isValueDefined() {
return valueDefined;
}
/**
* @param valueDefined
* whether value is considered defined
*/
public void setValueDefined(boolean valueDefined) {
this.valueDefined = valueDefined;
}
}