/*
* @(#)Geom.java
*
* Copyright (c) 1996-2010 The authors and contributors of JHotDraw.
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.geom;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.awt.*;
import java.awt.geom.*;
import static java.lang.Math.*;
/**
* Some geometric utilities.
*
* @version $Id$
*/
public class Geom {
private Geom() {
} // never instantiated
/**
* Tests if a point is on a line.
*/
public static boolean lineContainsPoint(int x1, int y1,
int x2, int y2,
int px, int py) {
return lineContainsPoint(x1, y1, x2, y2, px, py, 3d);
}
/**
* Tests if a point is on a line.
* <p>changed Werner Randelshofer 2003-11-26
*/
public static boolean lineContainsPoint(int x1, int y1,
int x2, int y2,
int px, int py, double tolerance) {
Rectangle r = new Rectangle(new Point(x1, y1));
r.add(x2, y2);
r.grow(max(2, (int) ceil(tolerance)), max(2, (int) ceil(tolerance)));
if (!r.contains(px, py)) {
return false;
}
double a, b, x, y;
if (x1 == x2) {
return (abs(px - x1) <= tolerance);
}
if (y1 == y2) {
return (abs(py - y1) <= tolerance);
}
a = (double) (y1 - y2) / (double) (x1 - x2);
b = (double) y1 - a * (double) x1;
x = (py - b) / a;
y = a * px + b;
return (min(abs(x - px), abs(y - py)) <= tolerance);
}
/**
* Tests if a point is on a line.
* <p>changed Werner Randelshofer 2003-11-26
*/
public static boolean lineContainsPoint(double x1, double y1,
double x2, double y2,
double px, double py, double tolerance) {
Rectangle2D.Double r = new Rectangle2D.Double(x1, y1, 0, 0);
r.add(x2, y2);
double grow = max(2, (int) ceil(tolerance));
r.x -= grow;
r.y -= grow;
r.width += grow * 2;
r.height += grow * 2;
if (!r.contains(px, py)) {
return false;
}
double a, b, x, y;
if (x1 == x2) {
return (abs(px - x1) <= tolerance);
}
if (y1 == y2) {
return (abs(py - y1) <= tolerance);
}
a = (y1 - y2) / (x1 - x2);
b = y1 - a * x1;
x = (py - b) / a;
y = a * px + b;
return (min(abs(x - px), abs(y - py)) <= tolerance);
}
/** The bitmask that indicates that a point lies above the rectangle. */
public static final int OUT_TOP = Rectangle2D.OUT_TOP;
/** The bitmask that indicates that a point lies below the rectangle. */
public static final int OUT_BOTTOM = Rectangle2D.OUT_BOTTOM;
/** The bitmask that indicates that a point lies to the left of the rectangle. */
public static final int OUT_LEFT = Rectangle2D.OUT_LEFT;
/** The bitmask that indicates that a point lies to the right of the rectangle. */
public static final int OUT_RIGHT = Rectangle2D.OUT_RIGHT;
/**
* Returns the direction OUT_TOP, OUT_BOTTOM, OUT_LEFT, OUT_RIGHT from
* one point to another one.
*/
public static int direction(int x1, int y1, int x2, int y2) {
int direction = 0;
int vx = x2 - x1;
int vy = y2 - y1;
if (vy < vx && vx > -vy) {
direction = OUT_RIGHT;
} else if (vy > vx && vy > -vx) {
direction = OUT_TOP;
} else if (vx < vy && vx < -vy) {
direction = OUT_LEFT;
} else {
direction = OUT_BOTTOM;
}
return direction;
}
/**
* Returns the direction OUT_TOP, OUT_BOTTOM, OUT_LEFT, OUT_RIGHT from
* one point to another one.
*/
public static int direction(double x1, double y1, double x2, double y2) {
int direction = 0;
double vx = x2 - x1;
double vy = y2 - y1;
if (vy < vx && vx > -vy) {
direction = OUT_RIGHT;
} else if (vy > vx && vy > -vx) {
direction = OUT_TOP;
} else if (vx < vy && vx < -vy) {
direction = OUT_LEFT;
} else {
direction = OUT_BOTTOM;
}
return direction;
}
/**
* This method computes a binary OR of the appropriate mask values
* indicating, for each side of Rectangle r1, whether or not the
* Rectangle r2 is on the same side of the edge as the rest
* of this Rectangle.
*
*
*
*
*
*
*
*
* @return the logical OR of all appropriate out codes OUT_RIGHT, OUT_LEFT, OUT_BOTTOM,
* OUT_TOP.
*/
public static int outcode(Rectangle r1, Rectangle r2) {
int outcode = 0;
if (r2.x > r1.x + r1.width) {
outcode = OUT_RIGHT;
} else if (r2.x + r2.width < r1.x) {
outcode = OUT_LEFT;
}
if (r2.y > r1.y + r1.height) {
outcode |= OUT_BOTTOM;
} else if (r2.y + r2.height < r1.y) {
outcode |= OUT_TOP;
}
return outcode;
}
/**
* This method computes a binary OR of the appropriate mask values
* indicating, for each side of Rectangle r1, whether or not the
* Rectangle r2 is on the same side of the edge as the rest
* of this Rectangle.
*
*
*
*
*
*
*
*
* @return the logical OR of all appropriate out codes OUT_RIGHT, OUT_LEFT, OUT_BOTTOM,
* OUT_TOP.
*/
public static int outcode(Rectangle2D.Double r1, Rectangle2D.Double r2) {
int outcode = 0;
if (r2.x > r1.x + r1.width) {
outcode = OUT_RIGHT;
} else if (r2.x + r2.width < r1.x) {
outcode = OUT_LEFT;
}
if (r2.y > r1.y + r1.height) {
outcode |= OUT_BOTTOM;
} else if (r2.y + r2.height < r1.y) {
outcode |= OUT_TOP;
}
return outcode;
}
public static Point south(Rectangle r) {
return new Point(r.x + r.width / 2, r.y + r.height);
}
public static Point2D.Double south(Rectangle2D.Double r) {
return new Point2D.Double(r.x + r.width / 2, r.y + r.height);
}
public static Point center(Rectangle r) {
return new Point(r.x + r.width / 2, r.y + r.height / 2);
}
public static Point2D.Double center(Rectangle2D.Double r) {
return new Point2D.Double(r.x + r.width / 2, r.y + r.height / 2);
}
/**
* Returns a point on the edge of the shape which crosses the line
* from the center of the shape to the specified point.
* If no edge crosses of the shape crosses the line, the nearest control
* point of the shape is returned.
*/
public static Point2D.Double chop(Shape shape, Point2D.Double p) {
Rectangle2D bounds = shape.getBounds2D();
Point2D.Double ctr = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
// Chopped point
double cx = -1;
double cy = -1;
double len = Double.MAX_VALUE;
// Try for points along edge
PathIterator i = shape.getPathIterator(new AffineTransform(), 1);
double[] coords = new double[6];
double prevX = coords[0];
double prevY = coords[1];
double moveToX = prevX;
double moveToY = prevY;
i.next();
for (; !i.isDone(); i.next()) {
switch (i.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
moveToX = coords[0];
moveToY = coords[1];
break;
case PathIterator.SEG_CLOSE:
coords[0] = moveToX;
coords[1] = moveToY;
break;
}
Point2D.Double chop = Geom.intersect(
prevX, prevY,
coords[0], coords[1],
p.x, p.y,
ctr.x, ctr.y);
if (chop != null) {
double cl = Geom.length2(chop.x, chop.y, p.x, p.y);
if (cl < len) {
len = cl;
cx = chop.x;
cy = chop.y;
}
}
prevX = coords[0];
prevY = coords[1];
}
/*
if (isClosed() && size() > 1) {
Node first = get(0);
Node last = get(size() - 1);
Point2D.Double chop = Geom.intersect(
first.x[0], first.y[0],
last.x[0], last.y[0],
p.x, p.y,
ctr.x, ctr.y
);
if (chop != null) {
double cl = Geom.length2(chop.x, chop.y, p.x, p.y);
if (cl < len) {
len = cl;
cx = chop.x;
cy = chop.y;
}
}
}*/
// if none found, pick closest vertex
if (len == Double.MAX_VALUE) {
i = shape.getPathIterator(new AffineTransform(), 1);
for (; !i.isDone(); i.next()) {
i.currentSegment(coords);
double l = Geom.length2(ctr.x, ctr.y, coords[0], coords[1]);
if (l < len) {
len = l;
cx = coords[0];
cy = coords[1];
}
}
}
return new Point2D.Double(cx, cy);
}
public static Point west(Rectangle r) {
return new Point(r.x, r.y + r.height / 2);
}
public static Point2D.Double west(Rectangle2D.Double r) {
return new Point2D.Double(r.x, r.y + r.height / 2);
}
public static Point east(Rectangle r) {
return new Point(r.x + r.width, r.y + r.height / 2);
}
public static Point2D.Double east(Rectangle2D.Double r) {
return new Point2D.Double(r.x + r.width, r.y + r.height / 2);
}
public static Point north(Rectangle r) {
return new Point(r.x + r.width / 2, r.y);
}
public static Point2D.Double north(Rectangle2D.Double r) {
return new Point2D.Double(r.x + r.width / 2, r.y);
}
/**
* Constains a value to the given range.
* @return the constrained value
*/
public static int range(int min, int max, int value) {
if (value < min) {
value = min;
}
if (value > max) {
value = max;
}
return value;
}
/**
* Constains a value to the given range.
* @return the constrained value
*/
public static double range(double min, double max, double value) {
if (value < min) {
value = min;
}
if (value > max) {
value = max;
}
return value;
}
/**
* Gets the square distance between two points.
*/
public static long length2(int x1, int y1, int x2, int y2) {
return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
}
/**
* Gets the distance between to points
*/
public static long length(int x1, int y1, int x2, int y2) {
return (long) sqrt(length2(x1, y1, x2, y2));
}
/**
* Gets the square distance between two points.
*/
public static double length2(double x1, double y1, double x2, double y2) {
return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
}
/**
* Gets the distance between to points
*/
public static double length(double x1, double y1, double x2, double y2) {
return sqrt(length2(x1, y1, x2, y2));
}
/**
* Gets the distance between to points
*/
public static double length(Point2D.Double p1, Point2D.Double p2) {
return sqrt(length2(p1.x, p1.y, p2.x, p2.y));
}
/**
* Caps the line defined by p1 and p2 by the number of units
* specified by radius.
* @return A new end point for the line.
*/
public static Point2D.Double cap(Point2D.Double p1, Point2D.Double p2, double radius) {
double angle = PI / 2 - atan2(p2.x - p1.x, p2.y - p1.y);
Point2D.Double p3 = new Point2D.Double(
p2.x + radius * cos(angle),
p2.y + radius * sin(angle));
return p3;
}
/**
* Gets the angle of a point relative to a rectangle.
*/
public static double pointToAngle(Rectangle r, Point p) {
int px = p.x - (r.x + r.width / 2);
int py = p.y - (r.y + r.height / 2);
return atan2(py * r.width, px * r.height);
}
/**
* Gets the angle of a point relative to a rectangle.
*/
public static double pointToAngle(Rectangle2D.Double r, Point2D.Double p) {
double px = p.x - (r.x + r.width / 2);
double py = p.y - (r.y + r.height / 2);
return atan2(py * r.width, px * r.height);
}
/**
* Gets the angle of the specified line.
*/
public static double angle(double x1, double y1, double x2, double y2) {
return atan2(y2 - y1, x2 - x1);
}
/**
* Gets the point on a rectangle that corresponds to the given angle.
*/
public static Point angleToPoint(Rectangle r, double angle) {
double si = sin(angle);
double co = cos(angle);
double e = 0.0001;
int x = 0, y = 0;
if (abs(si) > e) {
x = (int) ((1.0 + co / abs(si)) / 2.0 * r.width);
x = range(0, r.width, x);
} else if (co >= 0.0) {
x = r.width;
}
if (abs(co) > e) {
y = (int) ((1.0 + si / abs(co)) / 2.0 * r.height);
y = range(0, r.height, y);
} else if (si >= 0.0) {
y = r.height;
}
return new Point(r.x + x, r.y + y);
}
/**
* Gets the point on a rectangle that corresponds to the given angle.
*/
public static Point2D.Double angleToPoint(Rectangle2D.Double r, double angle) {
double si = sin(angle);
double co = cos(angle);
double e = 0.0001;
double x = 0, y = 0;
if (abs(si) > e) {
x = (1.0 + co / abs(si)) / 2.0 * r.width;
x = range(0, r.width, x);
} else if (co >= 0.0) {
x = r.width;
}
if (abs(co) > e) {
y = (1.0 + si / abs(co)) / 2.0 * r.height;
y = range(0, r.height, y);
} else if (si >= 0.0) {
y = r.height;
}
return new Point2D.Double(r.x + x, r.y + y);
}
/**
* Converts a polar to a point
*/
public static Point polarToPoint(double angle, double fx, double fy) {
double si = sin(angle);
double co = cos(angle);
return new Point((int) (fx * co + 0.5), (int) (fy * si + 0.5));
}
/**
* Converts a polar to a point
*/
public static Point2D.Double polarToPoint2D(double angle, double fx, double fy) {
double si = sin(angle);
double co = cos(angle);
return new Point2D.Double(fx * co + 0.5, fy * si + 0.5);
}
/**
* Gets the point on an oval that corresponds to the given angle.
*/
public static Point ovalAngleToPoint(Rectangle r, double angle) {
Point center = Geom.center(r);
Point p = Geom.polarToPoint(angle, r.width / 2, r.height / 2);
return new Point(center.x + p.x, center.y + p.y);
}
/**
* Gets the point on an oval that corresponds to the given angle.
*/
public static Point2D.Double ovalAngleToPoint(Rectangle2D.Double r, double angle) {
Point2D.Double center = Geom.center(r);
Point2D.Double p = Geom.polarToPoint2D(angle, r.width / 2, r.height / 2);
return new Point2D.Double(center.x + p.x, center.y + p.y);
}
/**
* Standard line intersection algorithm
* Return the point of intersection if it exists, else null.
**/
@Nullable
public static Point intersect(int xa, // line 1 point 1 x
// from Doug Lea's PolygonFigure
int ya, // line 1 point 1 y
int xb, // line 1 point 2 x
int yb, // line 1 point 2 y
int xc, // line 2 point 1 x
int yc, // line 2 point 1 y
int xd, // line 2 point 2 x
int yd) { // line 2 point 2 y
// source: http://vision.dai.ed.ac.uk/andrewfg/c-g-a-faq.html
// eq: for lines AB and CD
// (YA-YC)(XD-XC)-(XA-XC)(YD-YC)
// r = ----------------------------- (eqn 1)
// (XB-XA)(YD-YC)-(YB-YA)(XD-XC)
//
// (YA-YC)(XB-XA)-(XA-XC)(YB-YA)
// s = ----------------------------- (eqn 2)
// (XB-XA)(YD-YC)-(YB-YA)(XD-XC)
// XI = XA + r(XB-XA)
// YI = YA + r(YB-YA)
double denom = ((xb - xa) * (yd - yc) - (yb - ya) * (xd - xc));
double rnum = ((ya - yc) * (xd - xc) - (xa - xc) * (yd - yc));
if (denom == 0.0) { // parallel
if (rnum == 0.0) { // coincident; pick one end of first line
if ((xa < xb && (xb < xc || xb < xd))
|| (xa > xb && (xb > xc || xb > xd))) {
return new Point(xb, yb);
} else {
return new Point(xa, ya);
}
} else {
return null;
}
}
double r = rnum / denom;
double snum = ((ya - yc) * (xb - xa) - (xa - xc) * (yb - ya));
double s = snum / denom;
if (0.0 <= r && r <= 1.0 && 0.0 <= s && s <= 1.0) {
int px = (int) (xa + (xb - xa) * r);
int py = (int) (ya + (yb - ya) * r);
return new Point(px, py);
} else {
return null;
}
}
/**
* Standard line intersection algorithm
* Return the point of intersection if it exists, else null
**/
// from Doug Lea's PolygonFigure
@Nullable
public static Point2D.Double intersect(double xa, // line 1 point 1 x
double ya, // line 1 point 1 y
double xb, // line 1 point 2 x
double yb, // line 1 point 2 y
double xc, // line 2 point 1 x
double yc, // line 2 point 1 y
double xd, // line 2 point 2 x
double yd) { // line 2 point 2 y
// source: http://vision.dai.ed.ac.uk/andrewfg/c-g-a-faq.html
// eq: for lines AB and CD
// (YA-YC)(XD-XC)-(XA-XC)(YD-YC)
// r = ----------------------------- (eqn 1)
// (XB-XA)(YD-YC)-(YB-YA)(XD-XC)
//
// (YA-YC)(XB-XA)-(XA-XC)(YB-YA)
// s = ----------------------------- (eqn 2)
// (XB-XA)(YD-YC)-(YB-YA)(XD-XC)
// XI = XA + r(XB-XA)
// YI = YA + r(YB-YA)
double denom = ((xb - xa) * (yd - yc) - (yb - ya) * (xd - xc));
double rnum = ((ya - yc) * (xd - xc) - (xa - xc) * (yd - yc));
if (denom == 0.0) { // parallel
if (rnum == 0.0) { // coincident; pick one end of first line
if ((xa < xb && (xb < xc || xb < xd))
|| (xa > xb && (xb > xc || xb > xd))) {
return new Point2D.Double(xb, yb);
} else {
return new Point2D.Double(xa, ya);
}
} else {
return null;
}
}
double r = rnum / denom;
double snum = ((ya - yc) * (xb - xa) - (xa - xc) * (yb - ya));
double s = snum / denom;
if (0.0 <= r && r <= 1.0 && 0.0 <= s && s <= 1.0) {
double px = xa + (xb - xa) * r;
double py = ya + (yb - ya) * r;
return new Point2D.Double(px, py);
} else {
return null;
}
}
@Nullable
public static Point2D.Double intersect(
double xa, // line 1 point 1 x
double ya, // line 1 point 1 y
double xb, // line 1 point 2 x
double yb, // line 1 point 2 y
double xc, // line 2 point 1 x
double yc, // line 2 point 1 y
double xd, // line 2 point 2 x
double yd,
double limit) { // line 2 point 2 y
// source: http://vision.dai.ed.ac.uk/andrewfg/c-g-a-faq.html
// eq: for lines AB and CD
// (YA-YC)(XD-XC)-(XA-XC)(YD-YC)
// r = ----------------------------- (eqn 1)
// (XB-XA)(YD-YC)-(YB-YA)(XD-XC)
//
// (YA-YC)(XB-XA)-(XA-XC)(YB-YA)
// s = ----------------------------- (eqn 2)
// (XB-XA)(YD-YC)-(YB-YA)(XD-XC)
// XI = XA + r(XB-XA)
// YI = YA + r(YB-YA)
double denom = ((xb - xa) * (yd - yc) - (yb - ya) * (xd - xc));
double rnum = ((ya - yc) * (xd - xc) - (xa - xc) * (yd - yc));
if (denom == 0.0) { // parallel
if (rnum == 0.0) { // coincident; pick one end of first line
if ((xa < xb && (xb < xc || xb < xd))
|| (xa > xb && (xb > xc || xb > xd))) {
return new Point2D.Double(xb, yb);
} else {
return new Point2D.Double(xa, ya);
}
} else {
return null;
}
}
double r = rnum / denom;
double snum = ((ya - yc) * (xb - xa) - (xa - xc) * (yb - ya));
double s = snum / denom;
if (0.0 <= r && r <= 1.0 && 0.0 <= s && s <= 1.0) {
double px = xa + (xb - xa) * r;
double py = ya + (yb - ya) * r;
return new Point2D.Double(px, py);
} else {
double px = xa + (xb - xa) * r;
double py = ya + (yb - ya) * r;
if (length(xa, ya, px, py) <= limit
|| length(xb, yb, px, py) <= limit
|| length(xc, yc, px, py) <= limit
|| length(xd, yd, px, py) <= limit) {
return new Point2D.Double(px, py);
}
return null;
}
}
/**
* compute distance of point from line segment, or
* Double.MAX_VALUE if perpendicular projection is outside segment; or
* If pts on line are same, return distance from point
**/
// from Doug Lea's PolygonFigure
public static double distanceFromLine(int xa, int ya,
int xb, int yb,
int xc, int yc) {
// source:http://vision.dai.ed.ac.uk/andrewfg/c-g-a-faq.html#q7
//Let the point be C (XC,YC) and the line be AB (XA,YA) to (XB,YB).
//The length of the
// line segment AB is L:
//
// ___________________
// | 2 2
// L = \| (XB-XA) + (YB-YA)
//and
//
// (YA-YC)(YA-YB)-(XA-XC)(XB-XA)
// r = -----------------------------
// L**2
//
// (YA-YC)(XB-XA)-(XA-XC)(YB-YA)
// s = -----------------------------
// L**2
//
// Let I be the point of perpendicular projection of C onto AB, the
//
// XI=XA+r(XB-XA)
// YI=YA+r(YB-YA)
//
// Distance from A to I = r*L
// Distance from C to I = s*L
//
// If r < 0 I is on backward extension of AB
// If r>1 I is on ahead extension of AB
// If 0<=r<=1 I is on AB
//
// If s < 0 C is left of AB (you can just check the numerator)
// If s>0 C is right of AB
// If s=0 C is on AB
int xdiff = xb - xa;
int ydiff = yb - ya;
long l2 = xdiff * xdiff + ydiff * ydiff;
if (l2 == 0) {
return Geom.length(xa, ya, xc, yc);
}
double rnum = (ya - yc) * (ya - yb) - (xa - xc) * (xb - xa);
double r = rnum / l2;
if (r < 0.0 || r > 1.0) {
return Double.MAX_VALUE;
}
double xi = xa + r * xdiff;
double yi = ya + r * ydiff;
double xd = xc - xi;
double yd = yc - yi;
return sqrt(xd * xd + yd * yd);
/*
for directional version, instead use
double snum = (ya-yc) * (xb-xa) - (xa-xc) * (yb-ya);
double s = snum / l2;
double l = sqrt((double)l2);
return = s * l;
*/
}
/**
* Resizes the <code>Rectangle2D.Double</code> both horizontally and vertically.
* <p>
* This method modifies the <code>Rectangle2D.Double</code> so that it is
* <code>h</code> units larger on both the left and right side,
* and <code>v</code> units larger at both the top and bottom.
* <p>
* The new <code>Rectangle2D.Double</code> has (<code>x - h</code>,
* <code>y - v</code>) as its top-left corner, a
* width of
* <code>width</code> <code>+</code> <code>2h</code>,
* and a height of
* <code>height</code> <code>+</code> <code>2v</code>.
* <p>
* If negative values are supplied for <code>h</code> and
* <code>v</code>, the size of the <code>Rectangle2D.Double</code>
* decreases accordingly.
* The <code>grow</code> method does not check whether the resulting
* values of <code>width</code> and <code>height</code> are
* non-negative.
* @param h the horizontal expansion
* @param v the vertical expansion
*/
public static void grow(Rectangle2D.Double r, double h, double v) {
r.x -= h;
r.y -= v;
r.width += h * 2d;
r.height += v * 2d;
}
/**
* Returns true, if rectangle 1 contains rectangle 2.
* <p>
* This method is similar to Rectangle2D.contains, but also returns true,
* when rectangle1 contains rectangle2 and either or both of them
* are empty.
*
* @param r1 Rectangle 1.
* @param r2 Rectangle 2.
* @return true if r1 contains r2.
*/
public static boolean contains(Rectangle2D.Double r1, Rectangle2D.Double r2) {
return (r2.x >= r1.x
&& r2.y >= r1.y
&& (r2.x + max(0, r2.width)) <= r1.x + max(0, r1.width)
&& (r2.y + max(0, r2.height)) <= r1.y + max(0, r1.height));
}
/**
* Returns true, if rectangle 1 contains rectangle 2.
* <p>
* This method is similar to Rectangle2D.contains, but also returns true,
* when rectangle1 contains rectangle2 and either or both of them
* are empty.
*
* @param r1 Rectangle 1.
* @param r2 Rectangle 2.
* @return true if r1 contains r2.
*/
public static boolean contains(Rectangle2D r1, Rectangle2D r2) {
return (r2.getX()) >= r1.getX()
&& r2.getY() >= r1.getY()
&& (r2.getX() + max(0, r2.getWidth())) <= r1.getX() + max(0, r1.getWidth())
&& (r2.getY() + max(0, r2.getHeight())) <= r1.getY() + max(0, r1.getHeight());
}
}