package org.geogebra.common.geogebra3D.kernel3D.geos;
import java.util.TreeSet;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.PathNormalizer;
import org.geogebra.common.kernel.PathParameter;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.kernelND.GeoConicNDConstants;
import org.geogebra.common.kernel.kernelND.GeoConicSectionInterface;
import org.geogebra.common.plugin.GeoClass;
import org.geogebra.common.util.MyMath;
/**
* Partial conic for section of (limited) cylinders and cones
*
* @author mathieu
*
*/
public class GeoConicSection extends GeoConic3D
implements GeoConicSectionInterface {
private double[] paramStart, paramEnd, paramExtent;
private double[] edgeStartX, edgeStartY, edgeEndX, edgeEndY, edgeStartParam,
edgeEndParam;
private boolean[] edgeExists;
/**
* @param c
* construction
* @param isIntersection
* says if this is an intersection curve
*/
public GeoConicSection(Construction c, boolean isIntersection) {
super(c, isIntersection);
paramStart = new double[2];
paramEnd = new double[2];
paramExtent = new double[2];
edgeStartX = new double[2];
edgeStartY = new double[2];
edgeEndX = new double[2];
edgeEndY = new double[2];
edgeStartParam = new double[2];
edgeEndParam = new double[2];
edgeExists = new boolean[2];
}
private static class IndexedParameter
implements Comparable<IndexedParameter> {
protected double value;
protected int index;
public IndexedParameter(double value, int index) {
this.value = value;
this.index = index;
}
@Override
public int compareTo(IndexedParameter parameter) {
// NaN are the greatest
if (Double.isNaN(value)) {
return 1;
}
if (Double.isNaN(parameter.value)) {
return -1;
}
// compare values
if (Kernel.isGreater(parameter.value, value)) {
return -1;
}
if (Kernel.isGreater(value, parameter.value)) {
return 1;
}
// compare indices
if (index < parameter.index) {
return -1;
}
return 1; // never return 0 to ensure having four parameters
}
@Override
public boolean equals(Object o) {
if (o instanceof IndexedParameter) {
return compareTo((IndexedParameter) o) == 0;
}
return false;
}
@Override
public int hashCode() {
return index + 43 * Double.hashCode(value);
}
}
private TreeSet<IndexedParameter> parametersTree = new TreeSet<IndexedParameter>();
private IndexedParameter[] parametersArray = new IndexedParameter[4];
/**
* set parameters for "segments holes" regarding the index
*
* @param bottom0
* first parameter for bottom
* @param bottom1
* second parameter for bottom
* @param top0
* first parameter for top
* @param top1
* second parameter for top
*/
final public void setParameters(double bottom0, double bottom1, double top0,
double top1) {
// restart edges
for (int i = 0; i < 2; i++) {
edgeExists[i] = false;
}
// handle conic types
switch (type) {
default:
// do nothing
break;
case GeoConicNDConstants.CONIC_CIRCLE:
case GeoConicNDConstants.CONIC_ELLIPSE:
parametersTree.clear();
parametersTree.add(new IndexedParameter(bottom0, 1));
parametersTree.add(new IndexedParameter(bottom1, 1));
parametersTree.add(new IndexedParameter(top0, 2));
parametersTree.add(new IndexedParameter(top1, 2));
parametersTree.toArray(parametersArray);
double start1, end1, start2, end2;
if (parametersArray[0].index == parametersArray[1].index) {
start1 = parametersArray[0].value;
end1 = parametersArray[1].value;
start2 = parametersArray[2].value;
end2 = parametersArray[3].value;
} else {
start1 = parametersArray[1].value;
end1 = parametersArray[2].value;
start2 = parametersArray[3].value;
end2 = parametersArray[0].value;
}
// Log.debug(start1+","+end1+","+start2+","+end2);
// if no parameter for second hole (NaN), set second parameter to
// NaN
if (start2 == end2) {
start2 = start1;
end2 = Double.NaN;
start1 = Double.NaN;
} else if (start1 == end1) {
end1 = end2;
end2 = Double.NaN;
start1 = Double.NaN;
}
// set parameters
paramStart[0] = Kernel.convertToAngleValue(end1);
paramEnd[0] = Kernel.convertToAngleValue(start2);
paramExtent[0] = paramEnd[0] - paramStart[0];
if (paramExtent[0] < 0) {
paramExtent[0] += Kernel.PI_2;
}
paramStart[1] = Kernel.convertToAngleValue(end2);
paramEnd[1] = Kernel.convertToAngleValue(start1);
paramExtent[1] = paramEnd[1] - paramStart[1];
if (paramExtent[1] < 0) {
paramExtent[1] += Kernel.PI_2;
}
// set edges
if (!Double.isNaN(paramStart[0])) { // at least one edge
double x0 = getEigenvec(0).getX() * getHalfAxis(0);
double y0 = getEigenvec(0).getY() * getHalfAxis(0);
double x1 = getEigenvec(1).getX() * getHalfAxis(1);
double y1 = getEigenvec(1).getY() * getHalfAxis(1);
if (Double.isNaN(paramStart[1])) { // only one edge
edgeEndX[0] = b.getX() + x0 * Math.cos(paramStart[0])
+ x1 * Math.sin(paramStart[0]);
edgeEndY[0] = b.getY() + y0 * Math.cos(paramStart[0])
+ y1 * Math.sin(paramStart[0]);
edgeEndParam[0] = paramStart[0];
edgeStartX[0] = b.getX() + x0 * Math.cos(paramEnd[0])
+ x1 * Math.sin(paramEnd[0]);
edgeStartY[0] = b.getY() + y0 * Math.cos(paramEnd[0])
+ y1 * Math.sin(paramEnd[0]);
edgeStartParam[0] = paramEnd[0];
if (edgeStartParam[0] > edgeEndParam[0]) {
edgeStartParam[0] -= 2 * Math.PI;
}
edgeExists[0] = true;
} else {
edgeEndX[0] = b.getX() + x0 * Math.cos(paramStart[0])
+ x1 * Math.sin(paramStart[0]);
edgeEndY[0] = b.getY() + y0 * Math.cos(paramStart[0])
+ y1 * Math.sin(paramStart[0]);
edgeEndParam[0] = paramStart[0];
edgeStartX[0] = b.getX() + x0 * Math.cos(paramEnd[1])
+ x1 * Math.sin(paramEnd[1]);
edgeStartY[0] = b.getY() + y0 * Math.cos(paramEnd[1])
+ y1 * Math.sin(paramEnd[1]);
edgeStartParam[0] = paramEnd[1];
if (edgeStartParam[0] > edgeEndParam[0]) {
edgeStartParam[0] -= 2 * Math.PI;
}
edgeExists[0] = true;
edgeEndX[1] = b.getX() + x0 * Math.cos(paramStart[1])
+ x1 * Math.sin(paramStart[1]);
edgeEndY[1] = b.getY() + y0 * Math.cos(paramStart[1])
+ y1 * Math.sin(paramStart[1]);
edgeEndParam[1] = paramStart[1];
edgeStartX[1] = b.getX() + x0 * Math.cos(paramEnd[0])
+ x1 * Math.sin(paramEnd[0]);
edgeStartY[1] = b.getY() + y0 * Math.cos(paramEnd[0])
+ y1 * Math.sin(paramEnd[0]);
edgeStartParam[1] = paramEnd[0];
if (edgeStartParam[1] > edgeEndParam[1]) {
edgeStartParam[1] -= 2 * Math.PI;
}
edgeExists[1] = true;
}
}
break;
case CONIC_INTERSECTING_LINES:
case CONIC_PARALLEL_LINES:
if (bottom0 < bottom1) {
start1 = bottom0;
start2 = bottom1;
} else {
start1 = bottom1;
start2 = bottom0;
}
if (top0 < top1) {
end1 = top0;
end2 = top1;
} else {
end1 = top1;
end2 = top0;
}
paramStart[0] = PathNormalizer.infFunction(start1);
paramEnd[0] = PathNormalizer.infFunction(end1);
paramStart[1] = PathNormalizer.infFunction(start2 - 2);
paramEnd[1] = PathNormalizer.infFunction(end2 - 2);
break;
case CONIC_DOUBLE_LINE:
paramStart[0] = bottom0;
paramEnd[0] = top0;
break;
case CONIC_HYPERBOLA:
paramStart[0] = Double.NaN;
paramEnd[0] = Double.NaN;
paramStart[1] = Double.NaN;
paramEnd[1] = Double.NaN;
setInfParameter(paramStart, bottom0);
setInfParameter(paramStart, top0);
setInfParameter(paramEnd, bottom1);
setInfParameter(paramEnd, top1);
sortParameters();
// set edges
for (int i = 0; i < 2; i++) {
if (!Double.isNaN(paramStart[i])) {
double s = paramEnd[i];
double x = (1 - 2 * i) * halfAxes[0] * MyMath.cosh(s);
double y = halfAxes[1] * MyMath.sinh(s);
double x1 = b.getX() + x * getEigenvec(0).getX()
+ y * getEigenvec(1).getX();
double y1 = b.getY() + x * getEigenvec(0).getY()
+ y * getEigenvec(1).getY();
s = paramStart[i];
x = (1 - 2 * i) * halfAxes[0] * MyMath.cosh(s);
y = halfAxes[1] * MyMath.sinh(s);
double x2 = b.getX() + x * getEigenvec(0).getX()
+ y * getEigenvec(1).getX();
double y2 = b.getY() + x * getEigenvec(0).getY()
+ y * getEigenvec(1).getY();
if (i == 0) {
edgeStartX[i] = x2;
edgeStartY[i] = y2;
edgeStartParam[i] = PathNormalizer
.inverseInfFunction(paramStart[i]);
edgeEndX[i] = x1;
edgeEndY[i] = y1;
edgeEndParam[i] = PathNormalizer
.inverseInfFunction(paramEnd[i]);
} else {
edgeStartX[i] = x1;
edgeStartY[i] = y1;
edgeStartParam[i] = PathNormalizer
.inverseInfFunction(paramEnd[i]) + 2;
edgeEndX[i] = x2;
edgeEndY[i] = y2;
edgeEndParam[i] = PathNormalizer
.inverseInfFunction(paramStart[i]) + 2;
}
edgeExists[i] = true;
} else { // prevent second branch
double x1 = b.getX() - getEigenvec(1).getX();
double y1 = b.getY() - getEigenvec(1).getY();
double x2 = b.getX() + getEigenvec(1).getX();
double y2 = b.getY() + getEigenvec(1).getY();
if (i == 0) {
edgeStartX[i] = x1;
edgeStartY[i] = y1;
edgeEndX[i] = x2;
edgeEndY[i] = y2;
} else {
edgeStartX[i] = x2;
edgeStartY[i] = y2;
edgeEndX[i] = x1;
edgeEndY[i] = y1;
}
}
}
break;
case CONIC_PARABOLA:
if (bottom0 < bottom1) {
paramStart[0] = bottom0;
paramEnd[0] = bottom1;
} else {
paramStart[0] = bottom1;
paramEnd[0] = bottom0;
}
// set edges
double y = bottom0 * p;
double x = y * bottom0 / 2.0;
edgeEndX[0] = b.getX() + x * getEigenvec(0).getX()
+ y * getEigenvec(1).getX();
edgeEndY[0] = b.getY() + x * getEigenvec(0).getY()
+ y * getEigenvec(1).getY();
edgeEndParam[0] = bottom0;
y = bottom1 * p;
x = y * bottom1 / 2.0;
edgeStartX[0] = b.getX() + x * getEigenvec(0).getX()
+ y * getEigenvec(1).getX();
edgeStartY[0] = b.getY() + x * getEigenvec(0).getY()
+ y * getEigenvec(1).getY();
edgeStartParam[0] = bottom1;
edgeExists[0] = true;
break;
}
// Log.debug(getType()+":"+paramStart[0]+","+paramEnd[0]+","+paramStart[1]+","+paramEnd[1]);
}
private void sortParameters() {
for (int i = 0; i < 2; i++) {
if (Kernel.isZero(paramStart[i])) {
paramStart[i] = 0;
}
if (Kernel.isZero(paramEnd[i])) {
paramEnd[i] = 0;
}
// if (Math.abs(paramStart[i])>Math.abs(paramEnd[i])){
if (paramStart[i] > paramEnd[i]) {
double tmp = paramStart[i];
paramStart[i] = paramEnd[i];
paramEnd[i] = tmp;
}
}
}
/**
* set the value to the correct branch, converted from [-1,1] (or [1,3]) to
* -inf, +inf
*
* @param param
* @param value
*/
private static void setInfParameter(double[] param, double value) {
if (Double.isNaN(value)) {
return;
}
if (value < 1) {
param[0] = PathNormalizer.infFunction(value);
} else {
param[1] = PathNormalizer.infFunction(value - 2);
}
}
/**
* @param index
* index of the hole
* @return start parameter
*/
@Override
final public double getParameterStart(int index) {
return paramStart[index];
}
/**
* @param index
* index of the hole
* @return end parameter
*/
@Override
final public double getParameterEnd(int index) {
return paramEnd[index];
}
/**
* @param index
* index of the hole
* @return end parameter - start parameter
*/
@Override
final public double getParameterExtent(int index) {
return paramExtent[index];
}
@Override
public GeoClass getGeoClassType() {
return GeoClass.CONICSECTION;
}
@Override
public boolean isInRegion(double x0, double y0) {
if (!super.isInRegion(x0, y0)) {
return false;
}
return isInsideEdges(x0, y0);
}
private boolean isInsideEdges(double x0, double y0) {
for (int i = 0; i < 2; i++) {
if (edgeExists[i] || type == GeoConicNDConstants.CONIC_HYPERBOLA) {
if ((edgeStartX[i] - x0) * (edgeEndY[i] - y0)
- (edgeEndX[i] - x0) * (edgeStartY[i] - y0) < 0) {
return false;
}
}
}
return true;
}
@Override
public void pointChanged(Coords P, PathParameter pp, boolean checkSection) {
if (checkSection) {
double xOld = P.getX() / P.getZ();
double yOld = P.getY() / P.getZ();
double distance = Double.POSITIVE_INFINITY;
// calc point on conic and check it
super.pointChanged(P, pp, checkSection);
if (type == GeoConicNDConstants.CONIC_HYPERBOLA) {
if (edgeExists[0]) {
if (pp.t > 1) { // wrong branch: force correct branch apex
pp.t = 0;
P.setX(getHalfAxis(0));
P.setY(0);
P.setZ(1.0);
coordsEVtoRW(P);
}
} else {
if (pp.t < 1) { // wrong branch: force correct branch apex
pp.t = 2;
P.setX(-getHalfAxis(0));
P.setY(0);
P.setZ(1.0);
coordsEVtoRW(P);
}
}
}
P.setInhomCoords();
if (isInsideEdges(P.getX(), P.getY())) {
double dx = P.getX() - xOld;
double dy = P.getY() - yOld;
distance = dx * dx + dy * dy;
}
// calc points on edges
for (int i = 0; i < 2; i++) {
if (edgeExists[i]) {
double parameter = getParameterOnSegment(xOld, yOld,
edgeStartX[i], edgeStartY[i], edgeEndX[i],
edgeEndY[i]);
double x = edgeStartX[i] * (1 - parameter)
+ edgeEndX[i] * parameter;
double y = edgeStartY[i] * (1 - parameter)
+ edgeEndY[i] * parameter;
double dx = x - xOld;
double dy = y - yOld;
double d = dx * dx + dy * dy;
if (d < distance) {
distance = d;
P.setX(x);
P.setY(y);
P.setZ(1);
switch (type) {
default:
pp.t = Double.NaN;
break;
case GeoConicNDConstants.CONIC_CIRCLE:
case GeoConicNDConstants.CONIC_ELLIPSE:
// we map the [0,1] parameter to edge parameters
pp.t = edgeStartParam[i] * (1 - parameter)
+ edgeEndParam[i] * parameter;
if (pp.t > Math.PI) {
pp.t -= Kernel.PI_2;
}
break;
case GeoConicNDConstants.CONIC_PARABOLA:
// we add edge parameter to start parameter
if (edgeStartParam[0] < edgeEndParam[0]) {
parameter = -parameter;
}
pp.t = edgeStartParam[0] + parameter;
break;
case GeoConicNDConstants.CONIC_HYPERBOLA:
pp.t = edgeEndParam[i] * parameter
+ (1 - parameter);
break;
}
}
}
}
} else {
// calc point on conic and check it
super.pointChanged(P, pp, checkSection);
}
}
private static double getParameterOnSegment(double x, double y,
double startX, double startY, double endX, double endY) {
double dx = endX - startX;
double dy = endY - startY;
double parameter = ((x - startX) * (endX - startX)
+ (y - startY) * (endY - startY)) / (dx * dx + dy * dy);
if (parameter < 0) {
return 0;
}
if (parameter > 1) {
return 1;
}
return parameter;
}
@Override
protected void pathChangedWithoutCheckEllipse(Coords P, PathParameter pp,
boolean checkSection) {
if (checkSection) {
for (int i = 0; i < 2; i++) {
if (edgeExists[i]) {
// get parameter in [-pi,pi]
double parameter = pp.t % Kernel.PI_2;
if (parameter > Math.PI) {
parameter -= Kernel.PI_2;
}
// check if in edge
boolean inEdge = false;
if (edgeStartParam[i] > Math.PI) {
parameter += Kernel.PI_2;
inEdge = parameter >= edgeStartParam[i]
&& parameter <= edgeEndParam[i];
} else if (edgeEndParam[i] > Math.PI) {
if (parameter >= edgeStartParam[i]) {
inEdge = true;
} else {
parameter += Kernel.PI_2;
inEdge = parameter <= edgeEndParam[i];
}
} else {
inEdge = parameter >= edgeStartParam[i]
&& parameter <= edgeEndParam[i];
}
if (inEdge) {
double a = (parameter - edgeStartParam[i])
/ (edgeEndParam[i] - edgeStartParam[i]);
P.setX(edgeStartX[i] * (1 - a) + edgeEndX[i] * a);
P.setY(edgeStartY[i] * (1 - a) + edgeEndY[i] * a);
P.setZ(1);
return;
}
}
}
}
super.pathChangedWithoutCheckEllipse(P, pp, checkSection);
}
@Override
protected void pathChangedWithoutCheckParabola(Coords P, PathParameter pp,
boolean checkSection) {
if (checkSection) {
if (edgeExists[0]) {
if (edgeStartParam[0] < edgeEndParam[0]) {
if (pp.t < edgeStartParam[0]) {
double a = -pp.t + edgeStartParam[0];
if (a < 1) {
P.setX(edgeStartX[0] * (1 - a) + edgeEndX[0] * a);
P.setY(edgeStartY[0] * (1 - a) + edgeEndY[0] * a);
} else { // prevent outside of edge when path changes
P.setX(edgeEndX[0]);
P.setY(edgeEndY[0]);
}
P.setZ(1);
return;
} else if (pp.t > edgeEndParam[0]) {
P.setX(edgeEndX[0]);
P.setY(edgeEndY[0]);
P.setZ(1);
return;
}
} else {
if (pp.t > edgeStartParam[0]) {
double a = pp.t - edgeStartParam[0];
if (a < 1) {
P.setX(edgeStartX[0] * (1 - a) + edgeEndX[0] * a);
P.setY(edgeStartY[0] * (1 - a) + edgeEndY[0] * a);
} else { // prevent outside of edge when path changes
P.setX(edgeEndX[0]);
P.setY(edgeEndY[0]);
}
P.setZ(1);
return;
} else if (pp.t < edgeEndParam[0]) {
P.setX(edgeEndX[0]);
P.setY(edgeEndY[0]);
P.setZ(1);
return;
}
}
}
}
super.pathChangedWithoutCheckParabola(P, pp, checkSection);
}
@Override
protected void pathChangedWithoutCheckHyperbola(Coords P, PathParameter pp,
boolean checkSection) {
double oldParameter = pp.t;
if (checkSection) {
// reverse branch if needed
int i;
if (pp.t < 1) {
if (edgeExists[0]) {
i = 0;
} else {
i = 1;
pp.t = 2 - pp.t;
}
} else {
if (edgeExists[1]) {
i = 1;
} else {
i = 0;
pp.t = 2 - pp.t;
}
}
if (i == 0 ^ pp.t < edgeEndParam[i]) {
double a = (pp.t - edgeEndParam[i]) / (1 - edgeEndParam[i]); // pp.t
// is
// from
// edgeEndParam[i]
// to
// 1
if (a < 1) {
P.setX(edgeStartX[i] * a + edgeEndX[i] * (1 - a));
P.setY(edgeStartY[i] * a + edgeEndY[i] * (1 - a));
} else { // prevent outside of edge when path changes
P.setX(edgeEndX[i]);
P.setY(edgeEndY[i]);
}
P.setZ(1);
return;
}
}
super.pathChangedWithoutCheckHyperbola(P, pp, checkSection);
if (checkSection) {
pp.t = oldParameter;
}
}
}