- Converting a parametric graph into a
PathIterator - An applet demoing a
Spiral2Dclass
Parametric Equations and PathIterators
Suppose you have a parametric graph expressed as
x(t) and y(t): how do you draw that in Java?We need a java.awt.geom.PathIterator. With a PathIterator, we can call:
GeneralPath path = new GeneralPath();
path.append(myPathIterator, false);
(A separate blog article will cover actually making java.awt.Shape... this article already has a lot of ground to cover, so that comes later.)
The graph needs to be broken up into segments for the PathIterator. Specifically we should use cubic path segments. A cubic segment lets you supply 4 pieces of information. In this case those four pieces of information should be:
f(0) = v0
f(1) = v1
df/dt(0) = dv0
df/dt(1) = dv1
Having these 4 pieces lets us define the end points and the angles/tangents at each end point. Those angles are crucial to achieve a continuous-looking curve between segments.
Of course this is a parametric graph, so we have an x-equation and a y-equation -- and they can be treated in isolation.
The article "Shapes: an Introduction" explains how to convert bezier control points into an algebraic expression:
double ax = -lastX+3*x1-3*x2+x3;
double bx = 3*lastX-6*x1+3*x2;
double cx = -3*lastX+3*x1;
double dx = lastX;
//and x(t) = ax*(t^3)+bx*(t^2)+cx*t+dx
//and dx/dt(t) = 3*ax*(t^2)+2*bx*t+cx
So this is how each cubic segment is structured. Our task here is to calculate lastX, x1, x2 and x3. Our givens are:
x(0) = dx
x(1) = ax+bx+cx+dx
dx/dt(0) = cx
dx/dt(1) = 3*ax+2*bx+cx
Substitute our coefficients and reduce those expressions:
x(0) = lastX
x(1) = x3
dx/dt(0) = -3*lastX+3*x1
dx/dt(1) = -3*x2+3*x3
Now in this case we'll know our parametric functions; what we need to find are the points to define the bezier curve. So to isolate the x-variables:
lastX = x(0)
x3 = x(1)
x1 = (dx/dt(0)+3*x(0))/3
x2 = (3*x(1)-dx/dt(1))/3
I wrapped this up in a nice abstract class: the
ParametricPathIterator. The core methods you need to implement are:protected abstract double getX(double t);
protected abstract double getY(double t);
protected abstract double getDX(double t);
protected abstract double getDY(double t);
Also you're responsible for the methods
getMaxT() and getNextT(double prevT).Spirals
All this originally came up when a coworker recently asked for a
Spiral2D class. He found one from a programming book that provided an example, but:- It was line-based, so it looked bad.
- As per all examples in this book, the license to use this would be $500 for our company. (OK, really this should be problem #1.)
So having fleshed out the
ParametricPathIterator, this is a really easy undertaking. What is a spiral?x(t) = centerX+coilGap*t*cos(2*pi*t)
y(t) = centerY+coilGap*t*sin(2*pi*t)
The derivative is a little trickier. I had to refresh my memory on the product rule to really get this right:
dx/dt = coilGap*cos(2*pi*t)-2*pi*coilGap*t*sin(2*pi*t)
dy/dt = coilGap*sin(2*pi*t)-2*pi*coilGap*t*cos(2*pi*t)
Now in this case -- and in more complicated cases -- you don't necessarily need to calculate the derivative. If you wanted to be lazy you could just write this method:
protected double getDX(double t) {
double incr = .0000001;
return (getX(v+incr/2)-getX(v-incr/2))/incr;
}This is easier to program, but it will introduce some rounding errors as values get really small. Maybe there's not that much of a difference in practice? That's up to you to decide.
So that's the math. Codewise, these are the controls I wanted:
- center (Point2D): the center of this spiral.
- coilGap (double): the space between two coils.
- coils (double): the number of coil revolutions.
- clockwise (boolean): if this is false, we multiply the t expression in sine/cosine by negative one.
- offset (double): this is an angular offset in the sine/cosine.
- outward (boolean): this is a really subtle control. If this is
truethen the spiral will start at the center and spiral outward; if it isfalseit starts outside and spirals inward. This will not make a visual difference if you just want to "draw a spiral".
What's left to do? Not much. I need constructors, and a lot of getters and setters. The only thing that I haven't mentioned are the abstract methods
getNextT() and getMaxT(). In this case these are really simple:protected double getMaxT() {
return coils;
}
protected double getNextT(double t) {
//8 partitions in a coil
return t + 1.0/8.0;
}The number of partitions was arbitrary, but I found that if used 4 partitions in coil it didn't look right. A little more detail made a big visual difference.
Here is an applet that shows off this project:
This applet (source included) is available here.
p.s. Try clicking in the applet to define the endpoint for the spiral. (Also try using the shift key.)

0 comments:
Post a Comment