package org.geogebra.common.geogebra3D.euclidian3D;
import org.geogebra.common.awt.GPoint;
import org.geogebra.common.geogebra3D.euclidian3D.draw.DrawLabel3D;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.geos.GeoElement;
/**
* class for rays, spheres, etc. that can hit 3D objects in 3D view
*
* @author Proprietaire
*
*/
public class Hitting {
/**
* origin of the ray
*/
public Coords origin;
/**
* direction of the ray
*/
public Coords direction;
/**
* origin of the ray (screen coords)
*/
public Coords originScreen;
/**
* direction of the ray (screen coords)
*/
public Coords directionScreen;
/**
* View
*/
protected EuclidianView3D view;
/**
* current threshold
*/
protected int threshold;
/**
* constructor
*
* @param view
* 3D view
*/
public Hitting(EuclidianView3D view) {
this.view = view;
origin = new Coords(4);
origin.setW(1);
direction = new Coords(4);
originScreen = new Coords(4);
originScreen.setW(1);
directionScreen = new Coords(4);
}
/**
* set the hits
*
* @param mouseLoc
* mouse location
* @param threshold
* threshold
*/
public void setHits(GPoint mouseLoc, int threshold) {
setOriginDirectionThreshold(view.getHittingOrigin(mouseLoc),
view.getHittingDirection(), threshold);
setHits();
}
private boolean clippedValuesUpdated;
public double x0, y0, z0, x1, y1, z1, vx, vy, vz, squareNorm;
private double[] minmax;
/**
* calculate clipped x, y, z values if not already updated
*
*/
public void calculateClippedValues() {
if (clippedValuesUpdated) {
return;
}
if (minmax == null) {
minmax = new double[2];
}
minmax[0] = Double.NEGATIVE_INFINITY;
minmax[1] = Double.POSITIVE_INFINITY;
view.getIntervalClipped(minmax, origin, direction);
x0 = origin.getX() + direction.getX() * minmax[0];
y0 = origin.getY() + direction.getY() * minmax[0];
z0 = origin.getZ() + direction.getZ() * minmax[0];
x1 = origin.getX() + direction.getX() * minmax[1];
y1 = origin.getY() + direction.getY() * minmax[1];
z1 = origin.getZ() + direction.getZ() * minmax[1];
vx = x1 - x0;
vy = y1 - y0;
vz = z1 - z0;
if (vx * direction.getX() < 0 || vy * direction.getY() < 0
|| vz * direction.getZ() < 0) {
x0 = Double.NaN;
} else {
squareNorm = vx * vx + vy * vy + vz * vz;
}
clippedValuesUpdated = true;
}
/**
* set origin, direction, threshold
*
* @param origin
* origin
* @param direction
* direction
* @param threshold
* threshold
*/
public void setOriginDirectionThreshold(Coords origin, Coords direction,
int threshold) {
this.origin.set3(origin);
this.direction.set3(direction);
this.threshold = threshold;
// screen coords
originScreen.set3(origin);
view.toScreenCoords3D(originScreen);
directionScreen.set3(direction);
view.toScreenCoords3D(directionScreen);
// we need to update clipped values
clippedValuesUpdated = false;
}
/**
* set hits
*/
protected void setHits() {
Hits3D hits = view.getHits3D();
hits.init();
if (view.getShowPlane()) {
view.getPlaneDrawable().hitIfVisibleAndPickable(this, hits);
}
for (int i = 0; i < 3; i++) {
view.getAxisDrawable(i).hitIfVisibleAndPickable(this, hits);
}
view.getDrawList3D().hit(this, hits);
double zNear = hits.sort();
view.setZNearest(zNear);
}
/**
*
* @param mouseLoc
* mouse location
* @return first hitted label geo
*/
public GeoElement getLabelHit(GPoint mouseLoc) {
if (view.getProjection() == EuclidianView3D.PROJECTION_ORTHOGRAPHIC) {
return view.getDrawList3D().getLabelHit(originScreen.getX(),
originScreen.getY());
}
return view.getDrawList3D().getLabelHit(originScreen, directionScreen);
}
/**
*
* @param label
* label
* @return true if this hits the label
*/
public boolean hitLabel(DrawLabel3D label) {
if (view.getProjection() == EuclidianView3D.PROJECTION_ORTHOGRAPHIC) {
return label.hit(originScreen.getX(), originScreen.getY());
}
return label.hit(originScreen, directionScreen);
}
/**
*
* @param p
* point coords
* @return true if the point is inside the clipping box (if used)
*/
final public boolean isInsideClipping(Coords p) {
if (view.useClippingCube()) {
return view.isInside(p);
}
return true;
}
/**
*
* @return current threshold
*/
public int getThreshold() {
return threshold;
}
/**
*
* @return true if the hitting use depth values
*/
public boolean isSphere() {
return false;
}
}