/*
* Copyright (c) 2005, Graph Builder
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Graph Builder nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.graphbuilder.curve;
import java.awt.*;
import java.awt.geom.*;
import com.graphbuilder.geom.
Geom;
import com.graphbuilder.org.apache.harmony.awt.gl.
Crossing;
/**
The ShapeMultiPath is-a MultiPath and implements the java.awt.Shape interface.
Here is an example of how to use a ShapeMultiPath:
<pre>
ControlPath cp = new ControlPath();
cp.addPoint(...); // add points
Curve c = new BezierCurve(cp, new GroupIterator("0:n-1"));
ShapeMultiPath smp = new ShapeMultiPath();
c.appendTo(smp);
Graphics2D g = ...;
g.draw(smp);
</pre>
*/
public class
ShapeMultiPath extends
MultiPath implements
Shape {
private int
windingRule =
PathIterator.
WIND_EVEN_ODD;
private int
ai0 = 0;
private int
ai1 = 1;
/**
Constructs a new ShapeMultiPath with a dimension of 2.
*/
public
ShapeMultiPath() {
super(2);
}
/**
Constructs a new ShapeMultiPath with the specified dimension requirement.
@throws IllegalArgumentException If the specified dimension is less than 2.
*/
public
ShapeMultiPath(int
dimension) {
super(
dimension);
if (
dimension < 2)
throw new
IllegalArgumentException("dimension >= 2 required");
}
/**
The basis vectors specify which index corresponds to the x-axis and which index
corresponds to the y-axis. The value of the x-axis is at index location 0 and the
value of the y-axis is at index location 1.
@throws IllegalArgumentException If the axis values are less than 0 or greater than or
equal to the dimension.
@see #getBasisVectors()
*/
public void
setBasisVectors(int[]
b) {
int
b0 =
b[0];
int
b1 =
b[1];
int
dimension =
getDimension();
if (
b0 < 0 ||
b1 < 0 ||
b0 >=
dimension ||
b1 >=
dimension)
throw new
IllegalArgumentException("basis vectors must be >= 0 and < dimension");
ai0 =
b0;
ai1 =
b1;
}
/**
Returns a new integer array with the basis vectors. The default basis vectors are {0, 1}.
@see #setBasisVectors(int[])
*/
public int[]
getBasisVectors() {
return new int[] {
ai0,
ai1 };
}
/**
Returns the minimum distance^2 from the specified point to the line segments of this multi-path.
*/
public double
getDistSq(double
x, double
y) {
int
n =
getNumPoints();
if (
n == 0)
return
Double.
MAX_VALUE;
double[]
p =
get(0);
double
x2 =
p[
ai0];
double
y2 =
p[
ai1];
double
dist =
Double.
MAX_VALUE;
for (int
i = 1;
i <
n;
i++) {
p =
get(
i);
double
x1 =
p[
ai0];
double
y1 =
p[
ai1];
if (
getType(
i) ==
MultiPath.
LINE_TO) {
double
d =
Geom.
ptSegDistSq(
x1,
y1,
x2,
y2,
x,
y, null);
if (
d <
dist)
dist =
d;
}
x2 =
x1;
y2 =
y1;
}
return
dist;
}
//------------------------------------------------------------------------------------------
// methods for Shape interface:
/**
Returns the value of the winding rule. The default value is PathIterator.WIND_EVEN_ODD.
@see #setWindingRule(int)
*/
public int
getWindingRule() {
return
windingRule;
}
/**
Sets the winding rule. The winding rule can either by PathIterator.WIND_EVEN_ODD or
PathIterator.WIND_NON_ZERO, otherwise an IllegalArgumentException is thrown.
*/
public void
setWindingRule(int
rule) {
if (
rule !=
PathIterator.
WIND_EVEN_ODD &&
rule !=
PathIterator.
WIND_NON_ZERO)
throw new
IllegalArgumentException("winding rule must be WIND_EVEN_ODD or WIND_NON_ZERO");
windingRule =
rule;
}
/**
Returns a new PathIterator object.
*/
public
PathIterator getPathIterator(
AffineTransform at) {
return new
ShapeMultiPathIterator(this,
at);
}
/**
Returns a new PathIterator object. The flatness parameter is ignored since a multi-path, by
definition, is already flat.
*/
public
PathIterator getPathIterator(
AffineTransform at, double
flatness) {
return new
ShapeMultiPathIterator(this,
at);
}
//---------------------------------------------------------------
/**
See the getBounds2D() method.
@see #getBounds2D()
*/
public
Rectangle getBounds() {
Rectangle2D r =
getBounds2D();
if (
r == null) return null;
return
r.
getBounds();
}
/**
Computes the bounding box of the points. When computing the bounding box, a point is considered if it is
of type LINE_TO or it is of type MOVE_TO and the next point is of type LINE_TO. A value of null is
returned if there is not enough data to define a bounding box.
*/
public
Rectangle2D getBounds2D() {
int
n =
getNumPoints();
double
x1 =
Double.
MAX_VALUE;
double
y1 =
Double.
MAX_VALUE;
double
x2 = -
Double.
MAX_VALUE;
double
y2 = -
Double.
MAX_VALUE;
boolean
defined = false;
for (int
i = 0;
i <
n;
i++) {
double[]
p =
get(
i);
boolean
b = false;
if (
getType(
i) ==
MultiPath.
MOVE_TO) {
if (
i <
n - 1 &&
getType(
i+1) ==
MultiPath.
LINE_TO)
b = true;
}
else {
b = true;
}
if (
b) {
defined = true;
if (
p[
ai0] <
x1)
x1 =
p[
ai0];
if (
p[
ai1] <
y1)
y1 =
p[
ai1];
if (
p[
ai0] >
x2)
x2 =
p[
ai0];
if (
p[
ai1] >
y2)
y2 =
p[
ai1];
}
}
if (!
defined)
return null;
return new
Rectangle2D.
Double(
x1,
y1,
x2 -
x1,
y2 -
y1);
}
//---------------------------------------------------------------
/**
Returns true if the point is contained inside the shape. Otherwise false is returned.
*/
public boolean
contains(double
x, double
y) {
int
cross =
Crossing.
crossPath(
getPathIterator(null),
x,
y);
if (
windingRule ==
PathIterator.
WIND_NON_ZERO)
return
cross != 0;
return (
cross & 1) != 0;
}
/**
See the contains(x, y) method.
@see #contains(double,double)
*/
public boolean
contains(
Point2D p) {
return
contains(
p.
getX(),
p.
getY());
}
/**
Returns true only if the shape contains all points of the rectangle. First, if any of
the four corners is not contained in the shape then false is returned. Now we know
that all four corners are inside the shape. Next, we check to see if any line segment
of this shape intersects any of the 4 line segments formed by the rectangle. If there
is an intersection, then false is returned. Otherwise true is returned.
*/
public boolean
contains(double
x1, double
y1, double
w, double
h) {
double
x2 =
x1 +
w;
double
y2 =
y1 +
h;
if (!
contains(
x1,
y1)) return false;
if (!
contains(
x1,
y2)) return false;
if (!
contains(
x2,
y1)) return false;
if (!
contains(
x2,
y2)) return false;
int
n =
getNumPoints();
if (
n == 0) return false;
double[]
p =
get(0);
double
xb =
p[
ai0];
double
yb =
p[
ai1];
for (int
i = 1;
i <
n;
i++) {
p =
get(
i);
double
xa =
p[
ai0];
double
ya =
p[
ai1];
if (
getType(
i) ==
MultiPath.
LINE_TO) {
if (
Geom.
getSegSegIntersection(
xa,
ya,
xb,
yb,
x1,
y1,
x2,
y1, null) ==
Geom.
INTERSECT)
return false;
if (
Geom.
getSegSegIntersection(
xa,
ya,
xb,
yb,
x1,
y1,
x1,
y2, null) ==
Geom.
INTERSECT)
return false;
if (
Geom.
getSegSegIntersection(
xa,
ya,
xb,
yb,
x1,
y2,
x2,
y2, null) ==
Geom.
INTERSECT)
return false;
if (
Geom.
getSegSegIntersection(
xa,
ya,
xb,
yb,
x2,
y1,
x2,
y2, null) ==
Geom.
INTERSECT)
return false;
}
xb =
xa;
yb =
ya;
}
return true;
}
/**
See the contains(x, y, w, h) method.
@see #contains(double,double,double,double)
*/
public boolean
contains(
Rectangle2D r) {
return
contains(
r.
getX(),
r.
getY(),
r.
getWidth(),
r.
getHeight());
}
//---------------------------------------------------------------
/**
This method returns true if any line segment in this multi-path intersects any of the
4 line segments formed by the rectangle or any corner of the rectangle is inside the
shape or any point of the shape is inside the rectangle. Otherwise false is returned.
*/
public boolean
intersects(double
x1, double
y1, double
w, double
h) {
double
x2 =
x1 +
w;
double
y2 =
y1 +
h;
if (
contains(
x1,
y1)) return true;
if (
contains(
x1,
y2)) return true;
if (
contains(
x2,
y1)) return true;
if (
contains(
x2,
y2)) return true;
int
n =
getNumPoints();
if (
n == 0) return false;
double[]
p =
get(0);
double
xb =
p[
ai0];
double
yb =
p[
ai1];
for (int
i = 1;
i <
n;
i++) {
p =
get(
i);
double
xa =
p[
ai0];
double
ya =
p[
ai1];
if (
getType(
i) ==
MultiPath.
LINE_TO) {
if (
Geom.
getSegSegIntersection(
xa,
ya,
xb,
yb,
x1,
y1,
x2,
y1, null) ==
Geom.
INTERSECT)
return true;
if (
Geom.
getSegSegIntersection(
xa,
ya,
xb,
yb,
x1,
y1,
x1,
y2, null) ==
Geom.
INTERSECT)
return true;
if (
Geom.
getSegSegIntersection(
xa,
ya,
xb,
yb,
x1,
y2,
x2,
y2, null) ==
Geom.
INTERSECT)
return true;
if (
Geom.
getSegSegIntersection(
xa,
ya,
xb,
yb,
x2,
y1,
x2,
y2, null) ==
Geom.
INTERSECT)
return true;
if (
xa >=
x1 &&
ya >=
y1 &&
xa <=
x2 &&
ya <=
y2) return true;
if (
xb >=
x1 &&
yb >=
y1 &&
xb <=
x2 &&
yb <=
y2) return true;
}
xb =
xa;
yb =
ya;
}
return false;
}
/**
See the intersects(x, y, w, h) method.
@see #intersects(double,double,double,double)
*/
public boolean
intersects(
Rectangle2D r) {
return
intersects(
r.
getX(),
r.
getY(),
r.
getWidth(),
r.
getHeight());
}
}