/** * Copyright (C) 2016 eBusiness Information * * This file is part of OSM Contributor. * * OSM Contributor 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, either version 3 of the License, or * (at your option) any later version. * * OSM Contributor 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSM Contributor. If not, see <http://www.gnu.org/licenses/>. */ package io.jawg.osmcontributor.utils.ways; import android.graphics.Rect; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Clipper { private interface Function1<R, P> { R apply(P param); } private static class Edge { final XY pointA, pointB; final Function1<Boolean, XY> insideCheck; public Edge(XY pointA, XY pointB, Function1<Boolean, XY> insideCheck) { this.pointA = pointA; this.pointB = pointB; this.insideCheck = insideCheck; } @Override public String toString() { return "Edge{" + "pointA=" + pointA + ", pointB=" + pointB + '}'; } } Edge[] edges = new Edge[4]; public Clipper(Rect clippingBounds) { setClippingBounds(clippingBounds); } public Clipper() { } public void setClippingBounds(final Rect clippingBounds) { edges[0] = new Edge(new XY(clippingBounds.left, clippingBounds.top), new XY(clippingBounds.right, clippingBounds.top), new Function1<Boolean, XY>() { @Override public Boolean apply(XY param) { return param.getY() > clippingBounds.top; } }); edges[1] = new Edge(new XY(clippingBounds.right, clippingBounds.top), new XY(clippingBounds.right, clippingBounds.bottom), new Function1<Boolean, XY>() { @Override public Boolean apply(XY param) { return param.getX() < clippingBounds.right; } }); edges[2] = new Edge(new XY(clippingBounds.right, clippingBounds.bottom), new XY(clippingBounds.left, clippingBounds.bottom), new Function1<Boolean, XY>() { @Override public Boolean apply(XY param) { return param.getY() < clippingBounds.bottom; } }); edges[3] = new Edge(new XY(clippingBounds.left, clippingBounds.bottom), new XY(clippingBounds.left, clippingBounds.top), new Function1<Boolean, XY>() { @Override public Boolean apply(XY param) { return param.getX() > clippingBounds.left; } }); } /** * Lists used in the algorithm, we use the same lists for each invocation of the clipping method to minimise memory allocations. */ private List<XY> resultList = new ArrayList<>(100); private List<XY> tmpList = new ArrayList<>(100); /** * Sutherland–Hodgman algorithm for clipping polygons. * Clip the polygon or line to the Rect of the Clipper * * This method is not threadsafe * * @param pointsToClip list of XY points forming a line or polygon to clip * @param isPolygon * @return list of XY points forming the clipped line or polygon, for memory allocations reasons, * this list will be modified on the next call to the clip method, be sure you make a copy of it * if you need to keep it ! */ public List<XY> clip(List<XY> pointsToClip, boolean isPolygon) { resultList.clear(); resultList.addAll(pointsToClip); for (Edge edge : edges) { tmpList.clear(); tmpList.addAll(resultList); resultList.clear(); if (tmpList.isEmpty()) { return Collections.emptyList(); } XY s; if (isPolygon) { s = tmpList.get(tmpList.size() - 1); } else { s = tmpList.get(0); } for (XY e : tmpList) { if (edge.insideCheck.apply(e)) { if (!edge.insideCheck.apply(s)) { resultList.add(intersection(s, e, edge.pointA, edge.pointB)); } resultList.add(e); } else if (edge.insideCheck.apply(s)) { resultList.add(intersection(s, e, edge.pointA, edge.pointB)); } s = e; } } return resultList; } /** * Computes the existing intersection of two lines. The caller must ensure the intersection exists * * @param line1point1 first point on first line * @param line1point2 second point on first line * @param line2point1 first point on second line * @param line2point2 second point on second line * @return the intersection point */ XY intersection(XY line1point1, XY line1point2, XY line2point1, XY line2point2) { double line1Denominator = line1point2.getX() - line1point1.getX(); double line2Denominator = line2point2.getX() - line2point1.getX(); double a = (line1point2.getY() - line1point1.getY()) / line1Denominator; double b = line1point1.getY() - a * line1point1.getX(); double c = (line2point2.getY() - line2point1.getY()) / line2Denominator; double d = line2point1.getY() - c * line2point1.getX(); double x = line1Denominator == 0 ? line1point1.getX() : (line2Denominator == 0 ? line2point1.getX() : -(b - d) / (a - c)); double y = line1Denominator == 0 ? c * x + d : a * x + b; return new XY(x, y); } }