/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ package org.geogebra.common.euclidian; import java.util.ArrayList; import java.util.Iterator; import org.geogebra.common.euclidian.event.AbstractEvent; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.geos.FromMeta; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoElement.HitType; import org.geogebra.common.kernel.geos.GeoFunctionNVar; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoPolygon; import org.geogebra.common.kernel.geos.Test; import org.geogebra.common.kernel.kernelND.GeoAxisND; import org.geogebra.common.kernel.kernelND.GeoConicND; import org.geogebra.common.kernel.kernelND.GeoCoordSys2D; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoImplicitSurfaceND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.kernelND.GeoPolyhedronInterface; import org.geogebra.common.kernel.kernelND.GeoQuadric3DInterface; import org.geogebra.common.kernel.kernelND.GeoQuadric3DLimitedInterface; import org.geogebra.common.kernel.kernelND.GeoQuadric3DPartInterface; import org.geogebra.common.kernel.kernelND.GeoSegmentND; import org.geogebra.common.kernel.kernelND.HasVolume; /** * * class for hitting objects with the mouse * * @author Markus Hohenwarter */ // TODO change ArrayList to TreeSet public class Hits extends ArrayList<GeoElement> { private static final long serialVersionUID = 1L; private int listCount; private int polyCount; private int imageCount; /** number of coord sys 2D */ private int cs2DCount; private boolean hasXAxis, hasYAxis, hasZAxis; /** init the hits */ public void init() { clear(); listCount = 0; polyCount = 0; imageCount = 0; cs2DCount = 0; hasXAxis = false; hasYAxis = false; hasZAxis = false; } // Can't override and GWT don't support CLONE anyway. @SuppressWarnings("all") public Hits cloneHits() { Hits ret = newHits(); if (this.size() > 0) { for (int i = 0; i < this.size(); i++) { ret.add(this.get(i)); } } ret.listCount = this.listCount; ret.polyCount = this.polyCount; ret.imageCount = this.imageCount; ret.hasXAxis = this.hasXAxis; ret.hasYAxis = this.hasYAxis; ret.hasZAxis = this.hasZAxis; ret.cs2DCount = cs2DCount; return ret; } /** * * @return new instance of the same class */ protected Hits newHits() { return new Hits(); } /** adding specifics GeoElements */ @Override public boolean add(GeoElement geo) { if (geo == null) { return false; } if (!geo.isSelectionAllowed(null)) { // #3771 if (!(geo instanceof GeoList && ((GeoList) geo).drawAsComboBox())) { return false; } } if (geo instanceof GeoCoordSys2D) { cs2DCount++; } if (geo.isGeoList()) { listCount++; } else if (geo.isGeoImage()) { imageCount++; } else if (geo.isGeoPolygon()) { polyCount++; } else if (geo instanceof GeoAxisND) { switch (((GeoAxisND) geo).getType()) { default: case GeoAxisND.X_AXIS: hasXAxis = true; break; case GeoAxisND.Y_AXIS: hasYAxis = true; break; case GeoAxisND.Z_AXIS: hasZAxis = true; break; } } return super.add(geo); } /** * @return count of hit images */ public int getImageCount() { return imageCount; } /** * @return count of hit lists */ public int getListCount() { return listCount; } /** * @return true if x axis is hit */ public boolean hasXAxis() { return hasXAxis; } /** * @return true if y axis is hit */ public boolean hasYAxis() { return hasYAxis; } /** * @return true if z axis is hit */ public boolean hasZAxis() { return hasZAxis; } /** * returns GeoElement whose label is at screen coords (x,y). */ /* * final public GeoElement getLabelHit(Point p) { if * (!app.isLabelDragsEnabled()) return null; DrawableIterator it = * allDrawableList.getIterator(); while (it.hasNext()) { Drawable d = * it.next(); if (d.hitLabel(p.x, p.y)) { GeoElement geo = * d.getGeoElement(); if (geo.isEuclidianVisible()) return geo; } } return * null; } */ /** * absorbs new elements in hits2 Tam: 2011/5/21 * * @param hits2 * hits to be absorbed * @return the repeated elements in hits2 */ public Hits absorb(ArrayList<GeoElement> hits2) { Hits ret = new Hits(); for (int i = 0; i < hits2.size(); i++) { if (!contains(hits2.get(i))) { add(hits2.get(i)); } else { ret.add(hits2.get(i)); } } return ret; } /** * remove all the points Tam, 5/22/2011 */ final public void removeAllPoints() { for (int i = size() - 1; i >= 0; i--) { GeoElement geo = get(i); if (geo == null || geo.isGeoPoint()) { remove(i); } } } /** * Removes all transparent geos. Transparency criteria same as in * EuclidianController3D::decideHideIntersection */ final public void removeAllDimElements() { for (int i = size() - 1; i >= 0; i--) { GeoElement geo = get(i); if (geo == null || geo.isRegion() && (geo.getAlphaValue() < 0.1f || geo.getLineThickness() < 0.5f) || geo.isPath() && geo.getLineThickness() < 0.5f) { remove(i); } } } /** * A polygon is only kept if none of its sides is also in hits. */ final public void removePolygonsIfSidePresent() { removePolygonsDependingSidePresent(false); } /** * Removes polygons that are in hits but none of their sides is hit */ final public void removePolygonsIfSideNotPresent() { removePolygonsDependingSidePresent(true); } /** * Returns hits that are suitable for new point mode. A polygon is only kept * if one of its sides is also in hits. */ final public void keepOnlyHitsForNewPointMode() { removePolygonsDependingSidePresent(true); } /** * remove all conics hitted on the filling, not on the boundary */ final public void removeConicsHittedOnFilling() { Iterator<GeoElement> it = this.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); if (geo.isGeoConic()) { if (((GeoConicND) geo).getLastHitType() == HitType.ON_FILLING) { it.remove(); } } } } final private void removePolygonsDependingSidePresent( boolean sidePresentWanted) { Iterator<GeoElement> it = this.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); if (geo.isGeoPolygon()) { boolean sidePresent = false; GeoSegmentND[] sides = ((GeoPolygon) geo).getSegments(); if (sides != null) { for (int k = 0; k < sides.length; k++) { if (this.contains(sides[k])) { sidePresent = true; break; } } } if (sidePresent != sidePresentWanted) { it.remove(); } } } } /** * remove sliders from hits */ final void removeSliders() { Iterator<GeoElement> it = this.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); if (geo.isGeoNumeric() && ((GeoNumeric) geo).isSlider()) { it.remove(); } } } /** * remove all hits after geo * * @param geo * last geo */ final public void removeGeosAfter(GeoElementND geo) { for (int i = size() - 1; i >= 0 && get(i) != geo; i--) { remove(i); } } /** * remove segments from all present polygons */ final public void removeSegmentsFromPolygons() { ArrayList<GeoSegmentND> toRemove = new ArrayList<GeoSegmentND>(); Iterator<GeoElement> it = this.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); if (geo.isGeoPolygon()) { GeoSegmentND[] sides = ((GeoPolygon) geo).getSegments(); for (int k = 0; k < sides.length; k++) { toRemove.add(sides[k]); } } } for (GeoSegmentND d : toRemove) { this.remove(d); } } /** * @return vectors and points in this hits; NOT numerics */ final public Hits getPointVectorNumericHits() { Hits ret = new Hits(); for (int i = 0; i < size(); ++i) { GeoElement geo = get(i); if ( // geo.isGeoNumeric() || geo.isGeoVector() || geo.isGeoPoint()) { ret.add(geo); } } return ret; } // replaces EuclidianView . final public ArrayList getHits(Point p, boolean // includePolygons) { /** * removes all polygons */ final public void removePolygons() { if (size() - polyCount > 0) { for (int i = size() - 1; i >= 0; i--) { GeoElement geo = get(i); if (geo.isGeoPolygon()) { remove(i); } } } } /** * Removes all polygons */ final public void removeAllPolygons() { for (int i = size() - 1; i >= 0; i--) { GeoElement geo = get(i); if (geo.isGeoPolygon()) { remove(i); } } } /** * Removes all planes */ final public void removeAllPlanes() { for (int i = size() - 1; i >= 0; i--) { GeoElement geo = get(i); if (geo.isGeoPlane()) { remove(i); } } } /** * remove all polygons but one */ public void removeAllPolygonsButOne() { int toRemove = polyCount - 1; for (int i = size() - 1; i >= 0 && toRemove > 0; i--) { GeoElement geo = get(i); if (geo.isGeoPolygon()) { remove(i); toRemove--; } } } /** * * @return poly count in this */ public int getPolyCount() { return polyCount; } /** * Find the first set of geo corresponding to one of the tests. Found geos * are supposed to be in the same intervall. * * @param tests * class tests * @return correct hits (if exist) */ final public Hits keepFirsts(Test... tests) { Hits ret = new Hits(); Test testFound = null; boolean goFurther = true; for (int i = 0; i < size() && goFurther; i++) { GeoElement geo = get(i); if (testFound == null) { for (int j = 0; j < tests.length && testFound == null; j++) { if (tests[j].check(geo)) { testFound = tests[j]; ret.add(geo); } } } else { if (testFound.check(geo)) { ret.add(geo); } else { goFurther = false; } } } return ret; } /** * Removes all polygonsand quadrics but one; for 3D */ public void removeAllPolygonsAndQuadricsButOne() { // for 3D } /** * Keeps only images; for 3D */ final public void removeAllButImages() { // for 3D } /** * Removes images */ public void removeImages() { for (int i = size() - 1; i >= 0; i--) { GeoElement geo = get(i); if (geo.isGeoImage()) { remove(i); } } } /** * returns array of independent GeoElements whose visual representation is * at streen coords (x,y). order: points, vectors, lines, conics */ /* * final public ArrayList getMoveableHits(Point p) { return * getMoveableHits(getHits(p)); } */ /** * @param view * view * @return array of changeable GeoElements out of hits */ final public Hits getMoveableHits(EuclidianViewInterfaceSlim view) { return getMoveables(view, Test.MOVEABLE, null); } /** * PointRotateable * * @param view * view * @param rotCenter * rotation center * @return array of changeable GeoElements out of hits that implement */ final public Hits getPointRotateableHits(EuclidianViewInterfaceSlim view, GeoPointND rotCenter) { return getMoveables(view, Test.ROTATEMOVEABLE, rotCenter); } /** * @return hits that have selection allowed */ final public Hits getSelectableHits() { GeoElement geo; Hits selectableList = new Hits(); for (int i = 0; i < size(); ++i) { geo = get(i); if (geo.isSelectionAllowed(null)) { selectableList.add(geo); } } return selectableList; } /** * @param view * view * @param test * either ROTATEMOVEABLE or MOVEABLE * @param rotCenter * rotation center * @return (rotate)moveable geos */ protected Hits getMoveables(EuclidianViewInterfaceSlim view, Test test, GeoPointND rotCenter) { GeoElement geo; Hits moveableList = new Hits(); for (int i = 0; i < size(); ++i) { geo = get(i); switch (test) { case MOVEABLE: // moveable object if (geo.isMoveable(view)) { moveableList.add(geo); // Application.debug("moveable GeoElement = "+geo); } // point with changeable parent coords else if (geo.isGeoPoint()) { GeoPointND point = (GeoPointND) geo; if (point.hasChangeableCoordParentNumbers()) { moveableList.add((GeoElement) point); } } // not a point, but has moveable input points else if (geo.hasMoveableInputPoints(view)) { moveableList.add(geo); } break; case ROTATEMOVEABLE: // check for circular definition if (geo.isRotateMoveable()) { if (rotCenter == null || !geo.isParentOf(rotCenter)) { moveableList.add(geo); } } else if (geo.hasMoveableInputPoints(view)) { moveableList.add(geo); } break; default: break; } } /* * if (moveableList.size() == 0) return null; else return moveableList; */ return moveableList; } /** * returns array of GeoElements of type geoclass whose visual representation * is at streen coords (x,y). order: points, vectors, lines, conics */ /* * final public ArrayList getHits(Point p, Class geoclass, ArrayList result) * { return getHits(getHits(p), geoclass, false, result); } */ /** * @param geoclass * test for type that * @param result * hits object for result * @return array of GeoElements NOT passing test out of hits */ final public Hits getOtherHits(Test geoclass, Hits result) { return getHits(geoclass, true, result); } /** * @param geoclass * test for type * @param result * hits object for result * @return array of GeoElements passing test out of hits */ final public Hits getHits(Test geoclass, Hits result) { return getHits(geoclass, false, result); } /** * Returns array of polygons with n points out of hits. * * @return * * final public ArrayList getPolygons(ArrayList hits, int n, * ArrayList polygons) { // search for polygons in hits that exactly * have the needed number of // points polygons.clear(); * getHits(hits, GeoPolygon.class, polygons); for (int k = * polygons.size() - 1; k > 0; k--) { GeoPolygon poly = (GeoPolygon) * polygons.get(k); // remove poly with wrong number of points if (n * != poly.getPoints().length) polygons.remove(k); } return * polygons; } */ /** * Stores all GeoElements of type geoclass to result list. * * @param geoclass * test * * @param other * == true: returns array of GeoElements NOT passing test out of * hits. * @param result * Hits in which the result should be stored * @return result */ final protected Hits getHits(Test geoclass, boolean other, Hits result) { result.clear(); for (int i = 0; i < size(); ++i) { boolean success = geoclass.check(get(i)); if (other) { success = !success; } if (success) { result.add(get(i)); } } // return result.size() == 0 ? null : result; return result; } /** * @param result * hits to store result * @return result */ public final Hits getRegionHits(Hits result) { result.clear(); for (int i = 0; i < size(); ++i) { if (get(i).isRegion()) { result.add(get(i)); } } // return result.size() == 0 ? null : result; return result; } /** * Stores all GeoElements of any of type geoclasses to result list. * * @param geoclasses * test * * @param other * == true: returns array of GeoElements NOT passing any test out * of hits. * @param result * Hits in which the result should be stored * @return result */ final public Hits getHits(Test[] geoclasses, boolean other, Hits result) { result.clear(); for (int i = 0; i < size(); ++i) { for (int j = 0; j < geoclasses.length; ++j) { boolean success = geoclasses[j].check(get(i)); if (other) { success = !success; } if (success) { result.add(get(i)); } } } return result; } /** * return first hit of given class * * @param geoclass * test * @return first hit of given class */ final public GeoElement getFirstHit(Test geoclass) { for (int i = 0; i < size(); ++i) { if (geoclass.check(get(i))) { return get(i); } } return null; } /** * Stores all GeoElements of type GeoPoint, GeoVector, GeoNumeric to result * list. * */ /* * final protected ArrayList getRecordableHits(ArrayList hits, ArrayList * result) { if (hits == null) return null; * * result.clear(); for (int i = 0; i < hits.size(); ++i) { GeoElement hit = * (GeoElement)hits.get(i); boolean success = (hit.isGeoPoint() || * hit.isGeoVector() || hit.isGeoNumeric()); if (success) * result.add(hits.get(i)); } return result.size() == 0 ? null : result; } */ /** * returns array of GeoElements whose visual representation is on top of * screen coords of Point p. If there are points at location p only the * points are returned. Otherwise all GeoElements are returned. * * @see EuclidianController: mousePressed(), mouseMoved() */ /* * final public ArrayList getTopHits(Point p) { return * getTopHits(getHits(p)); } */ /** * if there are GeoPoints in hits, all these points are returned. Otherwise * hits is returned. * * @return list of hit points * * @see EuclidianController#wrapMousePressed(AbstractEvent) * @see EuclidianController#wrapMouseMoved(AbstractEvent) */ public Hits getTopHits() { if (isEmpty()) { return cloneHits(); } // point in there? Hits topHitsList = new Hits(); if (containsGeoPoint(topHitsList)) { // Hits topHitsList = new Hits(); getHits(Test.GEOPOINTND, false, topHitsList); return topHitsList; } if (containsGeoTextfield(topHitsList)) { getHits(Test.GEOTEXTFIELD, false, topHitsList); return topHitsList; } if (containsComboBox(topHitsList)) { getHits(Test.GEOLIST_AS_COMBO, false, topHitsList); return topHitsList; } // text in there? if (containsGeoText(topHitsList)) { getHits(Test.GEOTEXT, false, topHitsList); return topHitsList; } if (containsGeoNumeric()) { getHits(Test.GEONUMERIC, false, topHitsList); return topHitsList; } return cloneHits(); } /** * return hits at the top, limited to a number of nb * * @param nb * number of top hits to return * @return hits at the top, limited to a number of nb */ public Hits getTopHits(int nb) { Hits topHits = getTopHits(); /* * //remove all last elements, since topHits.size()<=nb * for(;topHits.size()>nb;) topHits.remove(topHits.size()-1); */ Hits ret = new Hits(); for (int i = 0; i < nb && i < topHits.size(); i++) { ret.add(topHits.get(i)); } return ret; } /** * @param nb * maximal number of hits * @return first at most nb hits */ public Hits getHits(int nb) { Hits ret = createNewHits(); for (int i = 0; i < nb && i < size(); i++) { ret.add(get(i)); } return ret; } /** * @return creates new instance of this class */ protected Hits createNewHits() { return new Hits(); } /** * @param depth * for 3D * @param geoN * maximal number of returned geos * @return top hits */ public Hits getTopHits(int depth, int geoN) { return getTopHits(geoN); } /** * @return true if contains GeoPointND */ final public boolean containsGeoPoint() { for (int i = 0; i < size(); i++) { if (get(i).isGeoPoint()) { return true; } } return false; } final private boolean containsGeoNumeric() { for (int i = 0; i < size(); i++) { if (get(i).isGeoNumeric()) { return true; } } return false; } /** * @param ret * if the point is found, it is added into ret * @return true if contains GeoPointND */ final public boolean containsGeoPoint(Hits ret) { GeoElement geo; for (int i = 0; i < size(); i++) { geo = get(i); if (geo.isGeoPoint()) { ret.add(geo); return true; } } return false; } /** * @param ret * if the point is found, it is added into ret * @return true if contains GeoPointND */ final public boolean containsGeoText(Hits ret) { GeoElement geo; for (int i = 0; i < size(); i++) { geo = get(i); if (geo.isGeoText()) { ret.add(geo); return true; } } return false; } /** * @param ret * hits * @return whether gits contain input box */ final public boolean containsGeoTextfield(Hits ret) { GeoElement geo; for (int i = 0; i < size(); i++) { geo = get(i); if (geo.isGeoInputBox()) { ret.add(geo); return true; } } return false; } /** * @param ret * hits * @return whether hits contain combobox */ final public boolean containsComboBox(Hits ret) { GeoElement geo; for (int i = 0; i < size(); i++) { geo = get(i); if (geo.isGeoList() && ((GeoList) geo).drawAsComboBox()) { ret.add(geo); return true; } } return false; } @Override public String toString() { StringBuilder s = new StringBuilder("hits: "); s.append(size()); GeoElement geo; for (int i = 0; i < size(); i++) { geo = get(i); s.append("\n hits("); s.append(i); s.append(") = "); s.append(geo.getLabel(StringTemplate.defaultTemplate)); } return s.toString(); } /** * * @param list * geo list * @return true if contains at least one of the elements of the list */ public boolean intersect(ArrayList<GeoElement> list) { for (GeoElement geo : list) { if (contains(geo)) { return true; } } return false; } /** * * @return hits that has finite volume */ public Hits getFiniteVolumeIncludingMetaHits() { Hits result = new Hits(); for (GeoElement geo : this) { // first check if is segment/polygon/quadric side from a geo that // has finite volume if (geo.getMetasLength() > 0) { for (GeoElement meta : ((FromMeta) geo).getMetas()) { addFiniteVolume(result, meta); // check if the geo has finite volume } } else { addFiniteVolume(result, geo); } } return result; } private static void addFiniteVolume(Hits result, GeoElement geo) { if (geo instanceof HasVolume) { if (((HasVolume) geo).hasFiniteVolume()) { result.add(geo); } } } /** * * @return hits that has finite volume */ public Hits getPolyhedronsIncludingMetaHits() { Hits result = new Hits(); for (GeoElement geo : this) { // first check if is segment/polygon/quadric side from a geo that // has finite volume if (geo.getMetasLength() > 0) { for (GeoElement meta : ((FromMeta) geo).getMetas()) { if (meta.isGeoPolyhedron()) { result.add(meta); } } // check if the geo has finite volume } else { if (geo.isGeoPolyhedron()) { result.add(geo); } } } return result; } /** * WARNING : only GeoCoordSys2D, GeoQuadric3DInterface and * GeoPolyhedronInterface implemented yet * * @param ignoredGeos * geos that are ignored * @return hits containing first surface (not included in ignoredGeos) */ final public Hits getFirstSurfaceBefore(ArrayList<GeoElement> ignoredGeos) { Hits ret = new Hits(); for (int i = 0; i < size(); i++) { GeoElement geo = get(i); if (geo instanceof GeoCoordSys2D || geo instanceof GeoQuadric3DInterface || geo instanceof GeoQuadric3DLimitedInterface || geo instanceof GeoPolyhedronInterface || geo instanceof GeoFunctionNVar || geo instanceof GeoImplicitSurfaceND) { if (!ignoredGeos.contains(geo)) { if (geo instanceof GeoQuadric3DPartInterface) { // temporary // fix (TODO // implement // intersection // GeoQuadric3DPart // / plane) GeoElement meta = ((FromMeta) geo).getMetas()[0]; if (!ignoredGeos.contains(meta)) { ret.add(meta); return ret; } } ret.add(geo); return ret; } } } return ret; } /** * remove all polygons, if hits are not all instance of GeoCoordSys2D */ public void removePolygonsIfNotOnlyCS2D() { // String s = "cs2DCount="+cs2DCount+"/"+(size()); if (size() - cs2DCount > 0) { removePolygons(); // s+="\n"+toString(); /* * for (int i = 0; i < size(); ) { GeoElement geo = (GeoElement) * get(i); * * if (geo instanceof GeoCoordSys2D) remove(i); else i++; } */ // Application.debug(s+"\n"+toString()); } } /** * * @return first 6 degrees of freedom moveable geo */ public GeoElement getFirstGeo6dofMoveable() { for (GeoElement geo : this) { if (geo.is6dofMoveable()) { return geo; } } return null; } }