package org.geogebra.common.euclidian.draw;
import org.geogebra.common.awt.GAffineTransform;
import org.geogebra.common.awt.GArc2D;
import org.geogebra.common.awt.GGeneralPath;
import org.geogebra.common.awt.GGraphics2D;
import org.geogebra.common.awt.GLine2D;
import org.geogebra.common.awt.GRectangle;
import org.geogebra.common.awt.GShape;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.euclidian.GeneralPathClipped;
import org.geogebra.common.euclidian.clipping.ClipShape;
import org.geogebra.common.factories.AwtFactory;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.kernelND.GeoConicND;
import org.geogebra.common.kernel.kernelND.GeoConicSectionInterface;
/**
* Class for drawing a conic section (limited quadric and plane)
*
* @author mathieu
*
*/
public class DrawConicSection extends DrawConic {
private GArc2D arc;
private GLine2D line;
private GLine2D[] lines;
private GeneralPathClipped hyp;
/**
* constructor
*
* @param view
* view
* @param c
* conic
*/
public DrawConicSection(EuclidianView view, GeoConicND c) {
super(view, c, false);
}
/**
*
* @param i
* index
* @return i-th start parameter for the section
*/
protected double getStart(int i) {
return ((GeoConicSectionInterface) getGeoElement())
.getParameterStart(i);
}
/**
*
* @param i
* index
* @return i-th extent parameter for the section
*/
protected double getExtent(int i) {
return ((GeoConicSectionInterface) getGeoElement())
.getParameterExtent(i);
}
/**
*
* @param i
* index
* @return i-th end parameter for the section
*/
protected double getEnd(int i) {
return ((GeoConicSectionInterface) getGeoElement()).getParameterEnd(i);
}
/**
*
* @param m
* midpoint
* @param ev0
* first eigen vec
* @param ev1
* second eigen vec
* @param r0
* first half axis
* @param r1
* second half axis
* @param parameter
* angle parameter
* @return ellipse point
*/
public static final Coords ellipsePoint(Coords m, Coords ev0, Coords ev1,
double r0, double r1, double parameter) {
return m.copy().addInsideMul(ev0, r0 * Math.cos(parameter))
.addInsideMul(ev1, r1 * Math.sin(parameter));
}
/**
* draw an edge of the ellipse (if not all in the view)
*/
private void updateEllipseEdge() {
Coords m = conic.getMidpoint3D();
Coords ev0 = conic.getEigenvec3D(0);
Coords ev1 = conic.getEigenvec3D(1);
double r0 = conic.getHalfAxis(0);
double r1 = conic.getHalfAxis(1);
double start0 = getStart(0);
double end0 = getEnd(0);
double start1 = getStart(1);
double end1 = getEnd(1);
Coords A, B;
if (!Double.isNaN(start1)) { // there is two segments
// try first segment
A = view.getCoordsForView(ellipsePoint(m, ev0, ev1, r0, r1, end0));
if (Kernel.isZero(A.getZ())) {
B = view.getCoordsForView(
ellipsePoint(m, ev0, ev1, r0, r1, start1));
} else { // try second segment
A = view.getCoordsForView(
ellipsePoint(m, ev0, ev1, r0, r1, end1));
B = view.getCoordsForView(
ellipsePoint(m, ev0, ev1, r0, r1, start0));
}
} else { // only one segment
A = view.getCoordsForView(ellipsePoint(m, ev0, ev1, r0, r1, end0));
B = view.getCoordsForView(
ellipsePoint(m, ev0, ev1, r0, r1, start0));
}
if (Kernel.isZero(B.getZ())) {
if (line == null) {
line = AwtFactory.getPrototype().newLine2D();
}
line.setLine(A.getX(), A.getY(), B.getX(), B.getY());
} else {
isVisible = false;
return;
}
// transform to screen coords
transform.setTransform(view.getCoordTransform());
shape = transform.createTransformedShape(line);
}
@Override
protected void updateCircle() {
onlyEdge = false;
super.updateCircle();
}
@Override
protected void updateHyperbola() {
onlyEdge = false;
super.updateHyperbola();
}
@Override
protected void updateParabola() {
onlyEdge = false;
super.updateParabola();
}
@Override
protected boolean checkIsOnFilling() {
return super.checkIsOnFilling() && !onlyEdge;
}
@Override
public boolean hitEllipse(int hitX, int hitY, int hitThreshold) {
if (onlyEdge) {
return shape.intersects(hitX - hitThreshold, hitY - hitThreshold,
2 * hitThreshold, 2 * hitThreshold);
}
return super.hitEllipse(hitX, hitY, hitThreshold);
}
private boolean onlyEdge;
@Override
protected void updateEllipse() {
onlyEdge = false;
Double start0 = getStart(0);
// if no hole, just draw an ellipse
if (Double.isNaN(start0)) {
super.updateEllipse();
return;
}
// check if in view
Coords M = view.getCoordsForView(conic.getMidpoint3D());
if (!Kernel.isZero(M.getZ())) {// check if in view
updateEllipseEdge();
onlyEdge = true;
return;
}
if (ev == null) {
ev = new Coords[2];
}
for (int j = 0; j < 2; j++) {
ev[j] = view.getCoordsForView(conic.getEigenvec3D(j));
if (!Kernel.isZero(ev[j].getZ())) {// check if in view
updateEllipseEdge();
onlyEdge = true;
return;
}
}
// check for huge pixel radius
double xradius = halfAxes[0] * view.getXscale();
double yradius = halfAxes[1] * view.getYscale();
/*
* if (xradius > DrawConic.HUGE_RADIUS || yradius >
* DrawConic.HUGE_RADIUS) { isVisible = false; return; }
*/
// use shape
GShape arcs;
// set arc
if (arc == null) {
arc = AwtFactory.getPrototype().newArc2D();
}
Double extent0 = getExtent(0);
Double start1 = getStart(1);
// set the arc type : if one hole, add chord to close the arc, if two
// holes, let arcs open
int type;
if (Double.isNaN(start1)) {
type = GArc2D.CHORD;
} else {
type = GArc2D.OPEN;
}
arc.setArc(-halfAxes[0], -halfAxes[1], 2 * halfAxes[0], 2 * halfAxes[1],
-Math.toDegrees(start0), -Math.toDegrees(extent0), type);
// if no second hole, just draw one arc
if (Double.isNaN(start1)) {
arcs = arc;
} else {
arcs = AwtFactory.getPrototype().newGeneralPath();
((GGeneralPath) arcs).append(arc, true);
// second arc
Double extent1 = getExtent(1);
arc.setArc(-halfAxes[0], -halfAxes[1], 2 * halfAxes[0],
2 * halfAxes[1], -Math.toDegrees(start1),
-Math.toDegrees(extent1), GArc2D.OPEN);
((GGeneralPath) arcs).append(arc, true);
((GGeneralPath) arcs).closePath();
}
// transform to screen coords
transform.setTransform(view.getCoordTransform());
transform.concatenate(view.getCompanion().getTransform(conic, M, ev));
// BIG RADIUS: larger than screen diagonal
int BIG_RADIUS = view.getWidth() + view.getHeight(); // > view's
// diagonal
if (xradius < BIG_RADIUS && yradius < BIG_RADIUS) {
shape = transform.createTransformedShape(arcs);
} else {
// clip big arc at screen
shape = ClipShape.clipToRect(arcs, transform,
AwtFactory.getPrototype().newRectangle(-1, -1,
view.getWidth() + 2, view.getHeight() + 2));
}
// set label coords
labelCoords[0] = halfAxes[0] * Math.cos(start0);
labelCoords[1] = halfAxes[1] * Math.sin(start0);
transform.transform(labelCoords, 0, labelCoords, 0, 1);
xLabel = (int) labelCoords[0];
yLabel = (int) labelCoords[1];
}
private Coords[] endPoints;
@Override
protected void updateLines() {
if (endPoints == null) {
endPoints = new Coords[4];
}
Coords m = conic.getOrigin3D(0);
Coords d = conic.getDirection3D(0);
endPoints[0] = view
.getCoordsForView(m.copy().addInsideMul(d, getStart(0)));
endPoints[1] = view
.getCoordsForView(m.copy().addInsideMul(d, getEnd(0)));
m = conic.getOrigin3D(1);
d = conic.getDirection3D(1);
endPoints[3] = view
.getCoordsForView(m.copy().addInsideMul(d, getStart(1)));
endPoints[2] = view
.getCoordsForView(m.copy().addInsideMul(d, getEnd(1)));
GGeneralPath path = AwtFactory.getPrototype().newGeneralPath();
int numPoints = -1;
int tx0 = 0, ty0 = 0, x1 = 0, y1 = 0, x2, y2;
double x, y;
for (int i = 0; i < 4; i++) {
if (Kernel.isZero(endPoints[i].getZ())) {
if (numPoints == -1) { // first point
x = endPoints[i].getX();
y = endPoints[i].getY();
path.moveTo(x, y);
numPoints++;
tx0 = view.toScreenCoordX(x);
ty0 = view.toScreenCoordY(y);
x1 = tx0;
y1 = ty0;
} else {
x = endPoints[i].getX();
y = endPoints[i].getY();
path.lineTo(x, y);
x2 = view.toScreenCoordX(x);
y2 = view.toScreenCoordY(y);
if (lines == null) {
lines = new GLine2D[4];
}
if (lines[numPoints] == null) {
lines[numPoints] = AwtFactory.getPrototype()
.newLine2D();
}
lines[numPoints].setLine(x1, y1, x2, y2);
x1 = x2;
y1 = y2;
numPoints++;
}
}
}
if (numPoints > 0) {// close path only if at least two points
path.closePath();
if (lines[numPoints] == null) {
lines[numPoints] = AwtFactory.getPrototype().newLine2D();
}
lines[numPoints].setLine(x1, y1, tx0, ty0);
}
// transform to screen coords
transform.setTransform(view.getCoordTransform());
shape = transform.createTransformedShape(path);
}
@Override
protected void updateDoubleLine() {
Coords m = conic.getOrigin3D(0);
Coords d = conic.getDirection3D(0);
Coords A = view.getCoordsForView(m.copy().addInsideMul(d, getStart(0)));
Coords B = view.getCoordsForView(m.copy().addInsideMul(d, getEnd(0)));
if (Kernel.isZero(A.getZ()) && Kernel.isZero(B.getZ())) {
if (line == null) {
line = AwtFactory.getPrototype().newLine2D();
}
line.setLine(A.getX(), A.getY(), B.getX(), B.getY());
} else {
isVisible = false;
return;
}
// transform to screen coords
transform.setTransform(view.getCoordTransform());
shape = transform.createTransformedShape(line);
}
@Override
protected void drawLines(GGraphics2D g2) {
fill(g2, shape);
if (geo.doHighlighting()) {
g2.setStroke(selStroke);
g2.setColor(geo.getSelColor());
g2.draw(shape);
}
g2.setStroke(objStroke);
g2.setColor(geo.getObjectColor());
g2.draw(shape);
if (labelVisible) {
g2.setFont(view.getFontConic());
g2.setColor(geo.getLabelColor());
drawLabel(g2);
}
}
@Override
public boolean hitLines(int screenx, int screeny, int hitThreshold) {
if (lines == null) {
return false;
}
for (int i = 0; i < 4; i++) {
line = lines[i];
if (line != null) {
if (line.intersects(screenx - hitThreshold,
screeny - hitThreshold, 2 * hitThreshold,
2 * hitThreshold)) {
return true;
}
}
}
return false;
}
@Override
protected void updateParabolaX0Y0(GAffineTransform trans) {
// TODO consider not symmetric parabola
y0 = getEnd(0) * conic.p;
x0 = y0 * y0 / (conic.p * 2);
}
@Override
protected void updateParabolaEdge() {
Coords m = conic.getMidpoint3D();
Coords ev1 = conic.getEigenvec3D(0);
Coords ev2 = conic.getEigenvec3D(1);
double t, u, v;
t = getStart(0);
u = conic.p * t * t / 2;
v = conic.p * t;
Coords A = view.getCoordsForView(
m.copy().addInsideMul(ev1, u).addInsideMul(ev2, v));
t = getEnd(0);
u = conic.p * t * t / 2;
v = conic.p * t;
Coords B = view.getCoordsForView(
m.copy().addInsideMul(ev1, u).addInsideMul(ev2, v));
if (Kernel.isZero(A.getZ()) && Kernel.isZero(B.getZ())) {
if (line == null) {
line = AwtFactory.getPrototype().newLine2D();
}
line.setLine(A.getX(), A.getY(), B.getX(), B.getY());
} else {
isVisible = false;
return;
}
// transform to screen coords
transform.setTransform(view.getCoordTransform());
shape = transform.createTransformedShape(line);
}
@Override
protected void updateParabolaPath() {
super.updateParabolaPath();
parabola.closePath();
}
@Override
protected void updateParabolaLabelCoords() {
labelCoords[0] = 0;
labelCoords[1] = 0;
}
@Override
protected void updateHyperbolaEdge() {
Coords m = conic.getMidpoint3D();
Coords ev1 = conic.getEigenvec3D(0);
Coords ev2 = conic.getEigenvec3D(1);
double e1 = conic.getHalfAxis(0);
double e2 = conic.getHalfAxis(1);
Coords A = null, B = null;
double start = getStart(0);
double end;
if (!Double.isNaN(start)) { // try first segment
end = getEnd(0);
A = view.getCoordsForView(
m.copy().addInsideMul(ev1, e1 * Math.cosh(start))
.addInsideMul(ev2, e2 * Math.sinh(start)));
B = view.getCoordsForView(
m.copy().addInsideMul(ev1, e1 * Math.cosh(end))
.addInsideMul(ev2, e2 * Math.sinh(end)));
} else { // try second segment
start = getStart(1);
if (!Double.isNaN(start)) {
end = getEnd(1);
A = view.getCoordsForView(
m.copy().addInsideMul(ev1, -e1 * Math.cosh(start))
.addInsideMul(ev2, e2 * Math.sinh(start)));
B = view.getCoordsForView(
m.copy().addInsideMul(ev1, -e1 * Math.cosh(end))
.addInsideMul(ev2, e2 * Math.sinh(end)));
}
}
if (A != null && Kernel.isZero(A.getZ()) && Kernel.isZero(B.getZ())) {
if (line == null) {
line = AwtFactory.getPrototype().newLine2D();
}
line.setLine(A.getX(), A.getY(), B.getX(), B.getY());
} else {
isVisible = false;
return;
}
// transform to screen coords
transform.setTransform(view.getCoordTransform());
shape = transform.createTransformedShape(line);
}
private boolean drawLeft;
@Override
protected void updateHyperbolaResetPaths() {
if (firstHyperbola) {
firstHyperbola = false;
points = PLOT_POINTS;
hyp = new GeneralPathClipped(view);
} else {
hyp.reset();
}
}
@Override
protected void updateHyperbolaX0() {
double end = getEnd(0);
if (Double.isNaN(end)) {
x0 = a * Math.cosh(getEnd(1));
drawLeft = false;
} else {
x0 = a * Math.cosh(end);
drawLeft = true;
}
}
@Override
protected void updateHyperbolaAddPoint(int index, double x, double y) {
if (drawLeft) {
hyp.addPoint(index, x, y);
} else {
hyp.addPoint(index, -x, y);
}
}
@Override
protected void updateHyperboalSetTransformToPaths() {
hyp.transform(transform);
}
@Override
protected void updateHyperbolaClosePaths() {
hyp.closePath();
}
@Override
protected void updateHyperbolaSetShape() {
shape = hyp;
}
@Override
protected void drawHyperbola(GGraphics2D g2) {
fill(g2, shape);
if (geo.doHighlighting()) {
g2.setStroke(selStroke);
g2.setColor(geo.getSelColor());
g2.draw(shape);
}
g2.setStroke(objStroke);
g2.setColor(geo.getObjectColor());
g2.draw(shape);
if (labelVisible) {
g2.setFont(view.getFontConic());
g2.setColor(geo.getLabelColor());
drawLabel(g2);
}
}
@Override
protected void updateHyperbolaLabelCoords() {
if (drawLeft) {
labelCoords[0] = a;
} else {
labelCoords[0] = -a;
}
labelCoords[1] = 0;
}
@Override
protected boolean checkHyperbolaOnScreen(GRectangle viewRect) {
// TODO ?
return true;
}
@Override
protected boolean checkCircleEllipseParabolaOnScreen(GRectangle viewRect) {
// TODO ?
return true;
}
@Override
public boolean hitHyperbola(int hitX, int hitY, int hitThreshold) {
// TODO ?
return false;
}
}