📄 morphing2d.java
字号:
}
// If the smallest Y coordinate is not the first coordinate,
// rotate the points so that it is...
if (minPt > 0) {
// Keep in mind that first 2 coords == last 2 coords
double newCoords[] = new double[numCoords];
// Copy all coordinates from minPt to the end of the
// array to the beginning of the new array
System.arraycopy(bezierCoords, minPt,
newCoords, 0,
numCoords - minPt);
// Now we do not want to copy 0,1 as they are duplicates
// of the last 2 coordinates which we just copied. So
// we start the source copy at index 2, but we still
// copy a full minPt coordinates which copies the two
// coordinates that were at minPt to the last two elements
// of the array, thus ensuring that thew new array starts
// and ends with the same pair of coordinates...
System.arraycopy(bezierCoords, 2,
newCoords, numCoords - minPt,
minPt);
bezierCoords = newCoords;
}
/* Clockwise enforcement:
* - This technique is based on the formula for calculating
* the area of a Polygon. The standard formula is:
* Area(Poly) = 1/2 * sum(x[i]*y[i+1] - x[i+1]y[i])
* - The returned area is negative if the polygon is
* "mostly clockwise" and positive if the polygon is
* "mostly counter-clockwise".
* - One failure mode of the Area calculation is if the
* Polygon is self-intersecting. This is due to the
* fact that the areas on each side of the self-intersection
* are bounded by segments which have opposite winding
* direction. Thus, those areas will have opposite signs
* on the acccumulation of their area summations and end
* up canceling each other out partially.
* - This failure mode of the algorithm in determining the
* exact magnitude of the area is not actually a big problem
* for our needs here since we are only using the sign of
* the resulting area to figure out the overall winding
* direction of the path. If self-intersections cause
* different parts of the path to disagree as to the
* local winding direction, that is no matter as we just
* wait for the final answer to tell us which winding
* direction had greater representation. If the final
* result is zero then the path was equal parts clockwise
* and counter-clockwise and we do not care about which
* way we order it as either way will require half of the
* path to unwind and re-wind itself.
*/
double area = 0;
// Note that first and last points are the same so we
// do not need to process coords[0,1] against coords[n-2,n-1]
curx = bezierCoords[0];
cury = bezierCoords[1];
for (int i = 2; i < numCoords; i += 2) {
newx = bezierCoords[i];
newy = bezierCoords[i + 1];
area += curx * newy - newx * cury;
curx = newx;
cury = newy;
}
if (area < 0) {
/* The area is negative so the shape was clockwise
* in a Euclidean sense. But, our screen coordinate
* systems have the origin in the upper left so they
* are flipped. Thus, this path "looks" ccw on the
* screen so we are flipping it to "look" clockwise.
* Note that the first and last points are the same
* so we do not need to swap them.
* (Not that it matters whether the paths end up cw
* or ccw in the end as long as all of them are the
* same, but above we called this section "Clockwise
* Enforcement", so we do not want to be liars. ;-)
*/
// Note that [0,1] do not need to be swapped with [n-2,n-1]
// So first pair to swap is [2,3] and [n-4,n-3]
int i = 2;
int j = numCoords - 4;
while (i < j) {
curx = bezierCoords[i];
cury = bezierCoords[i + 1];
bezierCoords[i] = bezierCoords[j];
bezierCoords[i + 1] = bezierCoords[j + 1];
bezierCoords[j] = curx;
bezierCoords[j + 1] = cury;
i += 2;
j -= 2;
}
}
}
public int getWindingRule() {
return windingrule;
}
public int getNumCoords() {
return numCoords;
}
public double getCoord(int i) {
return bezierCoords[i];
}
public double[] getTvals() {
if (myTvals != null) {
return myTvals;
}
// assert(numCoords >= 8);
// assert(((numCoords - 2) % 6) == 0);
double tvals[] = new double[(numCoords - 2) / 6 + 1];
// First calculate total "length" of path
// Length of each segment is averaged between
// the length between the endpoints (a lower bound for a cubic)
// and the length of the control polygon (an upper bound)
double segx = bezierCoords[0];
double segy = bezierCoords[1];
double tlen = 0;
int ci = 2;
int ti = 0;
while (ci < numCoords) {
double prevx, prevy, newx, newy;
prevx = segx;
prevy = segy;
newx = bezierCoords[ci++];
newy = bezierCoords[ci++];
prevx -= newx;
prevy -= newy;
double len = Math.sqrt(prevx * prevx + prevy * prevy);
prevx = newx;
prevy = newy;
newx = bezierCoords[ci++];
newy = bezierCoords[ci++];
prevx -= newx;
prevy -= newy;
len += Math.sqrt(prevx * prevx + prevy * prevy);
prevx = newx;
prevy = newy;
newx = bezierCoords[ci++];
newy = bezierCoords[ci++];
prevx -= newx;
prevy -= newy;
len += Math.sqrt(prevx * prevx + prevy * prevy);
// len is now the total length of the control polygon
segx -= newx;
segy -= newy;
len += Math.sqrt(segx * segx + segy * segy);
// len is now sum of linear length and control polygon length
len /= 2;
// len is now average of the two lengths
/* If the result is zero length then we will have problems
* below trying to do the math and bookkeeping to split
* the segment or pair it against the segments in the
* other shape. Since these lengths are just estimates
* to map the segments of the two shapes onto corresponding
* segments of "approximately the same length", we will
* simply fudge the length of this segment to be at least
* a minimum value and it will simply grow from zero or
* near zero length to a non-trivial size as it morphs.
*/
if (len < MIN_LEN) {
len = MIN_LEN;
}
tlen += len;
tvals[ti++] = tlen;
segx = newx;
segy = newy;
}
// Now set tvals for each segment to its proportional
// part of the length
double prevt = tvals[0];
tvals[0] = 0;
for (ti = 1; ti < tvals.length - 1; ti++) {
double nextt = tvals[ti];
tvals[ti] = prevt / tlen;
prevt = nextt;
}
tvals[ti] = 1;
return (myTvals = tvals);
}
public void setTvals(double newTvals[]) {
double oldCoords[] = bezierCoords;
double newCoords[] = new double[2 + (newTvals.length - 1) * 6];
double oldTvals[] = getTvals();
int oldci = 0;
double x0, xc0, xc1, x1;
double y0, yc0, yc1, y1;
x0 = xc0 = xc1 = x1 = oldCoords[oldci++];
y0 = yc0 = yc1 = y1 = oldCoords[oldci++];
int newci = 0;
newCoords[newci++] = x0;
newCoords[newci++] = y0;
double t0 = 0;
double t1 = 0;
int oldti = 1;
int newti = 1;
while (newti < newTvals.length) {
if (t0 >= t1) {
x0 = x1;
y0 = y1;
xc0 = oldCoords[oldci++];
yc0 = oldCoords[oldci++];
xc1 = oldCoords[oldci++];
yc1 = oldCoords[oldci++];
x1 = oldCoords[oldci++];
y1 = oldCoords[oldci++];
t1 = oldTvals[oldti++];
}
double nt = newTvals[newti++];
// assert(nt > t0);
if (nt < t1) {
// Make nt proportional to [t0 => t1] range
double relt = (nt - t0) / (t1 - t0);
newCoords[newci++] = x0 = interp(x0, xc0, relt);
newCoords[newci++] = y0 = interp(y0, yc0, relt);
xc0 = interp(xc0, xc1, relt);
yc0 = interp(yc0, yc1, relt);
xc1 = interp(xc1, x1, relt);
yc1 = interp(yc1, y1, relt);
newCoords[newci++] = x0 = interp(x0, xc0, relt);
newCoords[newci++] = y0 = interp(y0, yc0, relt);
xc0 = interp(xc0, xc1, relt);
yc0 = interp(yc0, yc1, relt);
newCoords[newci++] = x0 = interp(x0, xc0, relt);
newCoords[newci++] = y0 = interp(y0, yc0, relt);
} else {
newCoords[newci++] = xc0;
newCoords[newci++] = yc0;
newCoords[newci++] = xc1;
newCoords[newci++] = yc1;
newCoords[newci++] = x1;
newCoords[newci++] = y1;
}
t0 = nt;
}
bezierCoords = newCoords;
numCoords = newCoords.length;
myTvals = newTvals;
}
}
private static class Iterator implements PathIterator {
AffineTransform at;
Geometry g0;
Geometry g1;
double t;
int cindex;
public Iterator(AffineTransform at,
Geometry g0, Geometry g1,
double t) {
this.at = at;
this.g0 = g0;
this.g1 = g1;
this.t = t;
}
/**
* @{inheritDoc}
*/
public int getWindingRule() {
return g0.getWindingRule();
}
/**
* @{inheritDoc}
*/
public boolean isDone() {
return (cindex > g0.getNumCoords());
}
/**
* @{inheritDoc}
*/
public void next() {
if (cindex == 0) {
cindex = 2;
} else {
cindex += 6;
}
}
double dcoords[];
/**
* @{inheritDoc}
*/
public int currentSegment(float[] coords) {
if (dcoords == null) {
dcoords = new double[6];
}
int type = currentSegment(dcoords);
if (type != SEG_CLOSE) {
coords[0] = (float) dcoords[0];
coords[1] = (float) dcoords[1];
if (type != SEG_MOVETO) {
coords[2] = (float) dcoords[2];
coords[3] = (float) dcoords[3];
coords[4] = (float) dcoords[4];
coords[5] = (float) dcoords[5];
}
}
return type;
}
/**
* @{inheritDoc}
*/
public int currentSegment(double[] coords) {
int type;
int n;
if (cindex == 0) {
type = SEG_MOVETO;
n = 2;
} else if (cindex >= g0.getNumCoords()) {
type = SEG_CLOSE;
n = 0;
} else {
type = SEG_CUBICTO;
n = 6;
}
if (n > 0) {
for (int i = 0; i < n; i++) {
coords[i] = interp(g0.getCoord(cindex + i),
g1.getCoord(cindex + i),
t);
}
if (at != null) {
at.transform(coords, 0, coords, 0, n / 2);
}
}
return type;
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -