/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: DBMath.java
*
* Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
*
* Electric(tm) 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.
*
* Electric(tm) 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 Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.util.math;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import com.sun.electric.database.geometry.Dimension2D;
import com.sun.electric.database.geometry.Poly;
/**
* This class is a collection of math utilities used for
* Database Units. It overrides several important methods
* from GenMath used when comparing doubles.
*/
public class DBMath extends GenMath {
/**
* Number of grid points per unit
*/
public static final double GRID = 400;
/**
* epsilon is the largest amount of absolute difference
* between two numbers in the database for which those numbers
* will still be regarded as "equal".
*/
private static final double EPSILON = 1/GRID;
private static final double HALF_EPSILON = 0.5/GRID;
private static final double TINYDELTA = EPSILON*1.01;
/**
* To return private epsilon used for calculation.
* This might problably be removed
* @return epsilon
*/
public static double getEpsilon() { return EPSILON; }
/**
* Method to tell whether a point is inside of a bounds, compensating
* for possible database precision errors.
* The reason that this is necessary is that Rectangle2D.contains requires that
* the point be INSIDE of the bounds, whereas this method accepts a point that
* is ON the bounds.
* @param pt the point in question
* @param bounds the bounds being tested
* @return true if the point is basically within the bounds, within some
* epsilon.
*/
public static boolean pointInRect(Point2D pt, Rectangle2D bounds) {
if (pt.getX() < (bounds.getMinX() - TINYDELTA)) return false;
if (pt.getX() > (bounds.getMaxX() + TINYDELTA)) return false;
if (pt.getY() < (bounds.getMinY() - TINYDELTA)) return false;
if (pt.getY() > (bounds.getMaxY() + TINYDELTA)) return false;
return true;
}
/**
* Method to determine if point is completely inside a bound and not
* along its boundary.
* @param pt the Point in question.
* @param bounds the bounds to test.
* @return true if the point is inside the bounds.
*/
public static boolean pointInsideRect(Point2D pt, Rectangle2D bounds)
{
double ptX = pt.getX();
double ptY = pt.getY();
boolean newV = (isGreaterThan(ptX, bounds.getMinX()) &&
isGreaterThan(bounds.getMaxX(), ptX) &&
isGreaterThan(ptY, bounds.getMinY()) &&
isGreaterThan(bounds.getMaxY(), ptY));
return newV;
}
/**
* Method to compare two double-precision database values.
* @param a the first number.
* @param b the second number.
* @return true if the numbers are approximately equal (to a few decimal places).
*/
public static boolean areEquals(double a, double b) {
if (Math.abs(a-b) < TINYDELTA) return true;
return false;
}
/**
* Method to determine if a value is between two given values including the boundary.
* @param x the value to test.
* @param a one end of the boundary.
* @param b the other end of the boundary.
* @return true if the value is inside of the boundary.
*/
public static boolean isInBetween(double x, double a, double b)
{
if (isGreaterThan(a, b))
{
double c = a;
a = b; b = c;
}
boolean tooSmall = isGreaterThan(a, x);
boolean tooBig = isGreaterThan(x, b);
return !tooSmall && !tooBig;
}
/**
* Method to determine if a value is between two given values without the boundary.
* @param x the value to test.
* @param a one end of the boundary.
* @param b the other end of the boundary.
* @return true if the value is inside of the boundary.
*/
public static boolean isInBetweenExclusive(double x, double a, double b)
{
if (isGreaterThan(a, b))
{
double c = a;
a = b; b = c;
}
boolean tooSmall = isGreaterThanOrEqualTo(a, x); // it must exclude the boundaries
boolean tooBig = isGreaterThanOrEqualTo(x, b);
return !tooSmall && !tooBig;
}
/**
* Method to determine if one value is greater than another,
* but counting for rounding error
* @param a the first number.
* @param b the second number.
* @return true if first number is greater than the second number.
*/
public static boolean isGreaterThan(double a, double b)
{
return a - b > HALF_EPSILON;
}
/**
* Method to determine if one value is less than another,
* but counting for rounding error
* @param a the first number.
* @param b the second number.
* @return true if first number is less than the second number.
*/
public static boolean isLessThan(double a, double b)
{
return b - a > HALF_EPSILON;
}
/**
* Method to determine if one value is greater than or equal to another,
* but counting for rounding error
* @param a the first number.
* @param b the second number.
* @return true if first number is greater than or equal to the second number.
*/
public static boolean isGreaterThanOrEqualTo(double a, double b)
{
return a - b > -HALF_EPSILON;
}
/**
* Method to determine if one value is less than or equal to another,
* but counting for rounding error
* @param a the first number.
* @param b the second number.
* @return true if first number is less than or equal to the second number.
*/
public static boolean isLessThanOrEqualTo(double a, double b)
{
return b - a > -HALF_EPSILON;
}
/**
* Method to round a database value to database precision.
* @param lambdaValue the value to round in lambda units.
* @return the return value in lambda units is an approximation of x rounded to GRID.
*/
public static double round(double lambdaValue) {
double x = lambdaValue*GRID;
long l = (long)(x >= 0 ? x + HALF : x - HALF);
return l/GRID;
}
/**
* Method to convert a database value from lambda units to grid units.
* @param lambdaValue the value to round in lambda unit.
* @return the return value in grid units.
*/
public static long lambdaToGrid(double lambdaValue) {
double x = lambdaValue*GRID;
return (long)(x >= 0 ? x + HALF : x - HALF);
}
/**
* Method to convert a database size value from lambda units to grid units.
* Result is always even number.
* @param lambdaValue the value to round in lambda unit.
* @return the return value in grid units which is even number.
*/
public static long lambdaToSizeGrid(double lambdaValue) {
double x = lambdaValue*(GRID/2);
long l = (long)(x >= 0 ? x + HALF : x - HALF);
return l << 1;
}
/**
* Method to convert a database value from grid units to lambda units.
* @param gridValue the value in grid unit.
* @return the return value in lambda units.
*/
public static double gridToLambda(double gridValue) {
return gridValue/GRID;
}
/**
* Method to round coordinate to shape grid.
* Shape grid values are is k*2^(-20), where k in [-2^52..+2^52].
* All shape grid values in range [-2^32..+2^32] can be exactly represented by double value.
* Values larger thaan 2^32 may be rounded.
*/
public static double roundShapeCoord(double v) {
double LARGE = 1L << 32;
return v >= 0 ? (v + LARGE) - LARGE : (v - LARGE) + LARGE;
}
/**
* Method to snap a point to the nearest database-space grid unit.
* @param pt the point to be snapped.
* @param alignment the alignment values to use in X and Y.
*/
public static void gridAlign(Point2D pt, Dimension2D alignment)
{
gridAlign(pt, alignment, -1);
}
/**
* Method to snap the X, Y or both coordinates of a point to the nearest database-space grid unit
* @param pt the point to be snapped.
* @param alignment the alignment values to use in X and Y.
* @param direction -1 if X and Y coordinates, 0 if only X and 1 if only Y
*/
public static void gridAlign(Point2D pt, Dimension2D alignment, int direction)
{
switch (direction)
{
case -1:
if (alignment.getWidth() <= 0 || alignment.getHeight() <= 0) return;
long x = Math.round(pt.getX() / alignment.getWidth());
long y = Math.round(pt.getY() / alignment.getHeight());
pt.setLocation(x * alignment.getWidth(), y * alignment.getHeight());
break;
case 0: // X only
if (alignment.getWidth() <= 0) return;
x = Math.round(pt.getX() / alignment.getWidth());
pt.setLocation(x * alignment.getWidth(), pt.getY());
break;
case 1: // y only
if (alignment.getHeight() <= 0) return;
y = Math.round(pt.getY() / alignment.getHeight());
pt.setLocation(pt.getX(), y * alignment.getHeight());
break;
default:
assert(false); // it should not reach this point.
}
}
/**
* Method to compare two double-precision database coordinates within an approximate epsilon.
* @param a the first point.
* @param b the second point.
* @return true if the points are approximately equal.
*/
public static boolean areEquals(Point2D a, Point2D b)
{
if (areEquals(a.getX(), b.getX()) &&
areEquals(a.getY(), b.getY())) return true;
return false;
}
/**
* Method to tell whether a point is on a given line segment.
* @param end1 the first end of the line segment.
* @param end2 the second end of the line segment.
* @param pt the point in question.
* @return true if the point is on the line segment.
*/
public static boolean isOnLine(Point2D end1, Point2D end2, Point2D pt)
{
Point2D closestPointOnSegment = closestPointToSegment(end1, end2, pt);
return areEquals(closestPointOnSegment, pt);
}
/**
* Method to calcular remainder for doubles and avoid rounding errors
* by calculating the remainder for integers instead.
* @param a the numerator
* @param divisor the denominator.
* @return the remainder from the division.
*/
public static boolean hasRemainder(double a, double divisor)
{
double val = round(a / divisor);
return val % 1 != 0;
}
/**
* Method to transform a Rectangle2D by a given transformation.
* @param bounds the Rectangle to transform.
* It is transformed "in place" (its coordinates are overwritten).
* @param xform the transformation matrix.
*/
public static void transformRect(Rectangle2D bounds, AffineTransform xform)
{
if (xform.getType() == AffineTransform.TYPE_IDENTITY) // nothing to do
return;
Point2D [] corners = Poly.makePoints(bounds.getMinX(), bounds.getMaxX(), bounds.getMinY(), bounds.getMaxY());
xform.transform(corners, 0, corners, 0, 4);
double lX = corners[0].getX();
double lY = corners[0].getY();
double hX = lX;
double hY = lY;
for(int i=1; i<4; i++)
{
if (corners[i].getX() < lX) lX = corners[i].getX();
if (corners[i].getX() > hX) hX = corners[i].getX();
if (corners[i].getY() < lY) lY = corners[i].getY();
if (corners[i].getY() > hY) hY = corners[i].getY();
}
bounds.setRect(lX, lY, hX-lX, hY-lY);
}
/**
* Method to tell whether two Rectangle2D objects intersect.
* If one of the rectangles has zero size, then standard "intersect()" fails.
* @param r1 the first rectangle.
* @param r2 the second rectangle.
* @return true if they overlap.
*/
public static boolean rectsIntersect(Rectangle2D r1, Rectangle2D r2)
{
if (r2.getMaxX() < r1.getMinX()) return false;
if (r2.getMinX() > r1.getMaxX()) return false;
if (r2.getMaxY() < r1.getMinY()) return false;
if (r2.getMinY() > r1.getMaxY()) return false;
return true;
}
}