1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-03-15 11:50:12 +01:00
BezierInfo-2/data/tracing.jsx
2015-12-20 15:19:50 -08:00

170 lines
7.0 KiB
JavaScript

<p>Say you want to draw a curve with a dashed line, rather than a solid line,
or you want to move something along the curve at fixed distance intervals over
time, like a train along a track, and you want to use Bézier curves. Now you
have a problem.</p>
<p>The reason you have a problem is that Bézier curves are parametric functions
with non-linear behaviour, whereas moving a train along a track is about as
close to a practical example of linear behaviour as you can get. The problem
we're faced with is that we can't just pick <i>t</i> values at some fixed interval
and expect the Bézier functions to generate points that are spaced a fixed distance
apart. In fact, let's look at the relation between "distance long a curve" and
"<i>t</i> value", by plotting them against one another.</p>
<p>The following graphic shows a particularly illustrative curve, and it's length/<i>t</i>
plot. While the curve itself is a cubic curve, the plot shows that the distance function
along the curve is actually a function of a much higher order than the curve itself.</p>
<textarea class="sketch-code" data-sketch-preset="twopanel" data-sketch-title="The t-for-distance function">
HashMap<Float,Float> values = new HashMap<Float,Float>();
ArrayList<Float> keys = new ArrayList<Float>();
void setupCurve() {
curves.add(new BezierCurve( new Point[]{
new Point(150,140),
new Point(215,35),
new Point(25,275),
new Point(145,160)
}));
}
float lD=0, D, d, x, y;
void drawCurve(BezierCurve curve) {
additionals();
curve.draw();
D = curve.getCurveLength();
noAdditionals();
usePanelPadding();
nextPanel();
drawAxes("t",0,1, "d",0,D);
if(lD!=D) {
lD = D;
values.clear();
keys.clear();
for(float t=0.001; t<=1.0; t+=0.001) {
d = comp.getArcLength(t, curve.x_values, curve.y_values);
x = map(t,0,1,0,panelDim);
y = map(d,0,D,0,panelDim);
values.put(x,y);
keys.add(x);
}
}
stroke(100);
for(float x: keys) {
point(x, values.get(x));
}
}</textarea>
<p>We see a function that might be invertible, but we won't be able to do so, symbolically.
You may remember from the section on arc length that we cannot actually compute the true
arc length function as an expression of <i>t</i>, which means we also can't compute the true
inverted function that gives <i>t</i> as an expression of length. So how do we fix this?</p>
<p>One way is to do what the graphic does: simply run through the curve, determine its
<i>t</i>-for-length values as a set of discrete values at some high resolution (the graphic
uses 1000 discrete points), and then use those as a basis for finding an appropriate <i>t</i>
value, given a distance along the curve. This works quite well, actually, and is fairly fast
(you can move the curve around without noticeable lag on a 2 year old computer, for instance).</p>
<p>We can use some colour to show the difference between distance-based and time based intervals:
the following graph is similar to the previous one, except it segments the curve in terms of
equal-distance intervals. This shows as regular colour intervals going down the graph, but
the mapping to <i>t</i> values is not linear, so there will be (highly) irregular intervals
along the horizontal axis. It also shows the curve in an alternating colouring based on the
t-for-distance values we find our LUT:</p>
<textarea class="sketch-code" data-sketch-preset="threepanel" data-sketch-title="Fixed-interval coloring a curve">
HashMap<Float,Float> values = new HashMap<Float,Float>();
HashMap<Float,Float> inverted = new HashMap<Float,Float>();
ArrayList<Float> t_keys = new ArrayList<Float>();
ArrayList<Float> d_keys = new ArrayList<Float>();
void setupCurve() {
curves.add(new BezierCurve( new Point[]{
new Point(150,140),
new Point(215,35),
new Point(25,275),
new Point(145,160)
}));
}
float lD=0, D, d, x, y;
void drawCurve(BezierCurve curve) {
additionals();
curve.draw();
D = curve.getCurveLength();
float section = 15;
noAdditionals();
usePanelPadding();
nextPanel();
drawAxes("t",0,1, "d",0,D);
if(lD!=D) {
lD = D;
// clear everything
values.clear();
inverted.clear();
t_keys.clear();
d_keys.clear();
// rebuild;
for(float t=0.001; t<=1.0; t+=0.001) {
d = comp.getArcLength(t, curve.x_values, curve.y_values);
values.put(t,d);
inverted.put(d,t);
t_keys.add(t);
d_keys.add(d);
}
}
float x, y, ly=0;
ArrayList<Float> markers = new ArrayList<Float>();
color lc=0, c;
for(float d: d_keys) {
y = int(map(d,0,D,0,panelDim));
x = map(inverted.get(d),0,1,0,panelDim);
if(y%section==0 && ly != y) {
ly = y;
stroke(0,0,200,100);
line(0,y,x,y);
stroke(0,100,0,100);
line(x,0,x,y);
markers.add(inverted.get(d));
}
}
stroke(0);
for(float t: t_keys) {
x = map(t,0,1,0,panelDim);
y = map(values.get(t),0,D,0,panelDim);
point(x, y);
}
nextPanel();
additionals();
curve.draw();
noAdditionals();
for(int m=1, last=markers.size(); m<last; m++) {
BezierCurve segment = curve.split(markers.get(m-1), markers.get(m));
c = (m%2==0 ? color(0,255,120) : color(0,0,255));
segment.draw(c);
}
}</textarea>
<p>However, are there better ways? One such way is discussed in "<a href="http://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf"
>Moving Along a Curve with Specified Speed</a>" by David Eberly of Geometric Tools, LLC, but
basically because we have no explicit length function (or rather, one we don't have to
constantly compute for different intervals), you may simply be better off with a traditional
lookup table (LUT).</p>
<!--
<p>That said, this is an area of Bézier curves that I've not really investigated in any
depth, so if you think I'm missing something obvious and think I should have a look at
[<i>whatever thing you think I need to have a look at</i>], get in touch and I shall
try to update this section with new information.</p>
-->