/*
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.euclidian.draw;
import java.util.ArrayList;
import org.geogebra.common.awt.GAffineTransform;
import org.geogebra.common.awt.GArc2D;
import org.geogebra.common.awt.GGraphics2D;
import org.geogebra.common.awt.GRectangle;
import org.geogebra.common.awt.GShape;
import org.geogebra.common.euclidian.BoundingBox;
import org.geogebra.common.euclidian.Drawable;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.euclidian.Previewable;
import org.geogebra.common.euclidian.clipping.ClipShape;
import org.geogebra.common.factories.AwtFactory;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.algos.AlgoConicPartCircle;
import org.geogebra.common.kernel.algos.AlgoConicPartCircumcircle;
import org.geogebra.common.kernel.algos.AlgoSemicircle;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoElement.HitType;
import org.geogebra.common.kernel.geos.GeoLine;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.geos.Traceable;
import org.geogebra.common.kernel.kernelND.GeoConicND;
import org.geogebra.common.kernel.kernelND.GeoConicNDConstants;
import org.geogebra.common.kernel.kernelND.GeoConicPartND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
/**
*
* @author Markus Hohenwarter
*/
public class DrawConicPart extends Drawable implements Previewable {
private GeoConicPartND conicPart;
private boolean isVisible, labelVisible;
private GArc2D arc = AwtFactory.getPrototype().newArc2D();
private GShape shape;
// private GeoVec2D transVec;
private double[] halfAxes;
// private GeoVec2D center;
private int closure;
private static final int DRAW_TYPE_ELLIPSE = 1;
private static final int DRAW_TYPE_SEGMENT = 2;
private static final int DRAW_TYPE_RAYS = 3;
private int draw_type;
private GAffineTransform transform = AwtFactory.getPrototype()
.newAffineTransform();
// these are needed for degenerate arcs
private DrawRay drawRay1, drawRay2;
private DrawSegment drawSegment;
// private Drawable degDrawable;
private double[] coords = new double[2];
// preview
private ArrayList<GeoPointND> prevPoints;
private GeoPoint[] previewTempPoints;
private int previewMode, neededPrevPoints;
private boolean isPreview = false;
/**
* @param view
* view
* @param conicPart
* conic part
*/
public DrawConicPart(EuclidianView view, GeoConicPartND conicPart) {
this.view = view;
isPreview = false;
initConicPart(conicPart);
update();
}
private void initConicPart(GeoConicPartND initConicPart) {
this.conicPart = initConicPart;
geo = (GeoElement) initConicPart;
// center = conicPart.getTranslationVector();
halfAxes = ((GeoConicND) initConicPart).getHalfAxes();
// arc or sector?
closure = initConicPart
.getConicPartType() == GeoConicNDConstants.CONIC_PART_SECTOR
? GArc2D.PIE : GArc2D.OPEN;
}
/**
* Creates a new DrawConicPart for preview.
*
* @param view
* view
* @param mode
* preview mode
* @param points
* points
*/
public DrawConicPart(EuclidianView view, int mode,
ArrayList<GeoPointND> points) {
this.view = view;
prevPoints = points;
previewMode = mode;
isPreview = true;
Construction cons = view.getKernel().getConstruction();
neededPrevPoints = mode == EuclidianConstants.MODE_SEMICIRCLE ? 1 : 2;
previewTempPoints = new GeoPoint[neededPrevPoints + 1];
for (int i = 0; i < previewTempPoints.length; i++) {
previewTempPoints[i] = new GeoPoint(cons);
}
initPreview();
}
@Override
final public void update() {
isVisible = geo.isEuclidianVisible() && geo.isDefined();
if (isVisible) {
labelVisible = geo.isLabelVisible();
updateStrokes((GeoConicND) conicPart);
switch (((GeoConicND) conicPart).getType()) {
case GeoConicNDConstants.CONIC_CIRCLE:
case GeoConicNDConstants.CONIC_ELLIPSE:
updateEllipse();
break;
case GeoConicNDConstants.CONIC_LINE:
case GeoConicNDConstants.CONIC_PARALLEL_LINES:
updateParallelLines();
break;
case GeoConicNDConstants.CONIC_SINGLE_POINT:
isVisible = false;
break;
default:
// Application.debug("DrawConicPart: unsupported conic type: " +
// conicPart.getType());
isVisible = false;
return;
}
// shape on screen?
if (shape != null && !shape.intersects(0, 0, view.getWidth(),
view.getHeight())) {
isVisible = false;
// don't return here to make sure that getBounds() works for
// offscreen points too
}
// draw trace
if (((Traceable) conicPart).getTrace()) {
isTracing = true;
GGraphics2D g2 = view.getBackgroundGraphics();
if (g2 != null) {
drawTrace(g2);
}
} else {
if (isTracing) {
isTracing = false;
// view.updateBackground();
}
}
}
}
private Coords[] ev;
private void updateEllipse() {
draw_type = DRAW_TYPE_ELLIPSE;
// 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;
}
// check if in view
Coords M;
if (isPreview) { // coords have been calculated in view
M = ((GeoConicND) conicPart).getMidpoint3D().getInhomCoords();
} else {
M = view.getCoordsForView(((GeoConicND) conicPart).getMidpoint3D());
if (!Kernel.isZero(M.getZ())) {// check if in view
isVisible = false;
return;
}
}
if (ev == null) {
ev = new Coords[2];
}
for (int j = 0; j < 2; j++) {
if (isPreview) { // coords have been calculated in view
ev[j] = ((GeoConicND) conicPart).getEigenvec3D(j);
} else {
ev[j] = view.getCoordsForView(
((GeoConicND) conicPart).getEigenvec3D(j));
if (!Kernel.isZero(ev[j].getZ())) {// check if in view
isVisible = false;
return;
}
}
}
// set arc
arc.setArc(-halfAxes[0], -halfAxes[1], 2 * halfAxes[0], 2 * halfAxes[1],
-Math.toDegrees(conicPart.getParameterStart()),
-Math.toDegrees(conicPart.getParameterExtent()), closure);
// transform to screen coords
transform.setTransform(view.getCoordTransform());
transform.concatenate(view.getCompanion()
.getTransform((GeoConicND) conicPart, 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(arc);
} else {
// clip big arc at screen
shape = ClipShape.clipToRect(arc, transform,
AwtFactory.getPrototype().newRectangle(-1, -1,
view.getWidth() + 2, view.getHeight() + 2));
}
// label position
if (labelVisible) {
double midAngle = conicPart.getParameterStart()
+ conicPart.getParameterExtent() / 2.0;
coords[0] = halfAxes[0] * Math.cos(midAngle);
coords[1] = halfAxes[1] * Math.sin(midAngle);
transform.transform(coords, 0, coords, 0, 1);
labelDesc = geo.getLabelDescription();
xLabel = (int) (coords[0]) + 6;
yLabel = (int) (coords[1]) - 6;
addLabelOffset();
}
}
private void updateParallelLines() {
if (drawSegment == null
// also needs re-initing when changing Rays <-> Segment
|| (conicPart.positiveOrientation()
&& draw_type != DRAW_TYPE_SEGMENT)
|| (!conicPart.positiveOrientation()
&& draw_type != DRAW_TYPE_RAYS)) { // init
GeoLine[] lines = ((GeoConicND) conicPart).getLines();
drawSegment = new DrawSegment(view, lines[0]);
drawRay1 = new DrawRay(view, lines[0]);
drawRay2 = new DrawRay(view, lines[1]);
drawSegment.setGeoElement((GeoElement) conicPart);
drawRay1.setGeoElement((GeoElement) conicPart);
drawRay2.setGeoElement((GeoElement) conicPart);
}
Coords s = view.getCoordsForView(conicPart.getOrigin3D(0));
if (!Kernel.isZero(s.getZ())) {
isVisible = false;
return;
}
Coords e = view.getCoordsForView(conicPart.getSegmentEnd3D());
if (!Kernel.isZero(e.getZ())) {
isVisible = false;
return;
}
if (conicPart.positiveOrientation()) {
draw_type = DRAW_TYPE_SEGMENT;
drawSegment.setIsVisible();
drawSegment.update(s, e);
} else {
draw_type = DRAW_TYPE_RAYS;
Coords d = e.sub(s);
drawRay1.setIsVisible();
drawRay1.update(s, d.mul(-1), false); // don't show labels
drawRay2.setIsVisible();
drawRay2.update(conicPart.getOrigin3D(1), d, false);
}
}
@Override
final public void draw(GGraphics2D g2) {
if (isVisible) {
switch (draw_type) {
default:
// do nothing
break;
case DRAW_TYPE_ELLIPSE:
fill(g2, shape); // fill using default/hatching/image as
// appropriate
if (geo.doHighlighting()) {
g2.setPaint(geo.getSelColor());
g2.setStroke(selStroke);
g2.draw(shape);
}
g2.setPaint(getObjectColor());
g2.setStroke(objStroke);
g2.draw(shape);
if (labelVisible) {
g2.setPaint(geo.getLabelColor());
g2.setFont(view.getFontLine());
drawLabel(g2);
}
break;
case DRAW_TYPE_SEGMENT:
drawSegment.draw(g2);
break;
case DRAW_TYPE_RAYS:
drawRay1.setStroke(objStroke);
drawRay2.setStroke(objStroke);
drawRay1.draw(g2);
drawRay2.draw(g2);
break;
}
}
}
/**
* Returns the bounding box of this DrawPoint in screen coordinates.
*/
@Override
final public GRectangle getBounds() {
if (!geo.isDefined() || !geo.isEuclidianVisible()) {
return null;
}
switch (draw_type) {
case DRAW_TYPE_ELLIPSE:
if (shape == null) {
return null;
}
return shape.getBounds();
case DRAW_TYPE_SEGMENT:
if (drawSegment == null) {
return null;
}
return drawSegment.getBounds();
default:
return null;
}
}
@Override
protected final void drawTrace(GGraphics2D g2) {
switch (draw_type) {
default:
// do nothing
break;
case DRAW_TYPE_ELLIPSE:
fill(g2, shape); // fill using default/hatching/image as
// appropriate
g2.setPaint(getObjectColor());
g2.setStroke(objStroke);
g2.draw(shape);
break;
case DRAW_TYPE_SEGMENT:
drawSegment.drawTrace(g2);
break;
case DRAW_TYPE_RAYS:
drawRay1.setStroke(objStroke);
drawRay2.setStroke(objStroke);
drawRay1.drawTrace(g2);
drawRay2.drawTrace(g2);
break;
}
}
private void initPreview() {
// init the conicPart for preview
Construction cons = previewTempPoints[0].getConstruction();
int arcMode;
switch (previewMode) {
default:
// do nothing
break;
case EuclidianConstants.MODE_SEMICIRCLE:
AlgoSemicircle alg = new AlgoSemicircle(cons, previewTempPoints[0],
previewTempPoints[1]);
cons.removeFromConstructionList(alg);
initConicPart(alg.getSemicircle());
break;
case EuclidianConstants.MODE_CIRCLE_ARC_THREE_POINTS:
case EuclidianConstants.MODE_CIRCLE_SECTOR_THREE_POINTS:
arcMode = previewMode == EuclidianConstants.MODE_CIRCLE_ARC_THREE_POINTS
? GeoConicNDConstants.CONIC_PART_ARC
: GeoConicNDConstants.CONIC_PART_SECTOR;
AlgoConicPartCircle algo = new AlgoConicPartCircle(cons,
previewTempPoints[0], previewTempPoints[1],
previewTempPoints[2], arcMode);
cons.removeFromConstructionList(algo);
initConicPart(algo.getConicPart());
break;
case EuclidianConstants.MODE_CIRCUMCIRCLE_ARC_THREE_POINTS:
case EuclidianConstants.MODE_CIRCUMCIRCLE_SECTOR_THREE_POINTS:
arcMode = previewMode == EuclidianConstants.MODE_CIRCUMCIRCLE_ARC_THREE_POINTS
? GeoConicNDConstants.CONIC_PART_ARC
: GeoConicNDConstants.CONIC_PART_SECTOR;
AlgoConicPartCircumcircle algo2 = new AlgoConicPartCircumcircle(
cons, previewTempPoints[0], previewTempPoints[1],
previewTempPoints[2], arcMode);
cons.removeFromConstructionList(algo2);
initConicPart(algo2.getConicPart());
break;
}
if (conicPart != null) {
((GeoElement) conicPart).setLabelVisible(false);
}
}
@Override
final public void updatePreview() {
// two selected points + mouse position needed for preview
isVisible = conicPart != null && prevPoints.size() == neededPrevPoints;
if (isVisible) {
for (int i = 0; i < prevPoints.size(); i++) {
Coords c = view
.getCoordsForView(prevPoints.get(i).getCoordsInD3());
// Log.debug("\n"+c);
if (!Kernel.isZero(c.getZ())) {
previewTempPoints[i].setUndefined();
} else {
previewTempPoints[i].setCoords(c.projectInfDim(), true);
}
}
previewTempPoints[0].updateCascade();
}
}
@Override
final public void updateMousePos(double xRW, double yRW) {
if (isVisible) {
// avoid random line when mouse is over one of the 2 initial points
if (prevPoints.size() == 2) {
if (Kernel.isEqual(prevPoints.get(0).getInhomX(), xRW) && Kernel
.isEqual(previewTempPoints[0].getInhomY(), yRW)) {
isVisible = false;
return;
}
if (Kernel.isEqual(prevPoints.get(1).getInhomX(), xRW) && Kernel
.isEqual(previewTempPoints[1].getInhomY(), yRW)) {
isVisible = false;
return;
}
}
// double xRW = view.toRealWorldCoordX(x);
// double yRW = view.toRealWorldCoordY(y);
previewTempPoints[previewTempPoints.length - 1].setCoords(xRW, yRW,
1.0);
previewTempPoints[previewTempPoints.length - 1].updateCascade();
update();
}
}
@Override
final public void drawPreview(GGraphics2D g2) {
draw(g2);
}
@Override
public void disposePreview() {
if (conicPart != null) {
((GeoConicND) conicPart).remove();
}
}
@Override
public boolean intersectsRectangle(GRectangle rect) {
if (!isVisible) {
return false;
}
switch (draw_type) {
case DRAW_TYPE_ELLIPSE:
if (geo.isFilled()) {
shape.intersects(rect);
}
if (strokedShape == null) {
strokedShape = objStroke.createStrokedShape(shape);
}
return strokedShape.intersects(rect);
/*
* // sector: take shape for hit testing if (closure == Arc2D.PIE) {
* return shape.intersects(x-2, y-2, 4, 4) && !shape.contains(x-2, y-2,
* 4, 4); } else { if (tempPoint == null) { tempPoint = new
* GeoPoint(conicPart.getConstruction()); }
*
* double rwX = view.toRealWorldCoordX(x); double rwY =
* view.toRealWorldCoordY(y); double maxError = 4 * view.invXscale; //
* pixel tempPoint.setCoords(rwX, rwY, 1.0); return
* conicPart.isOnPath(tempPoint, maxError); }
*/
case DRAW_TYPE_SEGMENT:
return drawSegment.intersectsRectangle(rect);
case DRAW_TYPE_RAYS:
return drawRay1.intersectsRectangle(rect)
|| drawRay2.intersectsRectangle(rect);
default:
return false;
}
}
@Override
final public boolean hit(int x, int y, int hitThreshold) {
if (!isVisible) {
return false;
}
boolean pathHit = false, regionHit = false;
switch (draw_type) {
case DRAW_TYPE_ELLIPSE:
if (strokedShape == null) {
strokedShape = objStroke.createStrokedShape(shape);
}
pathHit = strokedShape.intersects(x - hitThreshold,
y - hitThreshold, 2 * hitThreshold, 2 * hitThreshold);
if (!pathHit && geo.isFilled()) {
regionHit = shape.intersects(x - hitThreshold, y - hitThreshold,
2 * hitThreshold, 2 * hitThreshold);
}
break;
/*
* // sector: take shape for hit testing if (closure == Arc2D.PIE) {
* return shape.intersects(x-2, y-2, 4, 4) && !shape.contains(x-2, y-2,
* 4, 4); } else { if (tempPoint == null) { tempPoint = new
* GeoPoint(conicPart.getConstruction()); }
*
* double rwX = view.toRealWorldCoordX(x); double rwY =
* view.toRealWorldCoordY(y); double maxError = 4 * view.invXscale; //
* pixel tempPoint.setCoords(rwX, rwY, 1.0); return
* conicPart.isOnPath(tempPoint, maxError); }
*/
case DRAW_TYPE_SEGMENT:
pathHit = drawSegment.hit(x, y, hitThreshold);
break;
case DRAW_TYPE_RAYS:
pathHit = drawRay1.hit(x, y, hitThreshold)
|| drawRay2.hit(x, y, hitThreshold);
break;
default:
return false;
}
if (pathHit) {
((GeoConicND) this.conicPart).setLastHitType(HitType.ON_BOUNDARY);
} else if (regionHit) {
((GeoConicND) this.conicPart).setLastHitType(HitType.ON_FILLING);
} else {
((GeoConicND) this.conicPart).setLastHitType(HitType.NONE);
}
return pathHit || regionHit;
}
@Override
final public boolean isInside(GRectangle rect) {
switch (draw_type) {
case DRAW_TYPE_ELLIPSE:
return rect.contains(shape.getBounds());
case DRAW_TYPE_SEGMENT:
return drawSegment.isInside(rect);
case DRAW_TYPE_RAYS:
default:
return false;
}
}
@Override
final public boolean hitLabel(int x, int y) {
switch (draw_type) {
case DRAW_TYPE_ELLIPSE:
return super.hitLabel(x, y);
case DRAW_TYPE_SEGMENT:
return drawSegment.hitLabel(x, y);
case DRAW_TYPE_RAYS:
return drawRay1.hitLabel(x, y) || drawRay2.hitLabel(x, y);
default:
return false;
}
}
@Override
public GeoElement getGeoElement() {
return geo;
}
@Override
public void setGeoElement(GeoElement geo) {
this.geo = geo;
}
@Override
public BoundingBox getBoundingBox() {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateBoundingBox() {
// TODO Auto-generated method stub
}
}