/* * Copyright 2012, 2013 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * * This program 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 3 of the License, or (at your option) any later version. * * This program 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 program. If not, see <http://www.gnu.org/licenses/>. */ package org.oscim.utils.geom; import org.oscim.core.GeometryBuffer; /** * from http://en.wikipedia.org/wiki/Cohen%E2%80%93 * Sutherland_algorithm */ public class LineClipper { public static final int INSIDE = 0; // 0000 public static final int LEFT = 1; // 0001 public static final int RIGHT = 2; // 0010 public static final int BOTTOM = 4; // 0100 public static final int TOP = 8; // 1000 private float xmin, xmax, ymin, ymax; public LineClipper(float minx, float miny, float maxx, float maxy) { this.xmin = minx; this.ymin = miny; this.xmax = maxx; this.ymax = maxy; } public void setRect(float minx, float miny, float maxx, float maxy) { this.xmin = minx; this.ymin = miny; this.xmax = maxx; this.ymax = maxy; } private int mPrevOutcode; private float mPrevX; private float mPrevY; public float outX1; public float outY1; public float outX2; public float outY2; public boolean clipStart(float x0, float y0) { mPrevX = x0; mPrevY = y0; mPrevOutcode = INSIDE; if (x0 < xmin) mPrevOutcode |= LEFT; else if (x0 > xmax) mPrevOutcode |= RIGHT; if (y0 < ymin) mPrevOutcode |= BOTTOM; else if (y0 > ymax) mPrevOutcode |= TOP; return mPrevOutcode == INSIDE; } public int outcode(float x, float y) { int outcode = INSIDE; if (x < xmin) outcode |= LEFT; else if (x > xmax) outcode |= RIGHT; if (y < ymin) outcode |= BOTTOM; else if (y > ymax) outcode |= TOP; return outcode; } /** * @return 0 if not intersection, 1 fully within, -1 clipped (and 'out' set * to new points) */ public int clipNext(float x1, float y1) { int accept; int outcode = INSIDE; if (x1 < xmin) outcode |= LEFT; else if (x1 > xmax) outcode |= RIGHT; if (y1 < ymin) outcode |= BOTTOM; else if (y1 > ymax) outcode |= TOP; if ((mPrevOutcode | outcode) == 0) { // Bitwise OR is 0. Trivially accept accept = 1; } else if ((mPrevOutcode & outcode) != 0) { // Bitwise AND is not 0. Trivially reject accept = 0; } else { accept = clip(mPrevX, mPrevY, x1, y1, mPrevOutcode, outcode) ? -1 : 0; } mPrevOutcode = outcode; mPrevX = x1; mPrevY = y1; return accept; } public int clipSegment(float x1, float y1, float x2, float y2) { clipStart(x1, y1); return clipNext(x2, y2); } /* CohenSutherland clipping algorithm clips a line from * P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with * diagonal from (xmin, ymin) to (xmax, ymax). * based on en.wikipedia.org/wiki/Cohen-Sutherland */ private boolean clip(float x0, float y0, float x1, float y1, int outcode0, int outcode1) { boolean accept = false; while (true) { if ((outcode0 | outcode1) == 0) { /* Bitwise OR is 0. Trivially accept and get out of loop */ accept = true; break; } else if ((outcode0 & outcode1) != 0) { /* Bitwise AND is not 0. Trivially reject and get out of loop */ break; } else { /* failed both tests, so calculate the line segment to clip * from an outside point to an intersection with clip edge */ float x = 0; float y = 0; /* At least one endpoint is outside the clip rectangle; pick it. */ int outcodeOut = (outcode0 == 0) ? outcode1 : outcode0; /* Now find the intersection point; * use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * * (y - y0) */ if ((outcodeOut & TOP) != 0) { /* point is above the clip rectangle */ x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0); y = ymax; } else if ((outcodeOut & BOTTOM) != 0) { /* point is below the clip rectangle */ x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0); y = ymin; } else if ((outcodeOut & RIGHT) != 0) { /* point is to the right of clip rectangle */ y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0); x = xmax; } else if ((outcodeOut & LEFT) != 0) { /* point is to the left of clip rectangle */ y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0); x = xmin; } int outcode = INSIDE; if (x < xmin) outcode |= LEFT; else if (x > xmax) outcode |= RIGHT; if (y < ymin) outcode |= BOTTOM; else if (y > ymax) outcode |= TOP; /* Now we move outside point to intersection point to clip * and get ready for next pass. */ if (outcodeOut == outcode0) { x0 = x; y0 = y; outcode0 = outcode; } else { x1 = x; y1 = y; outcode1 = outcode; } } } if (accept) { outX1 = x0; outY1 = y0; outX2 = x1; outY2 = y1; } return accept; } public float[] getLine(float out[], int offset) { if (out == null) return new float[] { outX1, outY1, outX2, outY2 }; out[offset + 0] = outX1; out[offset + 1] = outY1; out[offset + 2] = outX2; out[offset + 3] = outY2; return out; } public int clipLine(GeometryBuffer in, GeometryBuffer out) { out.clear(); int pointPos = 0; int numLines = 0; for (int i = 0, n = in.index.length; i < n; i++) { int len = in.index[i]; if (len < 0) break; if (len < 4) { pointPos += len; continue; } if (len == 0) { continue; } int inPos = pointPos; int end = inPos + len; float x = in.points[inPos++]; float y = in.points[inPos++]; boolean inside = clipStart(x, y); if (inside) { out.startLine(); out.addPoint(x, y); numLines++; } while (inPos < end) { /* get the current way point coordinates */ x = in.points[inPos++]; y = in.points[inPos++]; int clip = clipNext(x, y); if (clip == 0) { /* current segment is fully outside */ inside = false; // needed? } else if (clip == 1) { /* current segment is fully within */ out.addPoint(x, y); } else { /* clip == -1 */ if (inside) { /* previous was inside */ out.addPoint(outX2, outY2); } else { /* previous was outside */ out.startLine(); numLines++; out.addPoint(outX1, outY1); out.addPoint(outX2, outY2); } inside = clipStart(x, y); } } pointPos = end; } return numLines; } }