package org.geogebra.common.geogebra3D.euclidian3D.draw;
import java.util.ArrayList;
import java.util.Iterator;
import org.geogebra.common.euclidian.EuclidianController;
import org.geogebra.common.euclidian.Previewable;
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.Renderer;
import org.geogebra.common.geogebra3D.euclidian3D.openGL.Renderer.PickingType;
import org.geogebra.common.geogebra3D.euclidian3D.printer3D.ExportToPrinter3D;
import org.geogebra.common.geogebra3D.kernel3D.Kernel3D;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPoint3D;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPolygon3D;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.discrete.PolygonTriangulation;
import org.geogebra.common.kernel.discrete.PolygonTriangulation.Convexity;
import org.geogebra.common.kernel.geos.FromMeta;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoPolygon;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.main.Feature;
import org.geogebra.common.util.debug.Log;
/**
* Class for drawing 3D polygons.
*
* @author matthieu
*
*/
public class DrawPolygon3D extends Drawable3DSurfaces implements Previewable {
/**
* Common constructor
*
* @param a_view3D
* @param polygon
*/
public DrawPolygon3D(EuclidianView3D a_view3D, GeoPolygon polygon) {
super(a_view3D, polygon);
setPickingType(PickingType.SURFACE);
}
// drawing
@Override
public void drawGeometry(Renderer renderer) {
renderer.setLayer(getLayer()); // +0f for z-fighting with planes
renderer.getGeometryManager().draw(getGeometryIndex());
renderer.setLayer(0);
}
@Override
public void drawOutline(Renderer renderer) {
if (isVisible()) {
setHighlightingColor();
renderer.getTextures()
.setDashFromLineType(getGeoElement().getLineType());
drawGeometry(renderer);
}
drawTracesOutline(renderer, false);
}
@Override
public void drawGeometryHiding(Renderer renderer) {
drawSurfaceGeometry(renderer);
}
@Override
public void drawGeometryHidden(Renderer renderer) {
drawGeometry(renderer);
}
@Override
protected void drawGeometryForPicking(Renderer renderer, PickingType type) {
if (type == PickingType.POINT_OR_CURVE) {
drawGeometry(renderer);
} else {
if (getAlpha() > 0) { // surface is pickable only if not totally
// transparent
drawSurfaceGeometry(renderer);
}
}
}
@Override
protected void drawSurfaceGeometry(Renderer renderer) {
renderer.setLayer(getLayer()); // +0f to avoid z-fighting with planes
renderer.getGeometryManager().draw(getSurfaceIndex());
renderer.setLayer(0);
}
@Override
public int getPickOrder() {
/*
* Application.debug(alpha<1); if (alpha<1)
*/
return DRAW_PICK_ORDER_SURFACE; // when transparent
/*
* else return DRAW_PICK_ORDER_1D; //when not
*/
}
private int surfaceDrawTypeAdded;
private boolean curvesAdded;
@Override
public void addToDrawable3DLists(Drawable3DLists lists) {
if (((GeoPolygon) getGeoElement()).isPartOfClosedSurface()) {
surfaceDrawTypeAdded = DRAW_TYPE_CLOSED_SURFACES_NOT_CURVED;
} else {
surfaceDrawTypeAdded = DRAW_TYPE_SURFACES;
}
addToDrawable3DLists(lists, surfaceDrawTypeAdded);
if (!((GeoPolygon) getGeoElement()).wasInitLabelsCalled()) { // no
// labels
// for
// segments
addToDrawable3DLists(lists, DRAW_TYPE_CURVES);
curvesAdded = true;
} else {
curvesAdded = false;
}
}
@Override
public void removeFromDrawable3DLists(Drawable3DLists lists) {
removeFromDrawable3DLists(lists, surfaceDrawTypeAdded);
if (curvesAdded) {
removeFromDrawable3DLists(lists, DRAW_TYPE_CURVES);
}
}
private Coords[] vertices = new Coords[0];
private void updateVertices(GeoPolygon polygon, int pointLength) {
if (vertices.length < pointLength) {
vertices = new Coords[pointLength];
for (int i = 0; i < pointLength; i++) {
vertices[i] = new Coords(3);
}
}
for (int i = 0; i < pointLength; i++) {
vertices[i].setValues(polygon.getPoint3D(i), 3);
}
if (pointLength > 0) {
boundsMin.setValues(vertices[0], 3);
boundsMax.setValues(vertices[0], 3);
for (int i = 1; i < pointLength; i++) {
enlargeBounds(boundsMin, boundsMax, vertices[i]);
}
}
}
private Coords boundsMin = new Coords(3), boundsMax = new Coords(3);
@Override
public void enlargeBounds(Coords min, Coords max) {
enlargeBounds(min, max, boundsMin, boundsMax);
}
@Override
protected boolean updateForItSelf() {
// super.updateForItSelf();
// creates the polygon
GeoPolygon polygon = (GeoPolygon) getGeoElement();
int pointLength = polygon.getPointsLength();
if (pointLength < 2) { // no polygon
setSurfaceIndex(-1);
return true;
}
Renderer renderer = getView3D().getRenderer();
updateVertices(polygon, pointLength);
// outline
if (!isPreview && !polygon.wasInitLabelsCalled()) { // no labels for
// segments
updateOutline(renderer, vertices, pointLength);
}
if (pointLength < 3) { // no polygon
setSurfaceIndex(-1);
return true;
}
// surface
int index = renderer.startPolygons(getReusableSurfaceIndex());
drawPolygon(renderer, polygon, vertices, pointLength);
renderer.endPolygons();
setSurfaceIndex(index);
return true;
}
/**
*
* @param renderer
* GL renderer
* @param polygon
* polygon
* @param pt
* polygon triangulation
* @param vertices
* vertices of the polygon
* @param verticesLength
* vertices length (may <> vertices.length due to cache)
*/
static final public void drawPolygon(Renderer renderer, GeoPolygon polygon,
Coords[] vertices, int verticesLength) {
Coords n = polygon.getMainDirection();
PolygonTriangulation pt = polygon.getPolygonTriangulation();
pt.clear();
try {
// simplify the polygon and check if there are at least 3 points
// left
if (pt.updatePoints() > 2) {
// check if the polygon is convex
Convexity convexity = pt.checkIsConvex();
if (convexity != Convexity.NOT) {
drawConvex(renderer, polygon, n, vertices, verticesLength,
convexity);
} else {
// set intersections (if needed) and divide the polygon into
// non self-intersecting polygons
pt.setIntersections();
// convert the set of polygons to triangle fans
pt.triangulate();
// compute 3D coords for intersections
pt.setCompleteVertices(vertices, polygon.getCoordSys(),
verticesLength);
// draw the triangle fans
drawFans(renderer, polygon, n, vertices, verticesLength);
}
}
} catch (Exception e) {
Log.debug(e.getMessage());
e.printStackTrace();
}
}
static final private void drawConvex(Renderer renderer, GeoPolygon polygon,
Coords n, Coords[] vertices, int verticesLength,
Convexity convexity) {
boolean reverse = polygon.getReverseNormalForDrawing()
^ (convexity == Convexity.CLOCKWISE);
renderer.getGeometryManager().drawPolygonConvex(n, vertices,
verticesLength, reverse);
}
static final private void drawFans(Renderer renderer, GeoPolygon polygon,
Coords n, Coords[] vertices, int verticesLength) {
PolygonTriangulation pt = polygon.getPolygonTriangulation();
Coords[] verticesWithIntersections = pt.getCompleteVertices(vertices,
verticesLength);
renderer.getGeometryManager().drawTriangleFans(n,
verticesWithIntersections, pt.getMaxPointIndex(),
pt.getTriangleFans());
}
private void updateOutline(Renderer renderer, Coords[] vertices,
int length) {
PlotterBrush brush = renderer.getGeometryManager().getBrush();
brush.start(getReusableGeometryIndex());
brush.setThickness(getGeoElement().getLineThickness(),
(float) getView3D().getScale());
for (int i = 0; i < length - 1; i++) {
brush.setAffineTexture(0.5f, 0.25f);
brush.segment(vertices[i], vertices[i + 1]);
}
brush.setAffineTexture(0.5f, 0.25f);
brush.segment(vertices[length - 1], vertices[0]);
setGeometryIndex(brush.end());
}
@Override
protected void updateForView() {
if (getView3D().viewChangedByZoom()) {
Renderer renderer = getView3D().getRenderer();
GeoPolygon polygon = (GeoPolygon) getGeoElement();
int verticesLength = polygon.getPointsLength();
// no labels for segments
if (!((GeoPolygon) getGeoElement()).wasInitLabelsCalled()) {
if (vertices != null && vertices.length >= verticesLength) { // TODO
// remove
// this
// test
// outline
updateOutline(renderer, vertices, verticesLength);
}
}
if (getView3D().getApplication()
.has(Feature.DIFFERENT_AXIS_RATIO_3D)) {
try {
// surface
PolygonTriangulation pt = polygon.getPolygonTriangulation();
if (pt.getMaxPointIndex() > 2) {
int index = renderer
.startPolygons(getReusableSurfaceIndex());
Coords n = polygon.getMainDirection();
// check if the polygon is convex
Convexity convexity = pt.checkIsConvex();
if (convexity != Convexity.NOT) {
drawConvex(renderer, polygon, n, vertices,
verticesLength, convexity);
} else {
// draw the triangle fans
drawFans(renderer, polygon, n, vertices,
verticesLength);
}
renderer.endPolygons();
setSurfaceIndex(index);
}
} catch (Exception e) {
Log.debug(e.getMessage());
e.printStackTrace();
}
}
}
}
// //////////////////////////////
// Previewable interface
private ArrayList<GeoPointND> selectedPoints;
/** segments of the polygon preview */
private ArrayList<DrawSegment3D> segments;
private ArrayList<ArrayList<GeoPointND>> segmentsPoints;
private boolean isPreview = false;
/**
* Constructor for previewable
*
* @param a_view3D
* @param selectedPoints
*/
public DrawPolygon3D(EuclidianView3D a_view3D,
ArrayList<GeoPointND> selectedPoints) {
super(a_view3D);
Kernel3D kernel = getView3D().getKernel();
setGeoElement(new GeoPolygon3D(kernel.getConstruction(), null));
getGeoElement().setIsPickable(false);
this.selectedPoints = selectedPoints;
segments = new ArrayList<DrawSegment3D>();
segmentsPoints = new ArrayList<ArrayList<GeoPointND>>();
setPickingType(PickingType.SURFACE);
isPreview = true;
updatePreview();
}
@Override
public void updateMousePos(double xRW, double yRW) {
// TODO Auto-generated method stub
}
@Override
public void updatePreview() {
// intersection curve
if (segmentsPoints == null) {
// Log.debug(this);
setWaitForUpdate();
return;
}
int index = 0;
Iterator<ArrayList<GeoPointND>> spi = segmentsPoints.iterator();
Iterator<GeoPointND> i = selectedPoints.iterator();
GeoPointND point = null; // current point of the selected points
ArrayList<GeoPointND> sp = null; // segment selected points
// set points to existing segments points
while (i.hasNext() && spi.hasNext()) {
point = i.next();
if (sp != null)
{
sp.add(point); // add second point to precedent segment
}
sp = spi.next();
sp.clear();
sp.add(point); // add first point to current segment
}
// clear segments points if there are some more
while (spi.hasNext()) {
sp = spi.next();
sp.clear();
}
// set points to new segments points
while (i.hasNext()) {
if (sp != null && point != null)
{
sp.add(point); // add second point to precedent segment
}
sp = new ArrayList<GeoPointND>();
segmentsPoints.add(sp);
point = i.next();
sp.add(point);
DrawSegment3D s = new DrawSegment3D(getView3D(), sp);
s.getGeoElement().setVisualStyle(getGeoElement());
segments.add(s);
getView3D().addToDrawable3DLists(s);
}
// update segments
for (Iterator<DrawSegment3D> s = segments.iterator(); s.hasNext();) {
s.next().updatePreview();
}
// Application.debug("DrawList3D:\n"+getView3D().getDrawList3D().toString());
// polygon itself
if (selectedPoints.size() < 2) {
getGeoElement().setEuclidianVisible(false);
return;
}
getGeoElement().setEuclidianVisible(true);
GeoPointND[] points = new GeoPointND[selectedPoints.size() + 1];
index = 0;
for (Iterator<GeoPointND> p = selectedPoints.iterator(); p.hasNext();) {
points[index] = p.next();
index++;
}
points[index] = getView3D().getCursor3D();
// sets the points of the polygon
((GeoPolygon3D) getGeoElement()).setPoints(points, null, false);
// check if all points are on the same plane
((GeoPolygon3D) getGeoElement()).updateCoordSys();
if (getGeoElement().isDefined()) {
setWaitForUpdate();
}
}
@Override
public void disposePreview() {
// first update preview to ensure segments arrays are correct
updatePreview();
super.disposePreview();
// dispose segments
if (segments != null) {
for (DrawSegment3D s : segments) {
s.disposePreview();
}
}
// we may reuse it
if (segmentsPoints != null) {
segmentsPoints.clear();
}
}
@Override
public boolean doHighlighting() {
// if the polygon depends on a polyhedron, look at the meta'
// highlighting
if (getGeoElement().getMetasLength() > 0) {
for (GeoElement meta : ((FromMeta) getGeoElement()).getMetas()) {
if (meta != null && meta.doHighlighting()) {
return true;
}
}
}
return super.doHighlighting();
}
private GeoPoint3D hittingPointForOutline;
@Override
public boolean hit(Hitting hitting) {
if (waitForReset) { // prevent NPE
return false;
}
if (getGeoElement()
.getAlphaValue() < EuclidianController.MIN_VISIBLE_ALPHA_VALUE) {
return false;
}
GeoPolygon poly = (GeoPolygon) getGeoElement();
if (poly.getCoordSys() == null) {
return false;
}
// project hitting origin on polygon plane
if (globalCoords == null) {
globalCoords = new Coords(4);
inPlaneCoords = new Coords(4);
}
if (hitting.isSphere()) {
hitting.origin.projectPlane(
poly.getCoordSys().getMatrixOrthonormal(), globalCoords);
if (hittingPointForOutline == null) {
hittingPointForOutline = new GeoPoint3D(poly.getConstruction());
hittingPointForOutline.setWillingCoordsUndefined();
hittingPointForOutline.setWillingDirectionUndefined();
}
// try outline
hittingPointForOutline.setCoords(globalCoords);
poly.pointChanged(hittingPointForOutline);
Coords p3d = hittingPointForOutline.getInhomCoordsInD3();
if (project == null) {
project = Coords.createInhomCoorsInD3();
}
double d = getView3D().getScaledDistance(p3d, hitting.origin);
if (d <= poly.getLineThickness() + hitting.getThreshold()) {
setZPick(-d, -d);
setPickingType(PickingType.POINT_OR_CURVE);
return true;
}
// try inside
hittingPointForOutline.setCoords(globalCoords);
hittingPointForOutline.setRegion(poly);
poly.pointChangedForRegion(hittingPointForOutline);
p3d = hittingPointForOutline.getInhomCoordsInD3();
d = getView3D().getScaledDistance(p3d, hitting.origin);
if (d <= hitting.getThreshold()) {
setZPick(-d, -d);
setPickingType(PickingType.SURFACE);
return true;
}
} else {
hitting.origin.projectPlaneThruVIfPossible(
poly.getCoordSys().getMatrixOrthonormal(),
hitting.direction, globalCoords, inPlaneCoords);
if (!hitting.isInsideClipping(globalCoords)) {
return false;
}
boolean ret = false;
// check if hitting projection hits the polygon
if (poly.isInRegion(inPlaneCoords.getX(), inPlaneCoords.getY())) {
double parameterOnHitting = inPlaneCoords.getZ();// TODO use
// other for
// non-parallel
// projection
// :
// -hitting.origin.distance(project[0]);
setZPick(parameterOnHitting, parameterOnHitting);
setPickingType(PickingType.SURFACE);
ret = true;
}
// check if hitting is on path
if (!poly.wasInitLabelsCalled()) {
if (hittingPointForOutline == null) {
hittingPointForOutline = new GeoPoint3D(
poly.getConstruction());
}
hittingPointForOutline.setCoords(globalCoords);
poly.pointChanged(hittingPointForOutline);
Coords p3d = hittingPointForOutline.getInhomCoordsInD3();
if (hitting.isInsideClipping(p3d)) {
if (project == null) {
project = Coords.createInhomCoorsInD3();
}
p3d.projectLine(hitting.origin, hitting.direction, project,
parameters); // check distance to hitting line
double d = getView3D().getScaledDistance(p3d, project);
if (d <= poly.getLineThickness() + hitting.getThreshold()) {
double z = -parameters[0];
double dz = poly.getLineThickness()
/ getView3D().getScale();
setZPick(z + dz, z - dz);
setPickingType(PickingType.POINT_OR_CURVE);
return true;
}
}
}
return ret;
}
return false;
}
private Coords project, globalCoords, inPlaneCoords;
private double[] parameters = new double[2];
@Override
public void exportToPrinter3D(ExportToPrinter3D exportToPrinter3D) {
if (isVisible()) {
exportToPrinter3D.export((GeoPolygon) getGeoElement(), vertices);
}
}
}