/* * __ .__ .__ ._____. * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ * |__| \____/__/\_ \__|\___ >____/__||___ /____ > * \/ \/ \/ \/ * * Copyright (c) 2006-2011 Karsten Schmidt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * http://creativecommons.org/licenses/LGPL/2.1/ * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ package toxi.geom; import java.util.ArrayList; import java.util.List; /** * A version of the Sutherland-Hodgeman algorithm to clip 2D polygons optimized * for rectangular clipping regions. * * More information: http://en.wikipedia.org/wiki/Sutherland-Hodgman_algorithm * * @see ConvexPolygonClipper */ public class SutherlandHodgemanClipper implements PolygonClipper2D { /** * */ protected Rect bounds; /** * * @param bounds */ public SutherlandHodgemanClipper(Rect bounds) { this.bounds = bounds; } @Override public Polygon2D clipPolygon(Polygon2D poly) { List points = new ArrayList<>(poly.vertices); List clipped = new ArrayList<>(); points.add(points.get(0)); for (int edgeID = 0; edgeID < 4; edgeID++) { clipped.clear(); for (int i = 0, num = points.size() - 1; i < num; i++) { Vec2D p = points.get(i); Vec2D q = points.get(i + 1); if (isInsideEdge(p, edgeID)) { if (isInsideEdge(q, edgeID)) { clipped.add(q.copy()); } else { clipped.add(getClippedPosOnEdge(edgeID, p, q)); } continue; } if (isInsideEdge(q, edgeID)) { clipped.add(getClippedPosOnEdge(edgeID, p, q)); clipped.add(q.copy()); } } if (clipped.size() > 0 && clipped.get(0) != clipped.get(clipped.size() - 1)) { clipped.add(clipped.get(0)); } List t = points; points = clipped; clipped = t; } return new Polygon2D(points).removeDuplicates(0.001f); } /** * @return the bounding rect */ public Rect getBounds() { return bounds; } private Vec2D getClippedPosOnEdge(int edgeID, Vec2D p1, Vec2D p2) { switch (edgeID) { case 0: return new Vec2D(p1.x + ((bounds.y - p1.y) * (p2.x - p1.x)) / (p2.y - p1.y), bounds.y); case 2: float by = bounds.y + bounds.height; return new Vec2D(p1.x + ((by - p1.y) * (p2.x - p1.x)) / (p2.y - p1.y), by); case 1: float bx = bounds.x + bounds.width; return new Vec2D(bx, p1.y + ((bx - p1.x) * (p2.y - p1.y)) / (p2.x - p1.x)); case 3: return new Vec2D(bounds.x, p1.y + ((bounds.x - p1.x) * (p2.y - p1.y)) / (p2.x - p1.x)); default: return null; } } private boolean isInsideEdge(Vec2D p, int edgeID) { switch (edgeID) { case 0: return p.y >= bounds.y; case 2: return p.y < bounds.y + bounds.height; case 3: return p.x >= bounds.x; case 1: return p.x < bounds.x + bounds.width; default: return false; } } /** * * @param list * @param q * @return */ protected boolean isKnownVertex(List list, Vec2D q) { return list.stream().anyMatch((p) -> (p.equalsWithTolerance(q, 0.0001f))); } /** * @param bounds * the bounding rect to set */ public void setBounds(Rect bounds) { this.bounds = bounds; } }