package org.geogebra.common.euclidian.clipping; import org.geogebra.common.awt.GPoint2D; //============================================================================ //File: Clipping.java // //Project: DXF Viewer and general purpose // //Purpose: Workaround for Java 1.2/1.3 problem with line drawing // //Author: Rammi // //Copyright Notice: (c) 2000 Rammi (rammi@caff.de) // This source code is in the public domain. // USE AT YOUR OWN RISK! // //Version History: // Oct 27, 2000: First release // // May 17, 2010: Bug fix repairing incorrect results if // lower corner is hit //============================================================================= //package de.caff.gimmicks; //import java.awt.geom.Point2D; //import geogebra.common.awt.Point2D; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.kernel.Kernel; /** * Clipping of lines to the inside of a rectangle. This is useful as a * workaround for Java bug id 4252578 (i.e. the JVM hangs when you try to draw a * line with starting and/or end point a long distance outside the image area) * which came in with JDK 1.2 and is still there in JDK 1.3. It's also useful * because all Java versions have problems with lines outside the image area * which are sometimes drawn completely wrong. * * @author Rammi (rammi@caff.de) */ public class ClipLine { // some constants /** Flag for point lying left of clipping area. */ public final static int LEFT = 0x01; /** Flag for point lying between horizontal bounds of area. */ public final static int H_CENTER = 0x02; /** Flag for point lying right of clipping area. */ public final static int RIGHT = 0x04; /** Flag for point lying "below" clipping area. */ public final static int BELOW = 0x10; /** Flag for point lying between vertical bounds of clipping area. */ public final static int V_CENTER = 0x20; /** Flag for point lying "above" clipping area. */ public final static int ABOVE = 0x40; /** Mask for points which are inside. */ public final static int INSIDE = H_CENTER | V_CENTER; /** Mask for points which are outside. */ public final static int OUTSIDE = LEFT | RIGHT | BELOW | ABOVE; /** * Calculate the clipping points of a line with a rectangle. * * @param x1 * starting x of line * @param y1 * starting y of line * @param x2 * ending x of line * @param y2 * ending y of line * @param xmin * lower left x of rectangle * @param xmax * upper right x of rectangle * @param ymin * lower left y of rectangle * @param ymax * upper right y of rectangle * @return <code>null</code> (does not clip) or array of two points */ public static GPoint2D[] getClipped(double x1, double y1, double x2, double y2, int xmin, int xmax, int ymin, int ymax) { int mask1 = 0; // position mask for first point int mask2 = 0; // position mask for second point if (x1 < xmin) { mask1 |= LEFT; } else if (x1 >= xmax) { mask1 |= RIGHT; } else { mask1 |= H_CENTER; } if (y1 < ymin) { // btw: I know that in AWT y runs from down but I more used to // y pointing up and it makes no difference for the algorithms mask1 |= BELOW; } else if (y1 >= ymax) { mask1 |= ABOVE; } else { mask1 |= V_CENTER; } if (x2 < xmin) { mask2 |= LEFT; } else if (x2 >= xmax) { mask2 |= RIGHT; } else { mask2 |= H_CENTER; } if (y2 < ymin) { mask2 |= BELOW; } else if (y2 >= ymax) { mask2 |= ABOVE; } else { mask2 |= V_CENTER; } int mask = mask1 | mask2; if ((mask & OUTSIDE) == 0) { // fine. everything's internal GPoint2D[] ret = new GPoint2D[2]; ret[0] = AwtFactory.getPrototype().newPoint2D(x1, y1); ret[1] = AwtFactory.getPrototype().newPoint2D(x2, y2); return ret; } else if ((mask & (H_CENTER | LEFT)) == 0 || // everything's right (mask & (H_CENTER | RIGHT)) == 0 || // everything's left (mask & (V_CENTER | BELOW)) == 0 || // everything's above (mask & (V_CENTER | ABOVE)) == 0) { // everything's below // nothing to do return null; } else { // need clipping return getClipped(x1, y1, mask1, x2, y2, mask2, xmin, xmax, ymin, ymax); } } /** * Calculate the clipping points of a line with a rectangle. * * @param x1 * starting x of line * @param y1 * starting y of line * @param mask1 * clipping info mask for starting point * @param x2 * ending x of line * @param y2 * ending y of line * @param mask2 * clipping info mask for ending point * @param xmin * lower left x of rectangle * @param ymin * lower left y of rectangle * @param xmax * upper right x of rectangle * @param ymax * upper right y of rectangle * @return <code>null</code> (does not clip) or array of two points */ protected static GPoint2D[] getClipped(double x1, double y1, int mask1, double x2, double y2, int mask2, double xmin, double xmax, double ymin, double ymax) { int mask = mask1 ^ mask2; GPoint2D p1 = null; /* * System.out.println("mask1 = "+mask1); System.out.println("mask2 = " * +mask2); System.out.println("mask = "+mask); */ double xhack = 0; double yhack = 0; if (mask1 == INSIDE) { // point 1 is internal p1 = AwtFactory.getPrototype().newPoint2D((x1 + xhack), (y1 + yhack)); if (mask == 0) { // both masks are the same, so the second point is inside, too GPoint2D[] ret = new GPoint2D[2]; ret[0] = p1; ret[1] = AwtFactory.getPrototype().newPoint2D((x2 + xhack), (y2 + yhack)); return ret; } } else if (mask2 == INSIDE) { // point 2 is internal p1 = AwtFactory.getPrototype().newPoint2D((x2 + xhack), (y2 + yhack)); } else if (mask == 0) { // shortcut: no point is inside, but both are in the same sector, so // no intersection is possible return null; } if ((mask & LEFT) != 0) { // System.out.println("Trying left"); // try to calculate intersection with left line GPoint2D p = intersect(x1, y1, x2, y2, xmin, ymin, xmin, ymax); if (p != null) { if (p1 == null) { p1 = p; } else { GPoint2D[] ret = new GPoint2D[2]; ret[0] = p1; ret[1] = p; return ret; } } } if ((mask & RIGHT) != 0) { // System.out.println("Trying right"); // try to calculate intersection with right line GPoint2D p = intersect(x1, y1, x2, y2, xmax, ymin, xmax, ymax); if (p != null) { if (p1 == null) { p1 = p; } else { GPoint2D[] ret = new GPoint2D[2]; ret[0] = p1; ret[1] = p; return ret; } } } if (p1 != null && Kernel.isEqual(p1.getY(), (ymin + yhack))) { // use different sequence if a lower corner of clipping rectangle is // hit if ((mask & ABOVE) != 0) { // System.out.println("Trying top"); // try to calculate intersection with upper line GPoint2D p = intersect(x1, y1, x2, y2, xmin, ymax, xmax, ymax); if (p != null) { GPoint2D[] ret = new GPoint2D[2]; ret[0] = p1; ret[1] = p; return ret; } } if ((mask & BELOW) != 0) { // System.out.println("Trying bottom"); // try to calculate intersection with lower line GPoint2D p = intersect(x1, y1, x2, y2, xmin, ymin, xmax, ymin); if (p != null) { GPoint2D[] ret = new GPoint2D[2]; ret[0] = p1; ret[1] = p; return ret; } } } else { if ((mask & BELOW) != 0) { // System.out.println("Trying bottom"); // try to calculate intersection with lower line GPoint2D p = intersect(x1, y1, x2, y2, xmin, ymin, xmax, ymin); if (p != null) { if (p1 == null) { p1 = p; } else { GPoint2D[] ret = new GPoint2D[2]; ret[0] = p1; ret[1] = p; return ret; } } } if ((mask & ABOVE) != 0) { // System.out.println("Trying top"); // try to calculate intersection with upper line GPoint2D p = intersect(x1, y1, x2, y2, xmin, ymax, xmax, ymax); if (p != null) { if (p1 == null) { p1 = p; } else { GPoint2D[] ret = new GPoint2D[2]; ret[0] = p1; ret[1] = p; return ret; } } } } // no (or not enough) intersections found return null; } /** * Intersect two lines. * * @param x11 * starting x of 1st line * @param y11 * starting y of 1st line * @param x12 * ending x of 1st line * @param y12 * ending y of 1st line * @param x21 * starting x of 2nd line * @param y21 * starting y of 2nd line * @param x22 * ending x of 2nd line * @param y22 * ending y of 2nd line * @return intersection point or <code>null</code> */ private static GPoint2D intersect(double x11, double y11, double x12, double y12, double x21, double y21, double x22, double y22) { double dx1 = x12 - x11; double dy1 = y12 - y11; double dx2 = x22 - x21; double dy2 = y22 - y21; double det = (dx2 * dy1 - dy2 * dx1); // line vertical: no 0.5 hack double xhack = 0; // line horizontal: no hack double yhack = 0; if (det != 0.0) { double mu = ((x11 - x21) * dy1 - (y11 - y21) * dx1) / det; // System.out.println("mu = "+mu); if (mu >= 0.0 && mu <= 1.0) { GPoint2D p = AwtFactory.getPrototype().newPoint2D( (x21 + mu * dx2 + xhack), (y21 + mu * dy2 + yhack)); // System.out.println("p = "+p); return p; } } return null; } }