package org.geogebra.common.geogebra3D.euclidian3D; import java.util.ArrayList; import java.util.Iterator; import java.util.TreeSet; import org.geogebra.common.euclidian.Hits; import org.geogebra.common.geogebra3D.euclidian3D.draw.Drawable3D; import org.geogebra.common.geogebra3D.euclidian3D.draw.Drawable3D.DrawableComparator; import org.geogebra.common.geogebra3D.euclidian3D.openGL.Renderer.PickingType; import org.geogebra.common.geogebra3D.kernel3D.geos.GeoQuadric3D; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoElement.HitType; import org.geogebra.common.kernel.kernelND.GeoConicND; import org.geogebra.common.util.debug.Log; /** * 3D hits (for picking, selection, ...) * * @author Mathieu * */ public class Hits3D extends Hits { private static final long serialVersionUID = 1L; /** * class for tree set of drawable 3D * */ private static class TreeSetOfDrawable3D extends TreeSet<Drawable3D> { private static final long serialVersionUID = 1L; public TreeSetOfDrawable3D(DrawableComparator drawableComparator) { super(drawableComparator); } public void add(Drawable3D d, double zNear, double zFar) { // if already contained and not nearer, do nothing if (contains(d)) { if (d.getZPickNear() > zNear) { return; } // will re-add it at the correct z values remove(d); } d.setZPick(zNear, zFar); super.add(d); } } /** set of hits by picking order */ private TreeSetOfDrawable3D[] hitSet = new TreeSetOfDrawable3D[Drawable3D.DRAW_PICK_ORDER_MAX]; /** other hits */ private TreeSetOfDrawable3D hitsOthers = new TreeSetOfDrawable3D( new Drawable3D.DrawableComparator()); /** label hits */ private TreeSetOfDrawable3D hitsLabels = new TreeSetOfDrawable3D( new Drawable3D.DrawableComparator()); /** set of all the sets */ private TreeSet<TreeSetOfDrawable3D> hitSetSet = new TreeSet<TreeSetOfDrawable3D>( new Drawable3D.SetComparator()); private Hits topHits = new Hits(); private ArrayList<Drawable3D> drawables3D = new ArrayList<Drawable3D>(); /** number of quadrics 2D */ private int QuadCount; /** * common constructor */ public Hits3D() { super(); for (int i = 0; i < Drawable3D.DRAW_PICK_ORDER_MAX; i++) { hitSet[i] = new TreeSetOfDrawable3D( new Drawable3D.DrawableComparator()); } // init counters QuadCount = 0; } @Override public Hits3D cloneHits() { Hits3D ret = (Hits3D) super.cloneHits(); ret.topHits = this.topHits.cloneHits(); ret.QuadCount = QuadCount; // TreeSets are not cloned because they are only used when the hits are // constructed return ret; } @Override protected Hits newHits() { return new Hits3D(); } @Override public boolean add(GeoElement geo) { if (geo == null) { Log.error("adding null geo"); return false; } if (geo instanceof GeoQuadric3D) { QuadCount++; } return super.add(geo); } @Override public void init() { super.init(); for (int i = 0; i < Drawable3D.DRAW_PICK_ORDER_MAX; i++) { hitSet[i].clear(); } hitsOthers.clear(); hitsLabels.clear(); topHits.init(); } /** * init and set geo as only element * * @param geo * geo */ public void init(GeoElement geo) { init(); if (geo != null) { add(geo); topHits.add(geo); } } /** * insert a drawable in the hitSet, called by EuclidianRenderer3D * * @param d * the drawable * @param type * type of picking * @param zNear * nearest z for picking * @param zFar * most far z for picking */ public void addDrawable3D(Drawable3D d, PickingType type, double zNear, double zFar) { if (type == PickingType.LABEL) { if (!d.getGeoElement().isGeoText()) { hitsLabels.add(d, zNear, zFar); } } else { // remember last type for picking d.setPickingType(type); } // Log.debug("\n"+d+"\n"+type); if (d.getPickOrder() < Drawable3D.DRAW_PICK_ORDER_MAX) { hitSet[d.getPickOrder()].add(d, zNear, zFar); } else { hitsOthers.add(d, zNear, zFar); } } /** * insert a drawable in the hitSet * * @param d * the drawable * @param type * type of picking */ public void addDrawable3D(Drawable3D d, PickingType type) { if (type == PickingType.LABEL) { if (!d.getGeoElement().isGeoText()) { hitsLabels.add(d); } } else { // remember last type for picking d.setPickingType(type); } // Log.debug("\n"+d+"\n"+type); if (d.getPickOrder() < Drawable3D.DRAW_PICK_ORDER_MAX) { hitSet[d.getPickOrder()].add(d); } else { hitsOthers.add(d); } } /** * sort all hits in different sets * * @return nearest zNear */ public double sort() { hitSetSet.clear(); for (int i = 0; i < Drawable3D.DRAW_PICK_ORDER_MAX; i++) { hitSetSet.add(hitSet[i]); // Log.debug(i+"--"+hitSet[i]); } // return nearest zNear double zNear = Double.NaN; // top hits Iterator<Drawable3D> iter1 = hitSetSet.first().iterator(); if (iter1.hasNext()) { Drawable3D d = iter1.next(); topHits.add(d.getGeoElement()); zNear = d.getZPickNear(); } while (iter1.hasNext()) { Drawable3D d = iter1.next(); topHits.add(d.getGeoElement()); } // App.error(""+topHits); // sets the hits to this ArrayList<GeoElement> segmentList = new ArrayList<GeoElement>(); drawables3D.clear(); for (Iterator<TreeSetOfDrawable3D> iterSet = hitSetSet .iterator(); iterSet.hasNext();) { TreeSetOfDrawable3D set = iterSet.next(); for (Iterator<Drawable3D> iter = set.iterator(); iter.hasNext();) { Drawable3D d = iter.next(); drawables3D.add(d); GeoElement geo = d.getGeoElement(); this.add(geo); // add the parent of this if it's a segment from a GeoPolygon3D // or GeoPolyhedron if (geo.isGeoSegment()) { segmentList.add(geo); } else if (geo.isGeoConic()) { if (d.getPickingType() == PickingType.POINT_OR_CURVE) { ((GeoConicND) geo).setLastHitType(HitType.ON_BOUNDARY); } else { // PickingType.SURFACE ((GeoConicND) geo).setLastHitType(HitType.ON_FILLING); } } } } // add the parent of this if it's a segment from a GeoPolygon3D or // GeoPolyhedron /* * TODO ? for (Iterator<GeoElement> iter = segmentList.iterator(); * iter.hasNext();) { GeoSegment3D seg = (GeoSegment3D) iter.next(); * GeoElement parent = seg.getGeoParent(); if (parent!=null) if * (!this.contains(parent)) this.add(seg.getGeoParent()); } */ // debug /* * if (getLabelHit()==null) Application.debug(toString()); else * Application .debug(toString()+"\n first label : " * +getLabelHit().getLabel()); */ return zNear; } /** * WARNING : sort() should be called before * * @return all drawables, in pick order */ public ArrayList<Drawable3D> getDrawables() { return drawables3D; } @Override public Hits getTopHits() { if (topHits.isEmpty()) { return cloneHits(); } return topHits; } @Override public Hits getTopHits(int depth, int geoN) { Hits3D ret = new Hits3D(); int depthCount = 0; int geoNCount = 0; for (Iterator<TreeSetOfDrawable3D> iterSet = hitSetSet .iterator(); iterSet.hasNext() && depthCount < depth;) { TreeSetOfDrawable3D set = iterSet.next(); if (set.size() > 0) { depthCount++; } for (Iterator<Drawable3D> iter = set.iterator(); iter.hasNext() && geoNCount < geoN;) { Drawable3D d = iter.next(); GeoElement geo = d.getGeoElement(); ret.add(geo); geoNCount++; } } return ret; } /** * return the first label hit, if one * * @return the first label hit */ public GeoElement getLabelHit() { if (hitsLabels.isEmpty()) { return null; } // Log.debug("\nlabel:"+hitsLabels.first().zPickMin+"\nfirst // hit:"+drawables3D.get(0).zPickMin); GeoElement labelGeo = hitsLabels.first().getGeoElement(); // check if the label hit is the first geo hitted if (labelGeo == topHits.get(0)) { return labelGeo; } // else label is not hitted return null; } /** * remove all polygons but one */ @Override public void removeAllPolygonsButOne() { super.removeAllPolygonsButOne(); topHits.clear(); // getTopHits() return this } @Override public void removeAllPolygonsAndQuadricsButOne() { boolean foundTarget = false; for (int i = 0; i < size() - 1; ++i) { GeoElement geo = get(i); if (geo.isGeoPolygon() || geo instanceof GeoQuadric3D || geo.isGeoConic()) { if (foundTarget) { // not removing when found first time remove(i); } foundTarget = true; } } } @Override protected Hits createNewHits() { return new Hits3D(); } }