package org.geogebra.common.geogebra3D.euclidian3D.draw;
import org.geogebra.common.euclidian.EuclidianController;
import org.geogebra.common.geogebra3D.euclidian3D.EuclidianView3D;
import org.geogebra.common.geogebra3D.euclidian3D.Hitting;
import org.geogebra.common.geogebra3D.euclidian3D.openGL.PlotterBrush;
import org.geogebra.common.geogebra3D.euclidian3D.openGL.PlotterSurface;
import org.geogebra.common.geogebra3D.euclidian3D.openGL.Renderer;
import org.geogebra.common.geogebra3D.euclidian3D.openGL.Textures;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPlane3D;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.Matrix.CoordMatrix;
import org.geogebra.common.kernel.Matrix.CoordSys;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.geos.GProperty;
import org.geogebra.common.kernel.geos.GeoElement;
/**
* Class for drawing 3D planes.
*
* @author matthieu
*
*/
public class DrawPlane3D extends Drawable3DSurfaces {
/** gl index of the grid */
private int gridIndex = -1;
private int gridOutlineIndex = -1;
double[] minmaxXFinal = new double[2], minmaxYFinal = new double[2];
/** says if the view direction is parallel to the plane */
private boolean viewDirectionIsParallel;
@Override
public void setWaitForReset() {
gridIndex = -1;
gridOutlineIndex = -1;
super.setWaitForReset();
}
/**
* Common constructor
*
* @param a_view3D
* @param a_plane3D
*/
public DrawPlane3D(EuclidianView3D a_view3D, GeoPlane3D a_plane3D) {
this(a_view3D, a_plane3D, null);
}
/**
* Constructor for helpers
*
* @param a_view3D
* @param a_plane3D
* @param geo2
*/
public DrawPlane3D(EuclidianView3D a_view3D, GeoPlane3D a_plane3D,
GeoElement geo2) {
super(a_view3D);
init(a_plane3D, geo2);
setMinMax();
}
/**
* @param a_plane3D
* plane
* @param geo2
* geo caller
*/
protected void init(GeoElement a_plane3D, GeoElement geo2) {
super.init(a_plane3D);
}
@Override
public void drawGeometry(Renderer renderer) {
drawPlate(renderer);
}
@Override
protected void drawSurfaceGeometry(Renderer renderer) {
drawGeometry(renderer);
}
/**
* draw the plate if visible
*
* @param renderer
* GL renderer
*/
protected void drawPlate(Renderer renderer) {
if (getPlane().isPlateVisible()) {
renderer.setLayer(getLayer() - 1); // -1f for z-fighting with
// planes
renderer.getGeometryManager().draw(getSurfaceIndex());
renderer.setLayer(0);
}
}
@Override
public void drawGeometryHiding(Renderer renderer) {
drawPlate(renderer);
}
@Override
public void drawOutline(Renderer renderer) {
if (!isGridVisible()) {
return;
}
if (!viewDirectionIsParallel) {
renderer.getTextures()
.setDashFromLineType(getGeoElement().getLineType());
renderer.getGeometryManager().draw(gridIndex);
}
}
@Override
public void drawGeometryHidden(Renderer renderer) {
if (!isVisible()) {
return;
}
if (!isGridVisible()) {
return;
}
if (viewDirectionIsParallel) {
renderer.setDashTexture(Textures.DASH_LONG);
renderer.getGeometryManager().draw(gridOutlineIndex);
} else {
setLineTextureHidden(renderer);
renderer.getGeometryManager().draw(gridIndex);
}
}
/*
* @Override protected void drawGeometryForPicking(Renderer renderer){
* drawGeometry(renderer); renderer.getGeometryManager().draw(gridIndex);
* renderer.getGeometryManager().draw(gridOutlineIndex); }
*/
/**
*
* @return true if grid is visible
*/
protected boolean isGridVisible() {
return getPlane().isGridVisible() || viewDirectionIsParallel;
}
@Override
protected boolean updateForItSelf() {
getPlane().setGridCorners(minmaxXFinal[0], minmaxYFinal[0],
minmaxXFinal[1], minmaxYFinal[1]);
if (isGridVisible()) {
updateGridDistances();
}
return updateGeometry();
}
private void updateGridDistances() {
CoordMatrix drawingMatrix = getPlane().getCoordSys().getDrawingMatrix();
// getPlane().setGridDistances(getView3D().getGridDistances(0),
// getView3D()
// .getGridDistances(1));
getPlane().setGridDistances(
getView3D().getGridDistances(
getMaxLengthIndex(drawingMatrix.getVx())),
getView3D().getGridDistances(
getMaxLengthIndex(drawingMatrix.getVy())));
}
final private static int getMaxLengthIndex(Coords v) {
int ret = 0;
double max = Math.abs(v.getX());
double l = Math.abs(v.getY());
if (l > max) {
max = l;
ret = 1;
}
l = Math.abs(v.getZ());
if (l > max) {
ret = 2;
}
return ret;
}
/**
*
* @return grid thickness
*/
protected int getGridThickness() {
return getGeoElement().getLineThickness();
}
/**
* update the geometry
*
* @return true
*/
protected boolean updateGeometry() {
Renderer renderer = getView3D().getRenderer();
GeoPlane3D geo = getPlane();
CoordSys coordsys = geo.getCoordSys();
float xmin1 = (float) geo.getXmin(), xmax1 = (float) geo.getXmax(),
xdelta1 = xmax1 - xmin1;
float ymin1 = (float) geo.getYmin(), ymax1 = (float) geo.getYmax(),
ydelta1 = ymax1 - ymin1;
// update bounds
updateBounds(xmin1, xmax1, ymin1, ymax1);
// plane
PlotterSurface surface = renderer.getGeometryManager().getSurface();
surface.start(geo, getReusableSurfaceIndex());
surface.setU(xmin1, xmax1);
surface.setNbU(2);
surface.setV(ymin1, ymax1);
surface.setNbV(2);
if (!getView3D().useClippingCube()) {
float fading;
fading = xdelta1 * geo.getFading();
surface.setUFading(fading, fading);
fading = ydelta1 * geo.getFading();
surface.setVFading(fading, fading);
}
surface.draw();
setSurfaceIndex(surface.end());
// grid
if (isGridVisible()) {
PlotterBrush brush = renderer.getGeometryManager().getBrush();
if (hasTrace()) {
brush.start(-1);
} else {
brush.start(gridIndex);
}
removeGeometryIndex(gridIndex);
float thickness = brush.setThickness(getGridThickness(),
(float) getView3D().getScale());
brush.setColor(getGeoElement().getObjectColor());
double dx = geo.getGridXd();
double dy;
if (Double.isNaN(dx)) {
dx = getView3D().getNumbersDistance();
dy = dx;
} else {
dy = geo.getGridYd();
}
brush.setAffineTexture((0f - xmin1) / ydelta1, 0.25f);
int i0 = (int) (ymin1 / dy);
if (ymin1 > 0) {
i0++;
}
for (int i = i0; i <= ymax1 / dy; i++) {
brush.segment(coordsys.getPointForDrawing(xmin1, i * dy),
coordsys.getPointForDrawing(xmax1, i * dy));
}
// along y axis
brush.setAffineTexture((0f - ymin1) / xdelta1, 0.25f);
i0 = (int) (xmin1 / dx);
if (xmin1 > 0) {
i0++;
}
for (int i = i0; i <= xmax1 / dx; i++) {
brush.segment(coordsys.getPointForDrawing(i * dx, ymin1),
coordsys.getPointForDrawing(i * dx, ymax1));
}
gridIndex = brush.end();
brush.start(gridOutlineIndex);
removeGeometryIndex(gridOutlineIndex);
boolean showClippingCube = getView3D().showClippingCube();
// draws the rectangle outline
if (showClippingCube) {
brush.setAffineTexture((0f - xmin1) / ydelta1, 0.25f);
} else {
brush.setPlainTexture();
}
brush.segment(coordsys.getPointForDrawing(xmin1, ymax1 - thickness),
coordsys.getPointForDrawing(xmax1, ymax1 - thickness));
brush.segment(coordsys.getPointForDrawing(xmin1, ymin1 + thickness),
coordsys.getPointForDrawing(xmax1, ymin1 + thickness));
if (showClippingCube) {
brush.setAffineTexture((0f - ymin1) / xdelta1, 0.25f);
}
brush.segment(coordsys.getPointForDrawing(xmin1 + thickness, ymin1),
coordsys.getPointForDrawing(xmin1 + thickness, ymax1));
brush.segment(coordsys.getPointForDrawing(xmax1 - thickness, ymin1),
coordsys.getPointForDrawing(xmax1 - thickness, ymax1));
gridOutlineIndex = brush.end();
}
return true;
}
private Coords boundsMin = new Coords(3), boundsMax = new Coords(3);
protected void updateBounds(double xmin, double xmax, double ymin,
double ymax) {
GeoPlane3D geo = getPlane();
CoordSys coordsys = geo.getCoordSys();
// update z min/max
boundsMin.setZ(Double.POSITIVE_INFINITY);
boundsMax.setZ(Double.NEGATIVE_INFINITY);
updateZMinMax(coordsys, xmin, ymin);
updateZMinMax(coordsys, xmin, ymax);
updateZMinMax(coordsys, xmax, ymax);
updateZMinMax(coordsys, xmax, ymin);
// update x min/max
boundsMin.setX(xmin);
boundsMax.setX(xmax);
// update y min/max
boundsMin.setY(ymin);
boundsMax.setY(ymax);
}
private void updateZMinMax(CoordSys coordsys, double x, double y) {
coordsys.getPointForDrawing(x, y, tmpCoords1);
double z = tmpCoords1.getZ();
if (z < boundsMin.getZ()) {
boundsMin.setZ(z);
}
if (z > boundsMax.getZ()) {
boundsMax.setZ(z);
}
}
@Override
public void enlargeBounds(Coords min, Coords max) {
if (!Double.isNaN(boundsMin.getX())) {
enlargeBounds(min, max, boundsMin, boundsMax);
}
}
@Override
protected void updateForView() {
if (getView3D().viewChanged()) {
if (!getView3D().viewChangedByTranslate()
&& !getView3D().viewChangedByZoom()) { // only rotation
boolean oldViewDirectionIsParallel = viewDirectionIsParallel;
checkViewDirectionIsParallel(); // done in setWaitForUpdate()
// too
if (oldViewDirectionIsParallel != viewDirectionIsParallel) { // maybe
// have
// to
// update
// the
// outline
setWaitForUpdate();
}
return;
}
setWaitForUpdate();
}
}
@Override
public void setWaitForUpdate() {
super.setWaitForUpdate();
setMinMax();
checkViewDirectionIsParallel();
}
/**
* set x-y min/max values
*/
protected void setMinMax() {
if (!getGeoElement().isDefined()) {
return;
}
if (getView3D().useClippingCube()) { // make sure the plane goes more
// than the clipping cube
setMinMax(getView3D().getClippingVertex(0),
getView3D().getClippingVertex(1),
getView3D().getClippingVertex(2),
getView3D().getClippingVertex(4));
} else { // use interior clipping cube radius
setMinMax(getView3D().getCenter(),
getView3D().getFrustumInteriorRadius());
}
}
/**
*
* @return plane
*/
protected GeoPlane3D getPlane() {
return (GeoPlane3D) getGeoElement();
}
private Coords o = Coords.createInhomCoorsInD3();
private Coords tmpCoords1 = Coords.createInhomCoorsInD3(),
tmpCoords2 = Coords.createInhomCoorsInD3();
/**
* sets the min/max regarding a clipping box
*
* @param origin
* center of the clipping box
* @param vx
* first edge
* @param vy
* second edge
* @param vz
* third edge
*/
private void setMinMax(Coords origin, Coords vx, Coords vy, Coords vz) {
GeoPlane3D geo = getPlane();
CoordMatrix m = geo.getCoordSys().getDrawingMatrix();
origin.projectPlaneInPlaneCoords(m, o);
minmaxXFinal[0] = o.getX();
minmaxYFinal[0] = o.getY();
minmaxXFinal[1] = o.getX();
minmaxYFinal[1] = o.getY();
Coords[] v = new Coords[3];
vx.projectPlaneInPlaneCoords(m, tmpCoords1);
v[0] = tmpCoords1.sub(o);
vy.projectPlaneInPlaneCoords(m, tmpCoords1);
v[1] = tmpCoords1.sub(o);
vz.projectPlaneInPlaneCoords(m, tmpCoords1);
v[2] = tmpCoords1.sub(o);
for (int i = 0; i < 3; i++) {
double x = v[i].getX();
if (x < 0) {
minmaxXFinal[0] += x; // sub from xmin
}
else {
minmaxXFinal[1] += x; // add to xmax
}
double y = v[i].getY();
if (y < 0) {
minmaxYFinal[0] += y; // sub from ymin
}
else {
minmaxYFinal[1] += y; // add to ymax
}
}
}
private static final double INV_SQRT_2 = 1 / Math.sqrt(2);
private void setMinMax(Coords origin, double radius) {
GeoPlane3D geo = getPlane();
CoordMatrix m = geo.getCoordSys().getDrawingMatrix();
origin.projectPlaneInPlaneCoords(m, o);
double a = radius * INV_SQRT_2;
minmaxXFinal[0] = o.getX() - a;
minmaxYFinal[0] = o.getY() - a;
minmaxXFinal[1] = o.getX() + a;
minmaxYFinal[1] = o.getY() + a;
}
@Override
public int getPickOrder() {
return DRAW_PICK_ORDER_SURFACE;
}
@Override
public void addToDrawable3DLists(Drawable3DLists lists) {
addToDrawable3DLists(lists, DRAW_TYPE_CURVES);
super.addToDrawable3DLists(lists);
}
@Override
public void removeFromDrawable3DLists(Drawable3DLists lists) {
removeFromDrawable3DLists(lists, DRAW_TYPE_CURVES);
super.removeFromDrawable3DLists(lists);
}
private void checkViewDirectionIsParallel() {
if (Kernel.isZero(getPlane().getCoordSys().getEquationVector()
.dotproduct(getView3D().getEyePosition()))) {
viewDirectionIsParallel = true;
} else {
viewDirectionIsParallel = false;
}
}
@Override
public void setWaitForUpdateVisualStyle(GProperty prop) {
super.setWaitForUpdateVisualStyle(prop);
// also update for plane clip
setWaitForUpdate();
}
@Override
public boolean hit(Hitting hitting) {
return hit(hitting, tmpCoords1, tmpCoords2);
}
public boolean hit(Hitting hitting, Coords tmpCoords1, Coords tmpCoords2) {
if (waitForReset) { // prevent NPE
return false;
}
if (getGeoElement()
.getAlphaValue() < EuclidianController.MIN_VISIBLE_ALPHA_VALUE) {
return false;
}
GeoPlane3D plane = getPlane();
// project hitting origin on plane
if (hitting.isSphere()) {
hitting.origin.projectPlane(plane.getCoordSys().getDrawingMatrix(),
tmpCoords1, tmpCoords2);
} else {
hitting.origin.projectPlaneThruVIfPossible(
plane.getCoordSys().getDrawingMatrix(), hitting.direction,
tmpCoords1, tmpCoords2);
}
if (!hitting.isInsideClipping(tmpCoords1)) {
return false;
}
double x = tmpCoords2.getX();
if (x < plane.getXmin()) {
return false;
}
if (x > plane.getXmax()) {
return false;
}
double y = tmpCoords2.getY();
if (y < plane.getYmin()) {
return false;
}
if (y > plane.getYmax()) {
return false;
}
if (hitting.isSphere()) {
double d = getView3D().getScaledDistance(tmpCoords1,
hitting.origin);
if (d <= hitting.getThreshold()) {
setZPick(-d, -d);
return true;
}
} else {
double parameterOnHitting = tmpCoords2.getZ();// TODO use other for
// non-parallel
// projection :
// -hitting.origin.distance(project[0]);
setZPick(parameterOnHitting, parameterOnHitting);
return true;
}
return false;
}
}