/*
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.
*/
/*
* DrawVector.java
*
* Created on 16. Oktober 2001, 15:13
*/
package org.geogebra.common.euclidian.draw;
import java.util.ArrayList;
import org.geogebra.common.awt.GGeneralPath;
import org.geogebra.common.awt.GGraphics2D;
import org.geogebra.common.awt.GLine2D;
import org.geogebra.common.awt.GPoint2D;
import org.geogebra.common.awt.GRectangle;
import org.geogebra.common.euclidian.BoundingBox;
import org.geogebra.common.euclidian.Drawable;
import org.geogebra.common.euclidian.EuclidianStatic;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.euclidian.Previewable;
import org.geogebra.common.euclidian.clipping.ClipLine;
import org.geogebra.common.factories.AwtFactory;
import org.geogebra.common.kernel.ConstructionDefaults;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.kernel.kernelND.GeoVectorND;
import org.geogebra.common.util.MyMath;
/**
*
* @author Markus
*/
public class DrawVector extends Drawable implements Previewable {
private GeoVectorND v;
private GeoPointND P;
private boolean isVisible, labelVisible;
private boolean traceDrawingNeeded = false;
private GLine2D line;
private double[] coordsA = new double[2];
private double[] coordsB = new double[2];
private double[] coordsV = new double[2];
private GGeneralPath gp; // for arrow
private boolean arrowheadVisible, lineVisible;
private ArrayList<GeoPointND> points;
/**
* Creates new DrawVector
*
* @param view
* view
* @param v
* vector
*/
public DrawVector(EuclidianView view, GeoVectorND v) {
this.view = view;
this.v = v;
geo = (GeoElement) v;
update();
}
/**
* @param view
* view
* @param points
* start point and end point
*/
public DrawVector(EuclidianView view, ArrayList<GeoPointND> points) {
this.view = view;
this.points = points;
geo = view.getKernel().getConstruction().getConstructionDefaults()
.getDefaultGeo(ConstructionDefaults.DEFAULT_VECTOR);
updatePreview();
}
@Override
final public void update() {
isVisible = geo.isEuclidianVisible();
if (!isVisible) {
return;
}
labelVisible = geo.isLabelVisible();
updateStrokes(v);
Coords coords;
// start point in real world coords
P = v.getStartPoint();
if (P != null && !P.isInfinite()) {
coords = view.getCoordsForView(P.getInhomCoordsInD3());// P.getCoordsInD3();
if (!Kernel.isZero(coords.getZ())) {
isVisible = false;
return;
}
coordsA[0] = coords.getX();
coordsA[1] = coords.getY();
} else {
coordsA[0] = 0;
coordsA[1] = 0;
}
// vector
coords = view.getCoordsForView(v.getCoordsInD3());// v.getCoordsInD3();
if (!Kernel.isZero(coords.getZ())) {
isVisible = false;
return;
}
coordsV[0] = coords.getX();
coordsV[1] = coords.getY();
// end point
coordsB[0] = coordsA[0] + coordsV[0];
coordsB[1] = coordsA[1] + coordsV[1];
// set line and arrow of vector and converts all coords to screen
setArrow(((GeoElement) v).getLineThickness());
// label position
if (labelVisible) {
labelDesc = geo.getLabelDescription();
// note that coordsV was normalized in setArrow()
xLabel = (int) ((coordsA[0] + coordsB[0]) / 2.0 + coordsV[1]);
yLabel = (int) ((coordsA[1] + coordsB[1]) / 2.0 - coordsV[0]);
addLabelOffset();
}
// draw trace
// a vector is a Locateable and it might
// happen that there are several update() calls
// before the new trace should be drawn
// so the actual drawing is moved to draw()
traceDrawingNeeded = v.getTrace();
if (v.getTrace()) {
isTracing = true;
} else {
if (isTracing) {
isTracing = false;
// view.updateBackground();
}
}
}
/**
* @param lineThickness
* vector thickness
* @return arrow size
*/
static final public double getFactor(double lineThickness) {
// changed to make arrow-heads a bit bigger for line thickness 8-13
return lineThickness < 8 ? 12.0 + lineThickness : 3 * lineThickness;
}
/**
* Sets the line and arrow of the vector.
*/
private void setArrow(double lineThickness) {
// screen coords of start and end point of vector
boolean onscreenA = view.toScreenCoords(coordsA);
boolean onscreenB = view.toScreenCoords(coordsB);
coordsV[0] = coordsB[0] - coordsA[0];
coordsV[1] = coordsB[1] - coordsA[1];
// calculate endpoint F at base of arrow
double factor = getFactor(lineThickness);
double length = MyMath.length(coordsV[0], coordsV[1]);
// decrease arrowhead size if it's longer than the vector
if (length < factor) {
factor = length;
}
if (length > 0.0) {
coordsV[0] = (coordsV[0] * factor) / length;
coordsV[1] = (coordsV[1] * factor) / length;
}
double[] coordsF = new double[2];
coordsF[0] = coordsB[0] - coordsV[0];
coordsF[1] = coordsB[1] - coordsV[1];
// set clipped line
if (line == null) {
line = AwtFactory.getPrototype().newLine2D();
}
lineVisible = true;
if (onscreenA && onscreenB) {
// A and B on screen
line.setLine(coordsA[0], coordsA[1], coordsF[0], coordsF[1]);
} else {
// A or B off screen
// clip at screen, that's important for huge coordinates
// check if any of vector is on-screen
GPoint2D[] clippedPoints = ClipLine.getClipped(coordsA[0],
coordsA[1], coordsB[0], coordsB[1],
-EuclidianStatic.CLIP_DISTANCE,
view.getWidth() + EuclidianStatic.CLIP_DISTANCE,
-EuclidianStatic.CLIP_DISTANCE,
view.getHeight() + EuclidianStatic.CLIP_DISTANCE);
if (clippedPoints == null) {
isVisible = false;
lineVisible = false;
arrowheadVisible = false;
} else {
// now re-clip at A and F
clippedPoints = ClipLine.getClipped(coordsA[0], coordsA[1],
coordsF[0], coordsF[1], -EuclidianStatic.CLIP_DISTANCE,
view.getWidth() + EuclidianStatic.CLIP_DISTANCE,
-EuclidianStatic.CLIP_DISTANCE,
view.getHeight() + EuclidianStatic.CLIP_DISTANCE);
if (clippedPoints != null) {
line.setLine(clippedPoints[0].getX(),
clippedPoints[0].getY(), clippedPoints[1].getX(),
clippedPoints[1].getY());
} else {
lineVisible = false;
}
}
}
// add triangle if visible
if (gp == null) {
gp = AwtFactory.getPrototype().newGeneralPath();
} else {
gp.reset();
}
if (isVisible) {
if (length > 0) {
coordsV[0] /= 4.0;
coordsV[1] /= 4.0;
gp.moveTo(coordsB[0], coordsB[1]); // end point
gp.lineTo((coordsF[0] - coordsV[1]), (coordsF[1] + coordsV[0]));
gp.lineTo((coordsF[0] + coordsV[1]), (coordsF[1] - coordsV[0]));
gp.closePath();
}
arrowheadVisible = onscreenB
|| gp.intersects(0, 0, view.getWidth(), view.getHeight());
}
}
@Override
public void draw(GGraphics2D g2) {
if (isVisible) {
if (traceDrawingNeeded) {
traceDrawingNeeded = false;
GGraphics2D g2d = view.getBackgroundGraphics();
if (g2d != null) {
drawTrace(g2d);
}
}
if (geo.doHighlighting()) {
g2.setPaint(((GeoElement) v).getSelColor());
g2.setStroke(selStroke);
if (lineVisible) {
g2.draw(line);
}
}
g2.setPaint(getObjectColor());
g2.setStroke(objStroke);
if (lineVisible) {
g2.draw(line);
}
if (arrowheadVisible) {
g2.fill(gp);
}
if (labelVisible) {
g2.setFont(view.getFontVector());
g2.setPaint(((GeoElement) v).getLabelColor());
drawLabel(g2);
}
}
}
@Override
protected final void drawTrace(GGraphics2D g2) {
g2.setPaint(getObjectColor());
g2.setStroke(objStroke);
if (lineVisible) {
g2.draw(line);
}
if (arrowheadVisible) {
g2.fill(gp);
}
}
@Override
final public void updatePreview() {
isVisible = points.size() == 1;
if (isVisible) {
// start point
// GeoPoint P = (GeoPoint) points.get(0);
// P.getInhomCoords(coordsA);
view.getCoordsForView(points.get(0).getInhomCoordsInD3())
.get(coordsA);
coordsB[0] = coordsA[0];
coordsB[1] = coordsA[1];
}
}
private GPoint2D endPoint = AwtFactory.getPrototype().newPoint2D();
@Override
final public void updateMousePos(double xRWmouse, double yRWmouse) {
double xRW = xRWmouse;
double yRW = yRWmouse;
if (isVisible) {
// double xRW = view.toRealWorldCoordX(x);
// double yRW = view.toRealWorldCoordY(y);
// round angle to nearest 15 degrees if alt pressed
if (points.size() == 1
&& view.getEuclidianController().isAltDown()) {
GeoPoint p = (GeoPoint) points.get(0);
double px = p.inhomX;
double py = p.inhomY;
double angle = Math.atan2(yRW - py, xRW - px) * 180 / Math.PI;
double radius = Math.sqrt(
(py - yRW) * (py - yRW) + (px - xRW) * (px - xRW));
// round angle to nearest 15 degrees
angle = Math.round(angle / 15) * 15;
xRW = px + radius * Math.cos(angle * Math.PI / 180);
yRW = py + radius * Math.sin(angle * Math.PI / 180);
endPoint.setX(xRW);
endPoint.setY(yRW);
view.getEuclidianController().setLineEndPoint(endPoint);
} else {
view.getEuclidianController().setLineEndPoint(null);
}
// set start and end point in real world coords
// GeoPoint P = (GeoPoint) points.get(0);
// P.getInhomCoords(coordsA);
if (points.size() > 0) {
view.getCoordsForView(points.get(0).getInhomCoordsInD3())
.get(coordsA);
}
coordsB[0] = xRW;
coordsB[1] = yRW;
setArrow(1);
}
}
@Override
final public void drawPreview(GGraphics2D g2) {
if (isVisible) {
g2.setPaint(getObjectColor());
updateStrokes(geo);
g2.setStroke(objStroke);
if (arrowheadVisible) {
g2.fill(gp);
}
if (lineVisible) {
g2.draw(line);
}
}
}
@Override
public void disposePreview() {
// do nothing
}
@Override
final public boolean hit(int x, int y, int hitThreshold) {
return (lineVisible && line.intersects(x - 3, y - 3, 6, 6))
|| (arrowheadVisible && gp.intersects(x - 3, y - 3, 6, 6));
}
@Override
final public boolean isInside(GRectangle rect) {
return (lineVisible && rect.contains(line.getBounds()))
|| (arrowheadVisible && rect.contains(gp.getBounds()));
}
@Override
public boolean intersectsRectangle(GRectangle rect) {
return (lineVisible && line.intersects(rect))
|| (arrowheadVisible && gp.intersects(rect));
}
@Override
public GeoElement getGeoElement() {
return geo;
}
@Override
public void setGeoElement(GeoElement geo) {
this.geo = geo;
}
/**
* Returns the bounding box of this Drawable in screen coordinates.
*/
@Override
final public GRectangle getBounds() {
if (!geo.isDefined() || !geo.isEuclidianVisible()) {
return null;
}
GRectangle ret = null;
if (lineVisible) {
ret = line.getBounds();
}
if (arrowheadVisible) {
ret = (ret == null)
? AwtFactory.getPrototype().newRectangle(gp.getBounds())
: AwtFactory.getPrototype()
.newRectangle(ret.union(gp.getBounds()));
}
return ret;
}
@Override
public BoundingBox getBoundingBox() {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateBoundingBox() {
// TODO Auto-generated method stub
}
}