Your interest, Vitaliy, inspired me to revisit the old code, as I've learned a bit more about packed arrays and speed. I think we can speed this up a bit and make it a more effective interactive demonstration.
There are two important ways to speed up a graphics demonstration, GraphicsComplex
and using vectorized and compiled functions to transform the (packed) array of coordinates.
To create a GraphicsComplex
, we have to collect all the coordinates from txt
in an array and map each pair of coordinates to its index in the array. Nearest[array -> Automatic]
is a fast and easy way to do this.
coords = Cases[txt, {_Real, _Real}, Infinity];
nf = Nearest[coords -> Automatic]; (* for remapping coordinates to GraphicsComplex indices *)
txt2 = txt /. p : {_Real, _Real} :> First@nf[p];
Now the coordinates are stored in coords
and txt2
consist of all the graphics primitives needed to draw the text at whatever points are specified by coords
. To transform the text, we need only transform coords
. The primitives txt2
never have to be changed. This is the source of the speed up. First, GraphicsComplex
is handled very efficiently in rendering by the front end. Next, all that is left to do is to efficiently transform the coordinates.
To do that we want to take advantage of the fact that many basic numerical functions are vectorized (operate on vectors) efficiently or are Listable
. If we can refactor the code to treat all the x-coordinates as a vector and all the y-coordinates as a vector, we can take advantage of this strength of Mathematica. These operations are especially fast on packed arrays, which you can read about in the documentation or in this article by Rob Knapp, http://library.wolfram.com/infocenter/Demos/391/.
We can speed up the unit normal NN
by precomputing the derivative of c
and compiling the resulting expression. It is vectorized in the sense that it takes as an argument a vector of "times" t
(along the parametrization) and returns an array consisting of the normal at each time. (See note below about other minor changes.) The only change to maptext
that is needed for it to take vector arguments is to transpose the result of c[{t1, t2, ..}]
. With a vector t
input, c[t]
returns coordinates in the form {{x1, x2, ..}, {y1, y2, ..}}
. Finally we separate the coordinates of the text into two vectors xx
and yy
for convenience.
Clear[NN, maptext];
Block[{t}, (* protect t during evaluation of definition *)
NN = Compile[{{t, _Real, 1}}, Transpose@#] &@
Block[{Abs = Sqrt[#^2] &}, Normalize@Cross[c'[t]]] (* evaluate this expression before compiling *)
];
maptext[s_, ?n_] := With[{t = invarclength[Mod[s, totalarclength]]}, Transpose@c[t] + ?n NN[t]];
{xx, yy} = Transpose[coords];
Now maptext
with vector arguments in terms of xx
and yy
returns the transformed coordinates. It is passed to GraphicsComplex
as the first argument wrapped in Dynamic
, since it is the only computation that needs dynamic updating. (And since all the points need updating, this is about as efficient a design as one can get, I think.)
Manipulate[
Show[ParametricPlot[c[t], {t, 0, 1}, PlotStyle -> Directive[Red, Opacity[.2], Dashed]],
Graphics[GraphicsComplex[
Dynamic[maptext[-fs xx / xRange - s0, -fs (1 + .5 Sin[20 xx / xRange - s0]) yy / xRange + ?N]],
txt2]],
PlotRange -> {{-2.1, 2.1}, {-1.1, 1.1}}, Axes -> False, ImageSize -> 800],
{{?N, .15}, -1, 1}, {{s0, 0}, 0, totalarclength}, {{fs, 5, "font scale"}, 0.1, 5}]
The response of this is so fast on my laptop that I prefer to hold down the Option (Alt) key while moving the s0
slider.
Note: Cross[{x, y}]
rotates the vector 90 degrees to {-y, x}
, the same as {{0, -1}, {1, 0}} . {x, y}
. By temporarily setting Abs = Sqrt[#^2]&
, the result of Normalize
is automatically simplified. It's just an alternative to c'[t]/Sqrt[c'[t].c'[t]]
, which differentiates c
three times; if repeated many times (there are over 5000 points in txt
), it would slow things a bit, about 0.22 sec. on my machine to do 15000 differentiations. The code above computes the expression for NN
just once, before the Manipulate
is evaluated, so it makes little difference. The compiled functions would be the same.