/*
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.
*/
/*
* DrawSegment
*
* Created on 21. 8 . 2003
*/
package org.geogebra.common.euclidian.draw;
import java.util.ArrayList;
import org.geogebra.common.awt.GBasicStroke;
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.Matrix.Coords;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.kernelND.GeoLineND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.util.MyMath;
/**
*
* @author Markus Hohenwarter
*/
public class DrawRay extends Drawable implements Previewable {
private GeoLineND ray;
// private GeoPoint A;
private boolean isVisible, labelVisible;
private ArrayList<GeoPointND> points;
private GLine2D line = AwtFactory.getPrototype().newLine2D();
private double[] a = new double[2];
private double[] v = new double[2];
/**
* Creates new DrawRay
*
* @param view
* view
* @param ray
* ray
*/
public DrawRay(EuclidianView view, GeoLineND ray) {
this.view = view;
this.ray = ray;
geo = (GeoElement) ray;
update();
}
/**
* Creates a new DrawSegment for preview.
*
* @param view
* view
* @param points
* preview points
*/
public DrawRay(EuclidianView view, ArrayList<GeoPointND> points) {
this.view = view;
this.points = points;
geo = view.getKernel().getConstruction().getConstructionDefaults()
.getDefaultGeo(ConstructionDefaults.DEFAULT_RAY);
updatePreview();
}
@Override
final public void update() {
update(true);
}
/**
* @param showLabel
* true if label should be shown
*/
public void update(boolean showLabel) {
isVisible = geo.isEuclidianVisible();
if (isVisible) {
// calc direction vector of ray in screen coords
Coords equation = ray.getCartesianEquationVector(view.getMatrix());
if (equation == null || !equation.isFinite()) {
isVisible = false;
return;
}
// calc start point of ray in screen coords
Coords A = view.getCoordsForView(ray.getStartInhomCoords());
if (tmpCoords2 == null) {
tmpCoords2 = new Coords(2);
}
tmpCoords2.setX(equation.getY());
tmpCoords2.setY(-equation.getX());
update(A, tmpCoords2, showLabel);
}
}
private Coords tmpCoords2;
/**
* @param startPoint
* start point
* @param direction
* direction
* @param showLabel
* true if label should be shown
*/
public void update(Coords startPoint, Coords direction, boolean showLabel) {
labelVisible = showLabel && geo.isLabelVisible();
updateStrokes(ray);
// calc start point of ray in screen coords
a[0] = startPoint.getX();
a[1] = startPoint.getY();
view.toScreenCoords(a);
v[0] = direction.getX() * view.getXscale();
v[1] = -direction.getY() * view.getYscale();
setClippedLine();
// line on screen?
if (!line.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 (ray.getTrace()) {
isTracing = true;
GGraphics2D g2 = view.getBackgroundGraphics();
if (g2 != null) {
drawTrace(g2);
}
} else {
if (isTracing) {
isTracing = false;
// view.updateBackground();
}
}
// label position
// use unit perpendicular vector to move away from line
if (labelVisible) {
labelDesc = geo.getLabelDescription();
double nx = v[0];
double ny = -v[1];
double length = MyMath.length(nx, ny);
double unit;
if (length > 0.0) {
unit = 16d / length;
} else {
nx = 0.0;
ny = 1.0;
unit = 16d;
}
xLabel = (int) (a[0] + v[0] / 2.0 + nx * unit);
yLabel = (int) (a[1] + v[1] / 2.0 + ny * unit);
addLabelOffset();
}
}
private void setClippedLine() {
boolean onscreenA = view.isOnScreen(a);
// calc clip point C = a + lambda * v
double lambda;
if (Math.abs(v[0]) > Math.abs(v[1])) {
if (v[0] > 0) {
lambda = (view.getWidth() - a[0]) / v[0];
} else {
// LEFT
lambda = -a[0] / v[0];
}
} else {
if (v[1] > 0) {
lambda = (view.getHeight() - a[1]) / v[1];
} else {
lambda = -a[1] / v[1];
}
}
if (lambda < 0) { // ray is completely out of screen
isVisible = false;
return;
}
if (onscreenA) {
// A on screen
line.setLine(a[0], a[1], a[0] + lambda * v[0],
a[1] + lambda * v[1]);
} else {
// A off screen
// clip ray at screen, that's important for huge coordinates of A
GPoint2D[] clippedPoints = ClipLine.getClipped(a[0], a[1],
a[0] + lambda * v[0], a[1] + lambda * v[1],
-EuclidianStatic.CLIP_DISTANCE,
view.getWidth() + EuclidianStatic.CLIP_DISTANCE,
-EuclidianStatic.CLIP_DISTANCE,
view.getHeight() + EuclidianStatic.CLIP_DISTANCE);
if (clippedPoints == null) {
isVisible = false;
} else {
line.setLine(clippedPoints[0].getX(), clippedPoints[0].getY(),
clippedPoints[1].getX(), clippedPoints[1].getY());
}
}
}
@Override
final public void draw(GGraphics2D g2) {
if (isVisible) {
if (geo.doHighlighting()) {
g2.setPaint(geo.getSelColor());
g2.setStroke(selStroke);
g2.draw(line);
}
g2.setPaint(getObjectColor());
g2.setStroke(objStroke);
g2.draw(line);
if (labelVisible) {
g2.setPaint(geo.getLabelColor());
g2.setFont(view.getFontLine());
drawLabel(g2);
}
}
}
/**
* @param objStroke
* stroke
*/
final public void setStroke(GBasicStroke objStroke) {
this.objStroke = objStroke;
}
@Override
final public void drawTrace(GGraphics2D g2) {
g2.setPaint(getObjectColor());
g2.setStroke(objStroke);
g2.draw(line);
}
@Override
final public void updatePreview() {
isVisible = points.size() == 1;
if (isVisible) {
// start point
// Coords coords = ((GeoPointND)
// points.get(0)).getInhomCoordsInD2();
Coords coords = view
.getCoordsForView(points.get(0).getInhomCoordsInD3());
coords.get(a);
view.toScreenCoords(a);
}
}
private GPoint2D endPoint = AwtFactory.getPrototype().newPoint2D();
@Override
final public void updateMousePos(double mouseRWx, double mouseRWy) {
double xRW = mouseRWx;
double yRW = mouseRWy;
if (isVisible) {
// need these as we don't want rounding when Alt pressed (nearest 15
// degrees)
double xx = view.toScreenCoordX(xRW);
double yy = view.toScreenCoordY(yRW);
// round angle to nearest 15 degrees if alt pressed
if (points.size() == 1
&& view.getEuclidianController().isAltDown()) {
// double xRW = view.toRealWorldCoordX(x);
// double yRW = view.toRealWorldCoordY(y);
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);
// don't use view.toScreenCoordX/Y() as we don't want rounding
xx = view.getXZero() + xRW * view.getXscale();
yy = view.getYZero() - yRW * view.getYscale();
} else {
view.getEuclidianController().setLineEndPoint(null);
}
/*
* a[0] = A.inhomX; a[1] = A.inhomY; view.toScreenCoords(a);
*/
v[0] = xx - a[0];
v[1] = yy - a[1];
setClippedLine();
}
}
@Override
final public void drawPreview(GGraphics2D g2) {
if (isVisible) {
g2.setPaint(getObjectColor());
updateStrokes(geo);
g2.setStroke(objStroke);
g2.draw(line);
}
}
@Override
public void disposePreview() {
// do nothing
}
@Override
final public boolean hit(int x, int y, int hitThreshold) {
return line.intersects(x - hitThreshold, y - hitThreshold,
2 * hitThreshold, 2 * hitThreshold);
}
@Override
final public boolean isInside(GRectangle rect) {
return false;
}
@Override
public GeoElement getGeoElement() {
return geo;
}
@Override
public void setGeoElement(GeoElement geo) {
this.geo = geo;
}
@Override
public boolean intersectsRectangle(GRectangle rect) {
return line.intersects(rect);
}
/**
* set visible
*/
public void setIsVisible() {
isVisible = true;
}
@Override
public BoundingBox getBoundingBox() {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateBoundingBox() {
// TODO Auto-generated method stub
}
}