📄 morphing2d.java
字号:
/*
* $Id: Morphing2D.java,v 1.1 2007/01/26 17:35:35 gfx Exp $
*
* Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* Licensed under LGPL.
*/
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.IllegalPathStateException;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
/**
* <p>A morphing shape is a shape which geometry is constructed from two
* other shapes: a start shape and an end shape.</p>
* <p>The morphing property of a morphing shape defines the amount of
* transformation applied to the start shape to turn it into the end shape.</p>
* <p>Both shapes must have the same winding rule.</p>
*
* @author Jim Graham
* @author Romain Guy <romain.guy@mac.com> (Maintainer)
*/
public class Morphing2D implements Shape {
private double morph;
private Geometry startGeometry;
private Geometry endGeometry;
/**
* <p>Creates a new morphing shape. A morphing shape can be used to turn
* one shape into another one. The transformation can be controlled by the
* morph property.</p>
*
* @param startShape the shape to morph from
* @param endShape the shape to morph to
*
* @throws IllegalPathStateException if the shapes do not have the same
* winding rule
* @see #getMorphing()
* @see #setMorphing(double)
*/
public Morphing2D(Shape startShape, Shape endShape) {
startGeometry = new Geometry(startShape);
endGeometry = new Geometry(endShape);
if (startGeometry.getWindingRule() != endGeometry.getWindingRule()) {
throw new IllegalPathStateException("shapes must use same " +
"winding rule");
}
double tvals0[] = startGeometry.getTvals();
double tvals1[] = endGeometry.getTvals();
double masterTvals[] = mergeTvals(tvals0, tvals1);
startGeometry.setTvals(masterTvals);
endGeometry.setTvals(masterTvals);
}
/**
* <p>Returns the morphing value between the two shapes.</p>
*
* @return the morphing value between the two shapes
*
* @see #setMorphing(double)
*/
public double getMorphing() {
return morph;
}
/**
* <p>Sets the morphing value between the two shapes. This value controls
* the transformation from the start shape to the end shape. A value of 0.0
* is the start shap. A value of 1.0 is the end shape. A value of 0.5 is a
* new shape, morphed half way from the start shape to the end shape.</p>
* <p>The specified value should be between 0.0 and 1.0. If not, the value
* is clamped in the appropriate range.</p>
*
* @param morph the morphing value between the two shapes
*
* @see #getMorphing()
*/
public void setMorphing(double morph) {
if (morph > 1) {
morph = 1;
} else if (morph >= 0) {
// morphing is finite, not NaN, and in range
} else {
// morph is < 0 or NaN
morph = 0;
}
this.morph = morph;
}
private static double interp(double v0, double v1, double t) {
return (v0 + ((v1 - v0) * t));
}
private static double[] mergeTvals(double tvals0[], double tvals1[]) {
int i0 = 0;
int i1 = 0;
int numtvals = 0;
while (i0 < tvals0.length && i1 < tvals1.length) {
double t0 = tvals0[i0];
double t1 = tvals1[i1];
if (t0 <= t1) {
i0++;
}
if (t1 <= t0) {
i1++;
}
numtvals++;
}
double newtvals[] = new double[numtvals];
i0 = 0;
i1 = 0;
numtvals = 0;
while (i0 < tvals0.length && i1 < tvals1.length) {
double t0 = tvals0[i0];
double t1 = tvals1[i1];
if (t0 <= t1) {
newtvals[numtvals] = t0;
i0++;
}
if (t1 <= t0) {
newtvals[numtvals] = t1;
i1++;
}
numtvals++;
}
return newtvals;
}
/**
* @{inheritDoc}
*/
public Rectangle getBounds() {
return getBounds2D().getBounds();
}
/**
* @{inheritDoc}
*/
public Rectangle2D getBounds2D() {
int n = startGeometry.getNumCoords();
double xmin, ymin, xmax, ymax;
xmin = xmax = interp(startGeometry.getCoord(0), endGeometry.getCoord(0),
morph);
ymin = ymax = interp(startGeometry.getCoord(1), endGeometry.getCoord(1),
morph);
for (int i = 2; i < n; i += 2) {
double x = interp(startGeometry.getCoord(i),
endGeometry.getCoord(i), morph);
double y = interp(startGeometry.getCoord(i + 1),
endGeometry.getCoord(i + 1), morph);
if (xmin > x) {
xmin = x;
}
if (ymin > y) {
ymin = y;
}
if (xmax < x) {
xmax = x;
}
if (ymax < y) {
ymax = y;
}
}
return new Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax - ymin);
}
/**
* @{inheritDoc}
*/
public boolean contains(double x, double y) {
throw new InternalError("unimplemented");
}
/**
* @{inheritDoc}
*/
public boolean contains(Point2D p) {
return contains(p.getX(), p.getY());
}
/**
* @{inheritDoc}
*/
public boolean intersects(double x, double y, double w, double h) {
throw new InternalError("unimplemented");
}
/**
* @{inheritDoc}
*/
public boolean intersects(Rectangle2D r) {
return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
/**
* @{inheritDoc}
*/
public boolean contains(double x, double y, double w, double h) {
throw new InternalError("unimplemented");
}
/**
* @{inheritDoc}
*/
public boolean contains(Rectangle2D r) {
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
/**
* @{inheritDoc}
*/
public PathIterator getPathIterator(AffineTransform at) {
return new Iterator(at, startGeometry, endGeometry, morph);
}
/**
* @{inheritDoc}
*/
public PathIterator getPathIterator(AffineTransform at, double flatness) {
return new FlatteningPathIterator(getPathIterator(at), flatness);
}
private static class Geometry {
static final double THIRD = (1.0 / 3.0);
static final double MIN_LEN = 0.001;
double bezierCoords[];
int numCoords;
int windingrule;
double myTvals[];
public Geometry(Shape s) {
// Multiple of 6 plus 2 more for initial moveto
bezierCoords = new double[20];
PathIterator pi = s.getPathIterator(null);
windingrule = pi.getWindingRule();
if (pi.isDone()) {
// We will have 1 segment and it will be all zeros
// It will have 8 coordinates (2 for moveto, 6 for cubic)
numCoords = 8;
}
double coords[] = new double[6];
int type = pi.currentSegment(coords);
pi.next();
if (type != PathIterator.SEG_MOVETO) {
throw new IllegalPathStateException("missing initial moveto");
}
double curx = bezierCoords[0] = coords[0];
double cury = bezierCoords[1] = coords[1];
double newx, newy;
numCoords = 2;
while (!pi.isDone()) {
if (numCoords + 6 > bezierCoords.length) {
// Keep array size to a multiple of 6 plus 2
int newsize = (numCoords - 2) * 2 + 2;
double newCoords[] = new double[newsize];
System.arraycopy(bezierCoords, 0, newCoords, 0, numCoords);
bezierCoords = newCoords;
}
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
throw new InternalError(
"Cannot handle multiple subpaths");
case PathIterator.SEG_CLOSE:
if (curx == bezierCoords[0] && cury == bezierCoords[1])
{
break;
}
coords[0] = bezierCoords[0];
coords[1] = bezierCoords[1];
/* NO BREAK */
case PathIterator.SEG_LINETO:
newx = coords[0];
newy = coords[1];
// A third of the way from curxy to newxy:
bezierCoords[numCoords++] = interp(curx, newx, THIRD);
bezierCoords[numCoords++] = interp(cury, newy, THIRD);
// A third of the way from newxy back to curxy:
bezierCoords[numCoords++] = interp(newx, curx, THIRD);
bezierCoords[numCoords++] = interp(newy, cury, THIRD);
bezierCoords[numCoords++] = curx = newx;
bezierCoords[numCoords++] = cury = newy;
break;
case PathIterator.SEG_QUADTO:
double ctrlx = coords[0];
double ctrly = coords[1];
newx = coords[2];
newy = coords[3];
// A third of the way from ctrlxy back to curxy:
bezierCoords[numCoords++] = interp(ctrlx, curx, THIRD);
bezierCoords[numCoords++] = interp(ctrly, cury, THIRD);
// A third of the way from ctrlxy to newxy:
bezierCoords[numCoords++] = interp(ctrlx, newx, THIRD);
bezierCoords[numCoords++] = interp(ctrly, newy, THIRD);
bezierCoords[numCoords++] = curx = newx;
bezierCoords[numCoords++] = cury = newy;
break;
case PathIterator.SEG_CUBICTO:
bezierCoords[numCoords++] = coords[0];
bezierCoords[numCoords++] = coords[1];
bezierCoords[numCoords++] = coords[2];
bezierCoords[numCoords++] = coords[3];
bezierCoords[numCoords++] = curx = coords[4];
bezierCoords[numCoords++] = cury = coords[5];
break;
}
pi.next();
}
// Add closing segment if either:
// - we only have initial moveto - expand it to an empty cubic
// - or we are not back to the starting point
if ((numCoords < 8) ||
curx != bezierCoords[0] ||
cury != bezierCoords[1]) {
newx = bezierCoords[0];
newy = bezierCoords[1];
// A third of the way from curxy to newxy:
bezierCoords[numCoords++] = interp(curx, newx, THIRD);
bezierCoords[numCoords++] = interp(cury, newy, THIRD);
// A third of the way from newxy back to curxy:
bezierCoords[numCoords++] = interp(newx, curx, THIRD);
bezierCoords[numCoords++] = interp(newy, cury, THIRD);
bezierCoords[numCoords++] = newx;
bezierCoords[numCoords++] = newy;
}
// Now find the segment endpoint with the smallest Y coordinate
int minPt = 0;
double minX = bezierCoords[0];
double minY = bezierCoords[1];
for (int ci = 6; ci < numCoords; ci += 6) {
double x = bezierCoords[ci];
double y = bezierCoords[ci + 1];
if (y < minY || (y == minY && x < minX)) {
minPt = ci;
minX = x;
minY = y;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -