package org.geogebra.common.geogebra3D.euclidian3D.draw; import java.util.ArrayList; import java.util.Comparator; import java.util.Map.Entry; import java.util.TreeSet; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GGraphics2D; import org.geogebra.common.euclidian.DrawableND; import org.geogebra.common.euclidian.EuclidianController; import org.geogebra.common.geogebra3D.euclidian3D.EuclidianController3D; import org.geogebra.common.geogebra3D.euclidian3D.EuclidianView3D; import org.geogebra.common.geogebra3D.euclidian3D.Hits3D; import org.geogebra.common.geogebra3D.euclidian3D.Hitting; 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.geos.GeoElement3D; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.geos.GProperty; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.Traceable; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.plugin.EuclidianStyleConstants; /** * 3D representation of a {@link GeoElement3D} * * * <h3>How to create the drawable of a new element</h3> * * We'll call here our new element "GeoNew3D" and create a drawable3D linked to * it: * <ul> * * <li>It extends {@link Drawable3DCurves} (for points, lines, ...) or * {@link Drawable3DSurfaces} (for planes, surfaces, ...) * <p> * <code> public class DrawNew3D extends ... { </code></li> * <li>Create new constructor * <p> * <code> public DrawNew3D(EuclidianView3D a_view3d, GeoNew3D a_new3D){ <br>    super(a_view3d, a_new3D); <br> } </code></li> * <li><b> NOTE: </b> a Drawable3D uses the * {@link GeoElement3D#getDrawingMatrix()} method to know where to draw itself * </li> * <li>Eclipse will add auto-generated methods : * <ul> * <li>getPickOrder() : for picking objects order ; use * {@link #DRAW_PICK_ORDER_MAX} first * <p> * <code> public int getPickOrder() { <br>    return DRAW_PICK_ORDER_MAX; <br> } </code></li> * <li>for {@link Drawable3DCurves} : * <p> * <code> public void drawGeometry(EuclidianRenderer3D renderer) { <br>    // call the geometry to be drawn <br> } <br> public void drawGeometryHidden(EuclidianRenderer3D renderer) { <br>    // for hidden part, let it empty first <br> } <br> public void drawGeometryPicked(EuclidianRenderer3D renderer) { <br>    // to show the object is picked, let it empty first <br> } </code></li> * <li>for {@link Drawable3DSurfaces} : * <p> * <code> public void drawGeometry(EuclidianRenderer3D renderer) { <br>    // call the geometry to be drawn <br> } <br> void drawGeometryHiding(EuclidianRenderer3D renderer) { <br>    // call the geometry that hides other objects <br>   // first sets it to : <br>   drawGeometry(renderer); <br> } <br> public void drawGeometryHidden(EuclidianRenderer3D renderer) { <br>    // for hidden part, let it empty first <br> } <br> public void drawGeometryPicked(EuclidianRenderer3D renderer) { <br>    // to show the object is picked, let it empty first <br> } </code></li> * </ul> * </li> * </ul> * * <h3>See</h3> * <ul> * <li>{@link EuclidianView3D#createDrawable(GeoElement)} to make the drawable * be created when the GeoElement is created</li> * </ul> * * * @author ggb3D * * * * * */ public abstract class Drawable3D extends DrawableND { // constants for rendering /** * objects that are picked are drawn with a thickness * PICKED_DILATATION */ protected static final float PICKED_DILATATION = 1.3f; /** default radius for drawing 3D points */ // protected static final float POINT3D_RADIUS = 1.2f; /** points on a path are a little bit more bigger than others */ protected static final float POINT_ON_PATH_DILATATION = 1.01f; /** default thickness of 3D lines, segments, ... */ // protected static final float LINE3D_THICKNESS = 0.5f; /** default thickness of lines of a 3D grid ... */ protected static final float GRID3D_THICKNESS = 0.005f; /** value for surface / geometry index when not reusable */ protected static final int NOT_REUSABLE_INDEX = -1; private static final int ALPHA_MIN_HIGHLIGHTING = 64; private static final int LIGHT_COLOR = 3 * 127; /** view3D */ private EuclidianView3D m_view3D; /** says if it has to be updated */ private boolean waitForUpdate; /** says if the label has to be updated */ private boolean labelWaitForUpdate; /** says if this has to be reset */ protected boolean waitForReset; /** gl index of the geometry */ private int geomIndex = -1; /** * gl index of the surface geometry (used for elements that have outline and * surface) */ private int surfaceIndex = -1; // links to the GeoElement private GeoElement geo; /** label */ protected DrawLabel3D label; // picking // private boolean m_isPicked = false; /** * most far picking value, used for ordering elements with openGL picking */ private double zPickFar; /** nearest picking value, used for ordering elements with openGL picking */ private double zPickNear; /** (r,g,b,a) vector */ protected GColor[] color = new GColor[]{GColor.BLACK, GColor.BLACK}; protected GColor[] surfaceColor = new GColor[]{GColor.BLACK, GColor.BLACK}; private GColor tmpColor2; protected Trace trace; // constants for picking : have to be from 0 to DRAW_PICK_ORDER_MAX-1, // regarding to picking order /** default value for picking order */ static final public int DRAW_PICK_ORDER_MAX = 4; /** picking order value for points */ static final public int DRAW_PICK_ORDER_POINT = 0; /** picking order value for texts */ static final public int DRAW_PICK_ORDER_TEXT = 1; /** picking order value for path objects (lines, segments, ...) */ static final public int DRAW_PICK_ORDER_PATH = 2; /** picking order value for surface objects (polygons, planes, ...) */ static final public int DRAW_PICK_ORDER_SURFACE = 3; // type constants /** type for drawing default (GeoList, ...) */ public static final int DRAW_TYPE_DEFAULT = 0; /** type for drawing points */ public static final int DRAW_TYPE_POINTS = DRAW_TYPE_DEFAULT + 1; /** type for drawing lines, circles, etc. */ public static final int DRAW_TYPE_CURVES = DRAW_TYPE_POINTS + 1; /** type for drawing clipped curves (functions) */ public static final int DRAW_TYPE_CLIPPED_CURVES = DRAW_TYPE_CURVES + 1; /** type for drawing planes, polygons, etc. */ public static final int DRAW_TYPE_SURFACES = DRAW_TYPE_CLIPPED_CURVES + 1; /** type for drawing polyhedrons, etc. */ public static final int DRAW_TYPE_CLOSED_SURFACES_NOT_CURVED = DRAW_TYPE_SURFACES + 1; /** type for drawing quadrics, etc. */ public static final int DRAW_TYPE_CLOSED_SURFACES_CURVED = DRAW_TYPE_CLOSED_SURFACES_NOT_CURVED + 1; /** type for drawing parametric surfaces, etc., that need clipping */ public static final int DRAW_TYPE_CLIPPED_SURFACES = DRAW_TYPE_CLOSED_SURFACES_CURVED + 1; /** type for drawing texts */ public static final int DRAW_TYPE_TEXTS = DRAW_TYPE_CLIPPED_SURFACES + 1; /** type for drawing lists */ public static final int DRAW_TYPE_LISTS = DRAW_TYPE_TEXTS + 1; /** number max of drawing types */ public static final int DRAW_TYPE_MAX = DRAW_TYPE_LISTS + 1; // ///////////////////////////////////////////////////////////////////////////// // constructors /** * construct the Drawable3D with a link to a_view3D * * @param view3D * the view linked to this */ public Drawable3D(EuclidianView3D view3D) { setView3D(view3D); label = newDrawLabel3D(view3D); } protected DrawLabel3D newDrawLabel3D(EuclidianView3D view3D) { return new DrawLabel3D(view3D, this); } /** * Call the {@link #update()} method. * * @param a_view3D * the {@link EuclidianView3D} using this Drawable3D * @param a_geo * the {@link GeoElement3D} linked to this GeoElement3D */ public Drawable3D(EuclidianView3D a_view3D, GeoElement a_geo) { this(a_view3D); init(a_geo); } /** * init * * @param geo * geo */ protected void init(GeoElement geo) { setGeoElement(geo); waitForUpdate = true; } // ///////////////////////////////////////////////////////////////////////////// // update /** * update this according to the {@link GeoElement3D} * */ @Override public void update() { clearTraceForViewChanged(); boolean isVisible = isVisible(); if ((waitForUpdateVisualStyle || waitForUpdate) && isVisible) { updateColors(); setLabelWaitForUpdate(); waitForUpdateVisualStyle = false; } if (isVisible) { updateForView(); } if (waitForUpdate && isVisible) { if (updateForItSelf()) { recordTrace(); waitForUpdate = false; } else { // we need a new repaint after current one to refine the // drawable (used DrawSurface3DOld) getView3D().waitForNewRepaint(); } setLabelWaitForUpdate();// TODO remove that } if (isLabelVisible()) { if (labelWaitForUpdate) { updateLabel(); updateLabelPosition(); labelWaitForUpdate = false; } else if (getView3D().viewChanged()) { updateLabelPosition(); } } waitForReset = false; } /** * * @return true if the geo is traced */ protected boolean hasTrace() { if (createdByDrawList()) { return ((Drawable3D) getDrawListCreator()).hasTrace(); } if (getGeoElement() == null) { return false; } if (!getGeoElement().isTraceable()) { return false; } return ((Traceable) getGeoElement()).getTrace(); } /** * * @return true if something is recorded in trace */ protected boolean hasRecordedTrace() { if (trace == null) { return false; } return !trace.isEmpty(); } /** * update the label */ protected void updateLabel() { label.update(getGeoElement().getLabelDescription(), getView3D().getFontPoint(), getGeoElement().getObjectColor(), getLabelPosition(), getLabelOffsetX(), -getLabelOffsetY()); } /** * update label position on screen */ protected void updateLabelPosition() { label.updatePosition(getView3D().getRenderer()); } /** * * @return x offset for the label */ protected float getLabelOffsetX() { return getGeoElement().labelOffsetX; } /** * * @return y offset for the label */ protected float getLabelOffsetY() { return getGeoElement().labelOffsetY; } /** * update the drawable when view has changed TODO separate * translation/rotation/zoom of the view */ abstract protected void updateForView(); /** * update the drawable when element has changed * * @return true if the update is finished */ abstract protected boolean updateForItSelf(); /** * for logic hitting, we may need an update */ public void updateForHitting() { updateForItSelf(); } /** * says that it has to be updated */ @Override public void setWaitForUpdate() { waitForUpdate = true; } /** * @return true if this wait for update */ public boolean waitForUpdate() { return waitForUpdate; } /** * says that the label has to be updated */ final public void setLabelWaitForUpdate() { labelWaitForUpdate = true; } /** * says that the label has to be reset */ public void setLabelWaitForReset() { label.setWaitForReset(); setLabelWaitForUpdate(); } /** * reset the drawable */ public void setWaitForReset() { // reset geometry indices geomIndex = -1; surfaceIndex = -1; waitForReset = true; label.setWaitForReset(); setLabelWaitForUpdate(); setWaitForUpdate(); } private boolean waitForUpdateVisualStyle = true; /** * wait for reset color */ @Override public void setWaitForUpdateVisualStyle(GProperty prop) { waitForUpdateVisualStyle = true; } protected void removeGeometryIndex(int index) { if (!waitForReset) { if (!hasTrace()) { doRemoveGeometryIndex(index); } } } protected void doRemoveGeometryIndex(int index) { getView3D().getRenderer().getGeometryManager().remove(index); } protected void setGeometryIndex(int index) { removeGeometryIndex(geomIndex); geomIndex = index; } final public int getGeometryIndex() { return geomIndex; } /** * * @return current surface index if reusable (if no trace) */ final protected int getReusableGeometryIndex() { if (hasTrace()) { return NOT_REUSABLE_INDEX; } return getGeometryIndex(); } final protected void setSurfaceIndex(int index) { removeGeometryIndex(surfaceIndex); surfaceIndex = index; } final protected int getSurfaceIndex() { return surfaceIndex; } /** * * @return current surface index if reusable (if no trace) */ final protected int getReusableSurfaceIndex() { if (hasTrace()) { return NOT_REUSABLE_INDEX; } return getSurfaceIndex(); } /** * get the label position * * @return the label position */ public Coords getLabelPosition() { return getGeoElement().getLabelPosition(); } /** * get the 3D view * * @return the 3D view */ protected EuclidianView3D getView3D() { return m_view3D; } /** * set the 3D view * * @param a_view3D * the 3D view */ protected void setView3D(EuclidianView3D a_view3D) { m_view3D = a_view3D; } /** * say if the Drawable3D is visible * * @return the visibility */ protected boolean isVisible() { boolean visible; if (createdByDrawList()) { visible = isCreatedByDrawListVisible() && ((Drawable3D) getDrawListCreator()).isVisible(); } else { visible = true; } return visible && hasGeoElementVisible(); } /** * * @return true if geo is visible (and defined) */ protected boolean hasGeoElementVisible() { return getGeoElement().hasDrawable3D() && getGeoElement().isEuclidianVisible() && getGeoElement().isDefined(); } /** * * @return geo layer */ final protected int getLayer() { if (createdByDrawList()) { return ((Drawable3D) getDrawListCreator()).getLayer(); } return getGeoElement().getLayer(); } // /////////////////////////////////////////////////////////////////////////// // drawing /** * draw the geometry for not hidden parts * * @param renderer * the 3D renderer where to draw */ abstract public void drawGeometry(Renderer renderer); /** * draw the geometry to show the object is picked (highlighted) * * @param renderer * the 3D renderer where to draw */ abstract public void drawGeometryHidden(Renderer renderer); /** * draw the outline for hidden parts * * @param renderer * the 3D renderer where to draw */ abstract public void drawOutline(Renderer renderer); /** * draw the surface for hidden parts (when not transparent) * * @param renderer * the 3D renderer where to draw */ abstract public void drawNotTransparentSurface(Renderer renderer); /** * sets the matrix, the pencil and draw the geometry for hidden parts * * @param renderer * the 3D renderer where to draw */ public void drawHidden(Renderer renderer) { if (isVisible() && getGeoElement() .getLineTypeHidden() != EuclidianStyleConstants.LINE_TYPE_HIDDEN_NONE) { setHighlightingColor(); setLineTextureHidden(renderer); drawGeometryHidden(renderer); } } /** * draw in .obj format through renderer * * @param exportToPrinter3D * renderer */ public void exportToPrinter3D(ExportToPrinter3D exportToPrinter3D) { // default : do nothing } /** * set dash texture for lines * * @param renderer * renderer */ protected void setLineTextureHidden(Renderer renderer) { if (getGeoElement() .getLineTypeHidden() == EuclidianStyleConstants.LINE_TYPE_HIDDEN_AS_NOT_HIDDEN) { renderer.getTextures() .setDashFromLineType(getGeoElement().getLineType()); } else { renderer.getTextures() .setDashFromLineTypeHidden(getGeoElement().getLineType()); } } /** * sets the matrix, the pencil and draw the geometry for transparent parts * * @param renderer * the 3D renderer where to draw */ abstract public void drawTransp(Renderer renderer); /** * sets the matrix, the pencil and draw the geometry for hiding parts * * @param renderer * the 3D renderer where to draw */ abstract public void drawHiding(Renderer renderer); /** * draw for picking, and verify (or not) if pickable * * @param renderer * @param intersection * says if it's for intersection (in this case, no check for * pickable/visible) * @return this, or the DrawList that created it, or null if not * pickable/visible */ public Drawable3D drawForPicking(Renderer renderer, boolean intersection, PickingType type) { // check pickability if (!isVisible()) { return null; } if (intersection) { // used for intersection tool drawGeometryForPickingIntersection(renderer); } else { if (!getGeoElement().isPickable()) { return null; } drawGeometryForPicking(renderer, type); } return getDrawablePicked(); } /** * * @return the drawable that is really picked (e.g. parent list) */ protected Drawable3D getDrawablePicked() { return getDrawablePicked(this); } /** * * @param drawableSource * drawable at source of picking * @return the drawable that is really picked (e.g. parent list) */ protected Drawable3D getDrawablePicked(Drawable3D drawableSource) { if (createdByDrawList()) {// if it is part of a DrawList3D, the list is // picked return ((Drawable3D) getDrawListCreator()) .getDrawablePicked(drawableSource); } return this; } /** * draws the geometry for picking (in most case, draws the default geometry) * * @param renderer * renderer * @param type * type of picking */ protected void drawGeometryForPicking(Renderer renderer, PickingType type) { drawGeometry(renderer); } /** * draws the geometry for picking an intersection * * @param renderer * renderer */ protected void drawGeometryForPickingIntersection(Renderer renderer) { drawGeometryForPicking(renderer, PickingType.POINT_OR_CURVE); } /** * draws the label (if any) * * @param renderer * 3D renderer */ public void drawLabel(Renderer renderer) { drawLabel(renderer, false); } /** * draws the label for picking it * * @param renderer * 3D renderer */ public boolean drawLabelForPicking(Renderer renderer) { return drawLabel(renderer, true); } /** * draws the label (if any) * * @param renderer * 3D renderer * @param forPicking * says if this method is called for picking * @return if picking occurred */ private boolean drawLabel(Renderer renderer, boolean forPicking) { if (forPicking) { if (!(getGeoElement().isPickable())) { return false; } } if (!isLabelVisible()) { return false; } label.draw(renderer, forPicking); return true; } /** * @return true if the label is visible */ protected boolean isLabelVisible() { return getGeoElement() != null && isVisible() && getGeoElement().isLabelVisible(); } // /////////////////////////////////////////////////////////////////////////// // picking /** * get picking order * * @return the picking order */ abstract public int getPickOrder(); /** * say if another object is pickable through this Drawable3D. * * @return if the Drawable3D is transparent */ abstract public boolean isTransparent(); /** * compare this to another Drawable3D with picking * * @param d * the other Drawable3D * @param checkPickOrder * say if the comparison has to look to pick order * @return 1 if this is in front, 0 if equality, -1 either */ public int comparePickingTo(Drawable3D d, boolean checkPickOrder) { /* * Log.debug("\ncheckPickOrder="+checkPickOrder+"\n" +"zPickNear= " * +(this.zPickNear) +" | zPickFar= "+(this.zPickFar) +" (" * +this.getGeoElement()+") "+this+"\n" +"zPickFar= "+(d.zPickNear) + * " | zPickFar= "+(d.zPickFar) +" ("+d.getGeoElement()+") "+d+"\n"); */ // check if one is transparent and the other not -- ONLY FOR DIFFERENT // PICK ORDERS if ((!this.isTransparent()) && (d.isTransparent())) { // return -1; if (checkPickOrder && this.getPickOrder() < d.getPickOrder()) { return -1; } } else if ((this.isTransparent()) && (!d.isTransparent())) { // return 1; if (checkPickOrder && this.getPickOrder() > d.getPickOrder()) { return 1; } } // check if one is selected (and moveable) and not the other // to keep handling last moved or selected geo // -- ONLY when same pickorder to avoid last created geo to get the // focus if (this.getPickOrder() == d.getPickOrder()) { GeoElement thisGeo = this.getGeoElement(); GeoElement otherGeo = d.getGeoElement(); // check one (only) is selected if (thisGeo.isSelected() && !otherGeo.isSelected()) { return -1; } if (!thisGeo.isSelected() && otherGeo.isSelected()) { return 1; } // check can drag one (only) -- if same type if (thisGeo.getGeoClassType() == otherGeo.getGeoClassType()) { boolean thisDraggable = EuclidianController3D .isDraggable(thisGeo, getView3D()); boolean otherDraggable = EuclidianController3D .isDraggable(otherGeo, getView3D()); if (thisDraggable && !otherDraggable) { return -1; } if (!thisDraggable && otherDraggable) { return 1; } } } if (Kernel.isRatioEqualTo1(this.zPickNear, d.zPickNear)) { GeoElement geo1 = this.getGeoElement(); GeoElement geo2 = d.getGeoElement(); if (geo1 == geo2) { return 0; } if (geo1.getConstructionIndex() > geo2.getConstructionIndex()) { return -1; } if (geo1.getConstructionIndex() < geo2.getConstructionIndex()) { return 1; } } // check if the two objects are "mixed" if (this.zPickFar <= d.zPickNear && d.zPickFar <= this.zPickNear) { if (checkPickOrder) { if (this.getPickOrder() < d.getPickOrder()) { return -1; } if (this.getPickOrder() > d.getPickOrder()) { return 1; } } GeoElement geo1 = this.getGeoElement(); GeoElement geo2 = d.getGeoElement(); // if both are points if (geo1.isGeoPoint() && geo2.isGeoPoint()) { // check if one is on a path and the other not if ((((GeoPointND) geo1).isPointOnPath()) && (!((GeoPointND) geo2).isPointOnPath())) { return -1; } if ((!((GeoPointND) geo1).isPointOnPath()) && (((GeoPointND) geo2).isPointOnPath())) { return 1; } // check if one is the child of the other if (geo1.isMoveable() && geo1.isChildOf(geo2)) { return -1; } if (geo2.isMoveable() && geo2.isChildOf(geo1)) { return 1; } } else { // any geo before a plane if (!geo1.isGeoPlane() && geo2.isGeoPlane()) { return -1; } if (geo1.isGeoPlane() && !geo2.isGeoPlane()) { return 1; } } // smaller object is more likely to be picked // Note: all objects that are not yet have defined a measure have a // default measure = 0 // so that they are not affected by this comparison. /* * if (Kernel.isGreater(d.getGeoElement().getMeasure(),this. * getGeoElement ().getMeasure())) return -1; if * (Kernel.isGreater(this.getGeoElement * ().getMeasure(),d.getGeoElement().getMeasure())) return 1; */ } // finally check if one is before the other if (this.zPickNear > d.zPickNear) { // Log.debug("-1"); return -1; } if (this.zPickNear < d.zPickNear) { // Log.debug("1"); return 1; } // says that the two objects are equal for the comparator /* * if (DEBUG){ DecimalFormat df = new DecimalFormat("0.000000000"); * Log.debug("equality :\n" +"zMin= "+df.format(this.zPickNear) + * " | zMax= "+df.format(this.zPickFar) +" (" * +this.getGeoElement().getLabel (StringTemplate.defaultTemplate)+")\n" * +"zMin= "+df.format(d.zPickNear) +" | zMax= "+df.format(d.zPickFar) + * " (" * +d.getGeoElement().getLabel(StringTemplate.defaultTemplate)+")\n"); } */ return 0; } /** Comparator for Drawable3Ds */ static final public class DrawableComparator implements Comparator<Drawable3D> { @Override public int compare(Drawable3D d1, Drawable3D d2) { return d1.comparePickingTo(d2, false); } } /** Comparator for sets of Drawable3Ds */ static final public class SetComparator implements Comparator<TreeSet<Drawable3D>> { @Override public int compare(TreeSet<Drawable3D> set1, TreeSet<Drawable3D> set2) { /* * TreeSet set1 = (TreeSet) arg1; TreeSet set2 = (TreeSet) arg2; */ // check if one set is empty if (set1.isEmpty()) { return 1; } if (set2.isEmpty()) { return -1; } Drawable3D d1 = set1.first(); Drawable3D d2 = set2.first(); return d1.comparePickingTo(d2, true); } } /** * @return true if geo is highlighted, or if it is part of a list * highlighted */ public boolean doHighlighting() { // no highlighting if we're moving something if (getView3D().getEuclidianController() .getMoveMode() != EuclidianController.MOVE_NONE) { return false; } if (getGeoElement().doHighlighting()) { return true; } if (createdByDrawList()) { return ((Drawable3D) getDrawListCreator()).doHighlighting(); } return false; } /** * sets the color for drawing and alpha value */ protected void setHighlightingColor() { if (doHighlighting()) { setDrawingColor(color[1]); } else { setDrawingColor(color[0]); } } /** * sets the renderer drawing color * * @param color * color */ protected void setDrawingColor(GColor color) { getView3D().getRenderer().setColor(color); } /** * sets the color of surface for drawing and alpha value */ protected void setSurfaceHighlightingColor() { if (doHighlighting()) { setDrawingColor(surfaceColor[1]); } else { setDrawingColor(surfaceColor[0]); } } protected void updateColors() { setColors(alpha, color); } protected void setColorsOutlined() { setColors(255, color);// for outline setColors(alpha, surfaceColor); } protected void setColors(int alpha, GColor[] color) { GColor c = getGeoElement().getObjectColor().deriveWithAlpha(alpha); if (getView3D().isGrayScaled()) { color[0] = c.createGrayScale(); } else { color[0] = c; } // creates corresponding color for highlighting int r = color[0].getRed(); int g = color[0].getGreen(); int b = color[0].getBlue(); int d = r + g + b; double distance; if (d > LIGHT_COLOR) {// color is closer to white : darken it distance = Math.sqrt(r * r + g * g + b * b); // euclidian distance // to black tmpColor2 = GColor.BLACK; } else {// color is closer to black : lighten it r = 255 - r; g = 255 - g; b = 255 - b; distance = Math.sqrt(r * r + g * g + b * b); // euclidian distance // to white tmpColor2 = GColor.WHITE; } double s = 255 * getColorShift() / distance; int a = color[0].getAlpha(); // sufficient alpha to be seen if (a < ALPHA_MIN_HIGHLIGHTING) { a = ALPHA_MIN_HIGHLIGHTING; } // highlighted color color[1] = GColor.mixColors(color[0], tmpColor2, s, a); } /** alpha value for rendering transparency */ private int alpha = 255; protected void setAlpha(int alpha) { this.alpha = alpha; } protected int getAlpha() { return alpha; } protected void updateAlpha() { // only used by surfaces // use 1-(1-alpha)^(1/3) because transparent parts are drawn twice int a = (int) (255 * (1 - Math.pow(1 - getGeoElement().getAlphaValue(), 1. / 3.))); if (a < 0) { a = 0; } else if (a > 255) { a = 255; } setAlpha(a); // setAlpha(getGeoElement().getAlphaValue()); } /** * * @return true if has alpha that leads to a transparent surface */ protected boolean hasTransparentAlpha() { return getAlpha() > 0 && getAlpha() < 255; } protected final static double COLOR_SHIFT_SURFACE = 0.75; // 0.2 protected final static double COLOR_SHIFT_CURVES = 0.75; // 0.2 protected final static double COLOR_SHIFT_POINTS = 0.86;// mostly sqrt(3)/2 protected final static double COLOR_SHIFT_NONE = 0; abstract protected double getColorShift(); // /////////////////////////////////////////////////////////////////////////// // links to the GeoElement /** * get the GeoElementInterface linked to the Drawable3D * * @return the GeoElement3DInterface linked to */ @Override public GeoElement getGeoElement() { return geo; } /** * set the GeoElement linked to the Drawable3D * * @param a_geo * the GeoElement */ public void setGeoElement(GeoElement a_geo) { this.geo = a_geo; // ((GeoElement3DInterface) a_geo).setDrawable3D(this); } // /////////////////////////// // TYPE /** * add this to the correct lists * * @param lists */ abstract public void addToDrawable3DLists(Drawable3DLists lists); protected void addToDrawable3DLists(Drawable3DLists lists, int type) { lists.getList(type).add(this); } /** * remove this from the correct lists * * @param lists */ abstract public void removeFromDrawable3DLists(Drawable3DLists lists); protected void removeFromDrawable3DLists(Drawable3DLists lists, int type) { lists.getList(type).remove(this); } /** * remove from GPU memory */ public void removeFromGL() { doRemoveGeometryIndex(getGeometryIndex()); doRemoveGeometryIndex(getSurfaceIndex()); label.removeFromGL(); } // //////////////////////////// // FOR PREVIEWABLE INTERFACE /** * remove this from the draw list 3D */ public void disposePreview() { getView3D().remove(this); } /** * unused for 3D * * @param g2 */ public void drawPreview(GGraphics2D g2) { // overrides 2D, needed for subclasses that implement previewable } // //////////////////// // LAST PICKING TYPE // //////////////////// /** * set last picking type * * @param type * picking type */ final public void setPickingType(PickingType type) { lastPickingType = type; } /** * * @return last picking type */ final public PickingType getPickingType() { return lastPickingType; } private PickingType lastPickingType = PickingType.POINT_OR_CURVE; protected Trace getTrace() { if (trace == null) { trace = new Trace(); } return trace; } /** * record trace */ protected void recordTrace() { if (!hasTrace()) { return; } getTrace().record(this); } /** * * @return new trace index for current geometry */ protected TraceIndex newTraceIndex() { return new TraceIndex(geomIndex, surfaceIndex); } /** * draw traces * * @param renderer * renderer * @param hidden * says if its hidden outline */ protected void drawTracesOutline(Renderer renderer, boolean hidden) { if (trace == null) { return; } if (hidden) { setLineTextureHidden(renderer); } else { renderer.getTextures() .setDashFromLineType(getGeoElement().getLineType()); } for (Entry<TraceSettings, ArrayList<TraceIndex>> settings : trace .entrySet()) { ArrayList<TraceIndex> indices = settings.getValue(); setDrawingColor(settings.getKey().getColor()); // Log.debug(indices.size()); for (TraceIndex index : indices) { drawGeom(renderer, index); } } } /** * draws the geometry of the trace index * * @param renderer * GL renderer * @param index * trace index */ protected void drawGeom(Renderer renderer, TraceIndex index) { renderer.getGeometryManager().draw(index.geom); } /** * draws the surface of the trace index * * @param renderer * GL renderer * @param index * trace index */ protected void drawSurface(Renderer renderer, TraceIndex index) { renderer.getGeometryManager().draw(index.surface); } /** * draw traces * * @param renderer * renderer */ protected void drawTracesTranspSurface(Renderer renderer) { if (trace == null) { return; } for (Entry<TraceSettings, ArrayList<TraceIndex>> settings : trace .entrySet()) { ArrayList<TraceIndex> indices = settings.getValue(); TraceSettings key = settings.getKey(); double a = key.getAlpha(); if (a > 0 && a < 1) { setDrawingColor(key.getColor()); for (TraceIndex index : indices) { drawSurface(renderer, index); } } } } /** * draw traces * * @param renderer * renderer */ protected void drawTracesHidingSurface(Renderer renderer) { if (trace == null) { return; } for (Entry<TraceSettings, ArrayList<TraceIndex>> settings : trace .entrySet()) { ArrayList<TraceIndex> indices = settings.getValue(); double a = settings.getKey().getAlpha(); if (a > 0 && a < 1) { for (TraceIndex index : indices) { drawSurface(renderer, index); } } } } /** * draw traces * * @param renderer * renderer */ protected void drawTracesNotTranspSurface(Renderer renderer) { if (trace == null) { return; } for (Entry<TraceSettings, ArrayList<TraceIndex>> settings : trace .entrySet()) { ArrayList<TraceIndex> indices = settings.getValue(); TraceSettings key = settings.getKey(); double a = key.getAlpha(); if (a >= 1) { setDrawingColor(key.getColor()); for (TraceIndex index : indices) { drawSurface(renderer, index); } } } } /** * clear trace for view changed */ final protected void clearTraceForViewChanged() { if (getView3D().viewChangedByZoom() || getView3D().viewChangedByTranslate()) { clearTraceForViewChangedByZoomOrTranslate(); } } /** * clear trace for view changed by zoom or translate */ protected void clearTraceForViewChangedByZoomOrTranslate() { if (trace != null) { // remove all geometry indices from openGL manager for (ArrayList<TraceIndex> indices : trace.values()) { for (TraceIndex index : indices) { doRemoveGeometryIndex(index.geom); doRemoveGeometryIndex(index.surface); } } trace.clear(); } } /** * set near/far z values when picked (positive value in direction to the * eye) * * @param zNear * nearest value * @param zFar * most far value */ final public void setZPick(double zNear, double zFar) { zPickNear = zNear; zPickFar = zFar; // Log.debug("\n"+getGeoElement()+" : \n"+zNear+"\n"+zFar); } /** * Note that z are positive in direction to the eye * * @return nearest z value from last picking */ final public double getZPickNear() { return zPickNear; } /** * Note that z are positive in direction to the eye * * @return far z value from last picking */ final public double getZPickFar() { return zPickFar; } /** * says if the drawable is hit by the hitting (e.g. ray) * * @param hitting * e.g. ray * @return true if hit */ public boolean hit(Hitting hitting) { // do nothing by default return false; } /** * called when part of list * * @param hitting * e.g. ray * @return true if hit */ public boolean hitForList(Hitting hitting) { if (hasGeoElementVisible() && getGeoElement().isPickable()) { return hit(hitting); } return false; } /** * says if the drawable is hit by the hitting (e.g. ray), checking first if * visible and pickable * * @param hitting * e.g. ray * @param hits * storing the drawable if hit */ final public void hitIfVisibleAndPickable(Hitting hitting, Hits3D hits) { if (isVisible() && getGeoElement().isPickable()) { // try to hit label if (hitLabel(hitting, hits)) { return; // label is hitten } // try to hit geo if (hit(hitting)) { hits.addDrawable3D(this, getPickingType()); } } } /** * * @param hitting * hitting * @param hits * hits to record * @return true if label is hitted */ protected boolean hitLabel(Hitting hitting, Hits3D hits) { if (isLabelVisible() && hitting.hitLabel(label)) { setZPick(label.getDrawZ(), label.getDrawZ()); hits.addDrawable3D(this, PickingType.LABEL); return true; } return false; } /** * * @return if the label is pickable */ public boolean hasPickableLable() { return true; } /** * enlarge min and max values to enclose object * * @param min * (x,y,z) min * @param max * (x,y,z) max */ public void enlargeBounds(Coords min, Coords max) { // nothing done by default } /** * enlarge min and max to boundsMin and boundsMax * * @param min * (x,y,z) min * @param max * (x,y,z) max * @param boundsMin * (x,y,z) object bounds min * @param boundsMax * (x,y,z) object bounds max */ static protected void enlargeBounds(Coords min, Coords max, Coords boundsMin, Coords boundsMax) { for (int i = 0; i < 3; i++) { if (min.val[i] > boundsMin.val[i]) { min.val[i] = boundsMin.val[i]; } if (max.val[i] < boundsMax.val[i]) { max.val[i] = boundsMax.val[i]; } } } /** * enlarge min and max to boundsMin and boundsMax * * @param min * (x,y,z) min * @param max * (x,y,z) max * @param coords * (x,y,z) object coords */ static protected void enlargeBounds(Coords min, Coords max, Coords coords) { for (int i = 0; i < 3; i++) { if (min.val[i] > coords.val[i]) { min.val[i] = coords.val[i]; } if (max.val[i] < coords.val[i]) { max.val[i] = coords.val[i]; } } } /** * add and sub v1+v2 or v1-v2 max values to bounds * * @param min * (x,y,z) min * @param max * (x,y,z) max * @param center * center coords * @param v1 * first direction vector * @param v2 * second direction vector * @param r1 * first direction radius * @param r2 * second direction radius * */ static protected void enlargeBoundsToDiagonal(Coords min, Coords max, Coords center, Coords v1, Coords v2, double r1, double r2) { for (int i = 0; i < 3; i++) { double add = Math.abs(v1.val[i] * r1 + v2.val[i] * r2); double sub = Math.abs(v1.val[i] * r1 - v2.val[i] * r2); double v = Math.max(add, sub); double cMin = center.val[i] - v; double cMax = center.val[i] + v; if (min.val[i] > cMin) { min.val[i] = cMin; } if (max.val[i] < cMax) { max.val[i] = cMax; } } } @Override public boolean isTracing() { return false; } /** * add last geometry to traces */ public void addLastTrace() { getTrace().addLastTraceIndex(); } }