# Showcase of translations to Asymptote from TikZ/PSTricks

So you already know TikZ and/or PSTricks, but you would like to expand your knowledge by learning Asymptote as well? Here's your chance.

The task/question: Find an example of an impressive diagram drawn with TikZ or PSTricks (ideally but not necessarily one you created yourself), and redraw it using Asymptote. Your redrawn version should be at least as good as the original, except that 3d Asymptote pictures are allowed to be high-resolution rasterized images even if the original was a vector drawing. You should include, at a minimum, a link to the original; ideally, you should include (if allowed by copyright) the source code for the original and a picture of it. My hope is that ultimately, the answers to this question will become a useful resource for TikZ and PSTricks users seeking to learn Asymptote.

To sweeten the deal, I will be awarding a bounty of 500 reputation points to the most impressive answer. "Most impressive" will be determined by votes at the time the bounty ends, although I reserve the right to disqualify answers that I believe violate the letter or the spirit of original question.

Additional, semi-selfish motive: Please let me know if you find my Asymptote tutorial useful. Other potentially useful resources include the official documentation and this tutorial in French for 3d stuff.

If you would like to answer this question but would like more specific examples, try translating answers from this question on drawing an egg or this question on drawing a Christmas tree.

• Note that I am planning to start the bounty, but the StackExchange software will not let me do so until the question has been around for two days. – Charles Staats Dec 23 '13 at 17:27
• While I understand that "showcases" are often good to look at, I doubt that this question fits into tex.sx, especially compared to other questions which are asked to be edited to fit this site. Wouldn't it be better to pose a question of sorts "how can this particular use-case be solved by means of asymptote"? The question could be based on one or two designated use-cases for which answers of tikz/pstricks are available. Maybe it would even be better to post a new answer to such a question... – Christian Feuersänger Dec 23 '13 at 19:15
• @ChristianFeuersänger: I agree that this question is borderline, and should probably be closed after the bounty is awarded so as not to encourage too many similar questions. However, I was looking for a question that would encourage TikZ and PSTricks users to try translating their own projects into Asymptote, and I don't see how to do that with a more conventional question. – Charles Staats Dec 23 '13 at 21:52
• I think your document is great, hats off :) Had been playing around with it for a while now. I think for straightforward TikZ syntax examples xparse can do the translations but far from trivial. – percusse Dec 23 '13 at 23:46
• asymptote.sourceforge.net/doc/Editing-modes.html. nice Emacs mode for editing asymptotes. – Dror Dec 26 '13 at 6:27

For a baseline, here's an example I did when I was first learning Asymptote. The original TikZ picture (taken from Lecture 2 of these class notes) is on the left; the Asymptote translation is on the right.

The code is below. Note that the translation is a bit more thorough than necessary--for instance, I don't expect most answers to worry about changing the default line width from 0.5pt to 0.4pt. It's also less thorough than it conceivably could be: the arrow tips are not identical, nor are the heights of the labels.

\documentclass[margin=10pt]{standalone}
\usepackage{tikz}
\usepackage[squaren]{SIunits}
\usepackage[inline]{asymptote}

\begin{document}
\begin{asydef}
defaultpen(fontsize(10pt));
\end{asydef}
\begin{tikzpicture}[scale=4.0, axes/.style={thick,->}]
\draw[axes] (-1.2,0) -- (1.2,0) node[right] {$x$};
\draw[axes] (0,-1.2) -- (0,1.2) node[above] {$y$};

\draw[->] (0.2,0) node[above right]{$\scriptstyle t~\rad$} arc[start angle=0, end angle=30, radius=0.2];
\draw[->] (1.07,0) arc[start angle=0, end angle=30, radius=1.07] node[right]{$t$};
\draw (0,0) -- node[below]{$\Delta x = \cos t$} ({sqrt(3)/2},0)
-- node[right,fill=white]{$\Delta y = \sin t$} ({sqrt(3)/2},0.5)
-- cycle;
\path (0,0) -- node[above]{$1$} ({sqrt(3)/2},0.5);
\end{tikzpicture}

\begin{asy}
unitsize(4cm);
pen tikzthick = linewidth(0.8pt);
defaultpen(linewidth(0.4pt));       // This sets the default pen width to 0.4pt to match TikZ; in Asymptote, the default width is 0.5pt.

draw((-1.2,0)--(1.2,0), arrow=Arrow(TeXHead), L=Label("$x$",EndPoint), p=tikzthick);
draw((0,-1.2)--(0,1.2), arrow=Arrow(TeXHead), L=Label("$y$",EndPoint), p=tikzthick);

draw(circle(c=(0,0), r=1));

draw(arc(c=(0,0), r=0.2, angle1=0, angle2=30),
L=Label("$\scriptstyle t~\rad$",position=BeginPoint,align=NE) );
draw(arc(c=(0,0), r=1.07, angle1=0, angle2=30),
L=Label("$t$",position=EndPoint,align=E) );
path triangle = (0,0)--(sqrt(3)/2,0)--(sqrt(3)/2,1/2)--cycle;
draw(triangle);
label(subpath(triangle,0,1), L="$\Delta x = \cos t$", align=S);
label(subpath(triangle,1,2), L="$\Delta y = \sin t$", align=E, filltype=Fill(white));
label(subpath(triangle,2,3), L="$1$", align=N);
\end{asy}
\end{document}

• In case anyone is wondering, I am of course ineligible for my own bounty and I do not expect to be offering any other answers to this question. – Charles Staats Dec 24 '13 at 19:41

Stolen from the answers of this question 3D helix torus with hidden lines. It is about a helix wrapping around another helix which also wraps around a torus. Let me call it as the second order of helix wrapping around a torus, just for the sake of simplicity when referring to it.

## Herbert Voss' solution with the almighty PSTricks

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-solides3d}
\begin{document}
\begin{pspicture}[solidmemory](-6.5,-3.5)(6.5,3)
\psset{viewpoint=30 0 15 rtp2xyz,Decran=30,lightsrc=viewpoint}
\psSolid[object=tore,r1=5,r0=1,ngrid=36 36,tablez=0 0.05 1 {} for,
zcolor= 1 .5 .5 .5 .5 1,action=none,name=Torus]
\pstVerb{/R1 5 def /R0 1.2 def /k 20 def /RL 0.15 def /kRL 40 def}%
\defFunction[algebraic]{helix}(t)
{(R1+R0*cos(k*t))*sin(t)+RL*sin(kRL*k*t)}
{(R1+R0*cos(k*t))*cos(t)+RL*cos(kRL*k*t)}
{R0*sin(k*t)+RL*sin(kRL*k*t)}
\psSolid[object=courbe,
resolution=7800,
fillcolor=black,incolor=black,
r=0,
range=0 6.2831853,
function=helix,action=none,name=Helix]%
\psSolid[object=fusion,base=Torus Helix,grid]
\end{pspicture}
\end{document}


## Charles Staats' solution with the omnipotent Asymptote

settings.outformat = "png";
settings.render = 16;
settings.prc = false;
real unit = 2cm;
unitsize(unit);

import graph3;

void drawsafe(path3 longpath, pen p, int maxlength = 400) {
int length = length(longpath);
if (length <= maxlength) draw(longpath, p);
else {
int divider = floor(length/2);
drawsafe(subpath(longpath, 0, divider), p=p, maxlength=maxlength);
drawsafe(subpath(longpath, divider, length), p=p, maxlength=maxlength);
}
}

struct helix {
path3 center;
path3 helix;
int numloops;
int pointsperloop = 12;
/* t should range from 0 to 1*/
triple centerpoint(real t) {
return point(center, t*length(center));
}
triple helixpoint(real t) {
return point(helix, t*length(helix));
}
triple helixdirection(real t) {
return dir(helix, t*length(helix));
}
/* the vector from the center point to the point on the helix */
triple displacement(real t) {
return helixpoint(t) - centerpoint(t);
}
bool iscyclic() {
return cyclic(helix);
}
}

path3 operator cast(helix h) {
return h.helix;
}

helix helixcircle(triple c = O, real r = 1, triple normal = Z) {
helix toreturn;
toreturn.center = c;
toreturn.helix = Circle(c=O, r=r, normal=normal, n=toreturn.pointsperloop);
toreturn.numloops = 1;
}

helix toreturn;
toreturn.numloops = numloops;
from toreturn unravel pointsperloop;
toreturn.center = center.helix;
int n = numloops * pointsperloop;
triple[] newhelix;
for (int i = 0; i <= n; ++i) {
real theta = (i % pointsperloop) * 2pi / pointsperloop;
real t = i / n;
triple ihat = unit(center.displacement(t));
triple khat = center.helixdirection(t);
triple jhat = cross(khat, ihat);
triple newpoint = center.helixpoint(t) + radius*(cos(theta)*ihat + sin(theta)*jhat);
newhelix.push(newpoint);
}
toreturn.helix = graph(newhelix, operator ..);
}

int loopfactor = 20;
helix wrap(helix input, int order, int initialloops = 10, real initialradius = 0.6, int loopfactor=loopfactor) {
helix toreturn = input;
int loops = initialloops;
for (int i = 1; i <= order; ++i) {
loops *= loopfactor;
}
}

currentprojection = perspective(12,0,6);

helix circle = helixcircle(r=2, c=O, normal=Z);

/* The variable part of the code starts here. */
int order = 2;    // This line varies.
real safefactor = 1;
for (int i = 1; i < order; ++i)

helix todraw = wrap(circle, order=order, initialradius = helixradius, loopfactor=40);    // This line varies (optional loopfactor parameter).

surface torus = surface(Circle(c=2X, r=0.99*saferadius, normal=-Y, n=32), c=O, axis=Z, n=32);
material toruspen = material(diffusepen=gray, ambientpen=white);
draw(torus, toruspen);

drawsafe(todraw, p=0.5purple+linewidth(0.6pt));  // This line varies (linewidth only).


## About the Charles Staats' tutorial

The tutorial I regard as beautiful might also be regarded by other people as useful. (Donut E. Knot)

• +1, but this is not really a translation. For instance, a "translation" of Herbert Voss's answer would use the same parametrization he did for the second-order helix, same viewpoint, and possibly the same color scheme (and would probably be a lot shorter than what I wrote, which was designed such that I would not have to compute the helix parametrization by hand). – Charles Staats Dec 24 '13 at 15:50

# Disclaimer

This is the first picture I ever made using asymptote, so please comment.

I adapted a tikz answer I gave once here: Plot basic complex transformation in LaTeX

# TikZ Code

\documentclass[tikz]{standalone}
\usetikzlibrary{decorations.markings}
\tikzset{
arrow inside/.style = {
postaction = {
decorate,
decoration={
markings,
mark=at position 0.5 with {\arrow{>}}
}
}
}
}
\begin{document}
\begin{tikzpicture}[>=latex,scale=1.5]
\begin{scope}
% Axes
\draw (0,0) node[below left] {$O$}
(-0.5,0) -- (4,0) node[below] {$x$}
(0,-0.5) -- (0,3) node[left] {$y$};
% Ticks
\draw (1,0) -- (1,-0.1) node[below] {$a$}
(3,0) -- (3,-0.1) node[below] {$b$}
(0,1) -- (-0.1,1) node[left] {$c$}
(0,2) -- (-0.1,2) node[left] {$d$};
% Square
\draw[thick] (1,1) node[below left] {$A$} --
(3,1) node[below right] {$B$} --
(3,2) node[above right] {$C$} --
(1,2) node[above left] {$D$} -- cycle;
\draw[arrow inside] (1.5,1) -- (1.5,2);
\end{scope}

\begin{scope}[xshift=6cm]
% Axes
\draw (0,0) node[below left] {$O$}
(-0.5,0) -- (4,0) node[below] {$u$}
(0,-0.5) -- (0,3) node[left] {$v$};
%Help Lines
\draw (0,0) -- (30:3) (0,0) -- (70:3);
% Angles
\draw[->] (0.6,0) arc[start angle=0, end angle=70, radius=0.6] node[above right] {\small $\phi = d$};
\draw[->] (0.8,0) node[above right] {\small$\phi = c$} arc[start angle=0, end angle=30, radius=0.8];
% Transformation
\draw[thick] (30:1.5) node[right] {$A'$} --
(30:3) node[below right] {$B'$} arc[start angle=30, end angle=70, radius=3]
(70:3) node[above right] {$C'$} --
(70:1.5) node[above left] {$D'$} arc[start angle=70, end angle=30, radius=1.5];
\draw[arrow inside] (30:1.9) arc[start angle=30, end angle=70, radius=1.9];
\end{scope}
\end{tikzpicture}
\end{document}


# Asymptote Code

\documentclass{standalone}
\usepackage[inline]{asymptote}
\begin{document}
\begin{asy}
import geometry;
settings.outformat = "pdf";
unitsize(1.5cm);

picture realpane;
unitsize(realpane,1.5cm);

real x = 4.0, y = 3.0;
real a = 1.0, b = 3.0, c = 1.0, d = 2.0;

// Axes
label(realpane, "$O$", (0,0), align=SW);
draw(realpane, (-0.5,0) -- (x,0), L=Label("$x$", align=S, position=EndPoint));
draw(realpane, (0,-0.5) -- (0,y), L=Label("$y$", align=W, position=EndPoint));

// Ticks
draw(realpane, (a,0) -- (a,-0.1), L=Label("$a$",align=S));
draw(realpane, (b,0) -- (b,-0.1), L=Label("$b$",align=S));
draw(realpane, (0,c) -- (-0.1,c), L=Label("$c$",align=W));
draw(realpane, (0,d) -- (-0.1,d), L=Label("$d$",align=W));

// Square
draw(realpane, box((a,c),(b,d)), p=linewidth(2));
label(realpane, "$A$", (a,c), align=SW);
label(realpane, "$B$", (b,c), align=SE);
label(realpane, "$C$", (b,d), align=NE);
label(realpane, "$D$", (a,d), align=NW);
draw(realpane, (a+0.5,c) -- (a+0.5,d), arrow=MidArrow());

picture complexpane;
unitsize(complexpane,1.5cm);

pair A = 1.5*dir(30), B = 3*dir(30), C = 3*dir(70), D = 1.5*dir(70);

// Axes
label(complexpane, "$O$", (0,0), align=SW);
draw(complexpane, (-0.5,0) -- (x,0), L=Label("$u$", align=S, position=EndPoint));
draw(complexpane, (0,-0.5) -- (0,y), L=Label("$v$", align=W, position=EndPoint));

// Help Lines
draw(complexpane, (0,0) -- B);
draw(complexpane, (0,0) -- C);

// Angles
draw(complexpane, arc((x,0),(0,0),D,0.6), L=Label("$\phi = d$", align=NE, position=EndPoint), arrow=Arrow());
draw(complexpane, arc((x,0),(0,0),A,0.8), L=Label("$\phi = c$", align=E, position=MidPoint), arrow=Arrow());

// Transformation
draw(complexpane, A -- B -- arc(B,(0,0),C,3) -- C -- D -- arc(D,(0,0),A,1.5), p=linewidth(2));
label(complexpane, "$A'$", A, align=E);
label(complexpane, "$B'$", B, align=SE);
label(complexpane, "$C'$", C, align=NE);
label(complexpane, "$D'$", D, align=NW);
draw(complexpane, arc(B,(0,0),C,1.9), arrow=MidArrow());

\end{asy}
\end{document}


# Corrected Asymptote code

Thanks to Charles Staats comments, I was able to improve the code and get rid of the extra picture stuff.

\documentclass{standalone}
\usepackage[inline]{asymptote}
\begin{document}
\begin{asy}
import geometry;
settings.outformat = "pdf";
unitsize(1.5cm);
pen thick = linewidth(1.6pt);

real x = 4.0, y = 3.0;
real a = 1.0, b = 3.0, c = 1.0, d = 2.0;

// Axes
label("$O$", (0,0), align=SW);
draw((-0.5,0) -- (x,0), L=Label("$x$", align=S, position=EndPoint));
draw((0,-0.5) -- (0,y), L=Label("$y$", align=W, position=EndPoint));

// Ticks
draw((a,0) -- (a,-0.1), L=Label("$a$",align=S));
draw((b,0) -- (b,-0.1), L=Label("$b$",align=S));
draw((0,c) -- (-0.1,c), L=Label("$c$",align=W));
draw((0,d) -- (-0.1,d), L=Label("$d$",align=W));

// Square
draw(box((a,c),(b,d)), p=thick);
label("$A$", (a,c), align=SW);
label("$B$", (b,c), align=SE);
label("$C$", (b,d), align=NE);
label("$D$", (a,d), align=NW);
draw((a+0.5,c) -- (a+0.5,d), arrow=MidArrow());

currentpicture = shift(-6,0)*currentpicture;

pair A = 1.5*dir(30), B = 3*dir(30), C = 3*dir(70), D = 1.5*dir(70);

// Axes
label("$O$", (0,0), align=SW);
draw((-0.5,0) -- (x,0), L=Label("$u$", align=S, position=EndPoint));
draw((0,-0.5) -- (0,y), L=Label("$v$", align=W, position=EndPoint));

// Help Lines
draw((0,0) -- B);
draw((0,0) -- C);

// Angles
draw(arc((x,0),(0,0),D,0.6), L=Label("$\phi = d$", align=N+1.5E, position=EndPoint), arrow=ArcArrow());
draw(arc((x,0),(0,0),A,0.8), L=Label("$\phi = c$", align=E, position=MidPoint), arrow=ArcArrow());

// Transformation
draw(A -- B -- arc(B,(0,0),C,3) -- C -- D -- arc(D,(0,0),A,1.5), p=thick);
label("$A'$", A, align=SE);
label("$B'$", B, align=SE);
label("$C'$", C, align=NE);
label("$D'$", D, align=NW);
draw(arc(B,(0,0),C,1.9), arrow=MidArcArrow());

\end{asy}
\end{document}


• Excellent--this is exactly the sort of thing I am looking for! (More comments to come when I'm in less of a hurry.) – Charles Staats Dec 29 '13 at 15:07
• Some comments, questions, and suggestions: 1. Your use of picture objects is impressive, especially for a beginner, and probably the best reasonable translation for TikZ scopes. At the same time, there is a much easier way to achieve the desired effect in this image: the line currentpicture = shift(-6,0)*currentpicture; should shift everything that has come before six units to the left. 2. linewidth(2) is used for lines with thickness 2pt, which is extremely thick; by comparison, ultra thick tikz lines have thickness 1.6pt. 3. In the \phi=d label, try using align=N+2E. [to be ctd] – Charles Staats Dec 29 '13 at 18:25
• [ctd from previous comment] 4. Try using ArcArrow() and MidArcArrow() instead of Arrow() and MidArrow(). It will look more like the arrows in the TikZ version. 5. Once again, this is a great answer even without any of the tweaks I suggest. In particular, if you do follow my first suggestion, I think you should append the new version to this answer rather than replacing it, since your use of picture objects may be a useful example for others. – Charles Staats Dec 29 '13 at 18:36
• @CharlesStaats Thank you very much for commenting! I implemented the changes, you suggested in my updated answer. – Henri Menke Dec 29 '13 at 18:52

Here's a more direct translation of Herbert Voss's torus with a slinky wrapped around it.

First, some boilerplate:

settings.outformat = "png";
settings.render=16;
size(13cm,0);
import graph3;


The first line is, I think, reasonably clear. A png file is used rather than a vector format because, currently, Asymptote produces better 3d graphics when they are rasterized. The second line sets the resolution to 16 pixels per postscript point. (1 postscript point = 1/72 inch.) The third line sets the scale such that the final picture will have width equal to 13 cm. The fourth line gives us access not only to Asymptote's three-dimensional drawing facilities, but also to its three-dimensional scientific graphing module. The latter is needed since the slinky is specified as a three-dimensional parametric curve.

Setting the projection

The pstricks code viewpoint=30 0 15 rtp2xyz places the camera thirty units away from the origin and 15 degrees north of the equator. The corresponding code in Asymptote is

currentprojection = perspective(30*dir(75,0));


which places the camera 75 degrees south of the north pole and 30 units away from the origin. The placement of the picture plane is handled automatically, so there is no need for anything corresponding to Decran=30. Likewise, the default light source does not, in my opinion, require adjusting, although it would be possible to adjust it.

Constructing a torus

In Asymptote, there is, so far as I know, no built-in functionality to construct a torus directly. However, it is easy enough to construct a torus as a solid of revolution. [An alternative approach not discussed here is to construct the torus as a tube, i.e., a path (a circle) with width.]

First, let's define some values:

real r1=5, r0=1;
int nu = 36, nv = 36;


The torus we construct will have cross-section a circle of radius r0, with its center at distance r1 from the origin. This circle will be approximated by 36 bezier curves, since the torus to be constructed is supposed to be given by a 36 x 36 grid of bezier patches.

path3 crossSection = Circle(r=r0, c=(r1,0,0), normal=Y, n= nu);


Next, we want to revolve this circle about Z-axis. The following line of code constructs a surface by revolving crossSection in 36 segments about the line through the point (0,0,0) and in the direction of the vector Z (which is an alias for (0,0,1)):

surface torus = surface(crossSection, c=(0,0,0), axis=Z, n=nv);


Finally, let's draw the torus and see where we are:

draw(torus, lightgray);


Here's the result:

Coloring the torus

While this does not look bad, it hardly an accurate translation of Herbert Voss's torus, which is colored in a particular pattern. To get this right, we need to define a function that colors the surface:

pen colorFunction(int u, real theta) {
real z = sin(u/nu * 2pi);
real t = (z + 1) / 2;
return interp(red, lightblue, t);
}


This function takes two parameters--an integer u, which describes how far along the cross-section a point is; and the angle theta, which describes (in degrees) how far the point has been revolved from the original cross-section. For our purposes, theta is irrelevant. The line real z = sin(u/nu * 2pi); creates a new variable z (of type real, i.e., a real number). The integer u initially ranges from 0 to nu = 36, since the circle has 36 segments by construction. u/nu * 2pi reparametrizes this to go from 0 to 2pi. (Note that in Asymptote, 2pi implicitly means 2*pi; the same holds if pi is replaced by any variable of type real.) Taking the sin then gives the height of the corresponding point on the unit circle. This means the value of z will range from -1 to 1. In order to interpolate, we would rather have it range from 0 to 1; this is the purpose of the line real t = (z+1)/2;. Finally, interp(red, lightblue, t) interpolates between the two extremes of red (when t=0) and lightblue (when t=1). It is equivalent to (1-t)*red + t*blue.

Once this function is defined, it can be used to construct a torus with a gradient color shading:

surface torus = surface(crossSection, c=(0,0,0), axis=Z, n=nv, color = colorFunction);
draw(torus);


The result:

Graphing a parametric curve

There's still an important element missing--the parametric curve that gives the slinky wrapped around the torus. Here's how to define a parametric curve, using Herbert Voss's parametrization:

real R1 = r1, R0 = 1.2, k = 20, RL = 0.15, kRL = 40;

triple helix(real t) {
return ( (R1+R0*cos(k*t))*sin(t)+RL*sin(kRL*k*t),
(R1+R0*cos(k*t))*cos(t)+RL*cos(kRL*k*t),
R0*sin(k*t)+RL*sin(kRL*k*t));
}
path3 Helix = graph(helix, n=7800, 0, 2pi);
draw(Helix);


The result: Asymptote runs for a while and then outputs

Illegal instruction: 4


and stops. As far as I can determine, this is some sort of weird out-of-memory bug that shows up when attempting to draw very long three-dimensional paths. (This one has, I believe, 7799 segments.)

Bug workaround

To work around this bug, use the following recursive method, which breaks the path down into manageable chunks before drawing it:

void drawsafe(path3 longpath, pen p = currentpen, int maxlength = 400) {
int length = length(longpath);
if (length <= maxlength) draw(longpath, p);
else {
int divider = floor(length/2);
drawsafe(subpath(longpath, 0, divider), p=p, maxlength=maxlength);
drawsafe(subpath(longpath, divider, length), p=p, maxlength=maxlength);
}
}
drawsafe(Helix);


The final result:

settings.outformat = "png";
settings.render=16;
size(13cm,0);
import graph3;

currentprojection = perspective(30*dir(75,0));

void drawsafe(path3 longpath, pen p = currentpen, int maxlength = 400) {
int length = length(longpath);
if (length <= maxlength) draw(longpath, p);
else {
int divider = floor(length/2);
drawsafe(subpath(longpath, 0, divider), p=p, maxlength=maxlength);
drawsafe(subpath(longpath, divider, length), p=p, maxlength=maxlength);
}
}

real r1=5, r0=1;
int nu = 36, nv = 36;
path3 crossSection = Circle(r=r0, c=(r1,0,0), normal=Y, n= nu);

pen colorFunction(int u, real theta) {
real z = sin(u/nu * 2pi);
real t = (z + 1) / 2;
return interp(red, lightblue, t);
}

surface torus = surface(crossSection, c=(0,0,0), axis=Z, n=nv, color = colorFunction);
draw(torus);

real R1 = r1, R0 = 1.2, k = 20, RL = 0.15, kRL = 40;

triple helix(real t) {
return ( (R1+R0*cos(k*t))*sin(t)+RL*sin(kRL*k*t),
(R1+R0*cos(k*t))*cos(t)+RL*cos(kRL*k*t),
R0*sin(k*t)+RL*sin(kRL*k*t));
}
path3 Helix = graph(helix, n=7800, 0, 2pi);
drawsafe(Helix);


The original pstricks version is more succinct, but in my admittedly biased opinion, less beautiful:

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-solides3d}
\begin{document}
\begin{pspicture}[solidmemory](-6.5,-3.5)(6.5,3)
\psset{viewpoint=30 0 15 rtp2xyz,Decran=30,lightsrc=viewpoint}
\psSolid[object=tore,r1=5,r0=1,ngrid=36 36,tablez=0 0.05 1 {} for,
zcolor= 1 .5 .5 .5 .5 1,action=none,name=Torus]
\pstVerb{/R1 5 def /R0 1.2 def /k 20 def /RL 0.15 def /kRL 40 def}%
\defFunction[algebraic]{helix}(t)
{(R1+R0*cos(k*t))*sin(t)+RL*sin(kRL*k*t)}
{(R1+R0*cos(k*t))*cos(t)+RL*cos(kRL*k*t)}
{R0*sin(k*t)+RL*sin(kRL*k*t)}
\psSolid[object=courbe,
resolution=7800,
fillcolor=black,incolor=black,
r=0,
range=0 6.2831853,
function=helix,action=none,name=Helix]%
\psSolid[object=fusion,base=Torus Helix,grid]
\end{pspicture}
\end{document}

• @Manuel: Unfortunately, png images above a certain size are automatically converted to jpegs when they are uploaded. – Charles Staats Mar 25 '14 at 1:02

Asymptote vs Sage

I am submitting an example created in Asymptote which is and isn't working (a bug?), let me hope it won't offend someone. I had an opportunity to compare Asymptote and Sage (neither TikZ nor PSTricks this time, I'm sorry), I would like to share my modest experience anyway.

A story

But before that, if you are begging so nicely, I will tell you a story behind the scene of creating these pictures. There is always a story behind our work.

I met a girl once. Virtually, of course, how else? And I wanted to make an impression on this mathematician. So I was given an assignment (her school homework) to solve three out of her three tasks before I would get a chance of meeting her face-to-face. Tasks from mathematics, something with integrals to compute... I decided to go for an interactive mathematics, I was so thrilled about all this that I parallely solved the same tasks in two programs. In Sage (Python with support of Jmol) and in Asymptote (PDF).

I did my best and you will see the results in a minute. But there was a catch! My results weren't virtually perfect. The Sage notebook wasn't publicly available at that time and I couldn't persuade Jmol to export me the 3D model to PDF.

And how about Asymptote? Even Asymptote didn't save the day. If you try my example and uncomment if condition (lines 25 and 32) you will get an error no matching variable 'f' (even Linux and Microsoft Windows agreed on this one). If you try to save a day by uncommenting line 20 in addition to that, you are getting a plane!

Once she saw that, she had left me at once, and, virtually, how else?

Looking back, I think she was no mathematician, but she was a TeXist! Poor me!

Back to reality

Sage provided me a very fine interface. It was interactive, the equations were typeset in TeX, it computed areas, volumes and the models were in 3D (Jmol is Java-based). Asymptote provided me a perfect solution to have those 3D models interactive and in the PDF file.

(Encapsulated) postscript

Please see a comment section to get this mal-asymptote.asy file working as it really should be!

import settings;
outformat="eps";
// settings.render=16;
// interactiveView=false;
// batchView=false;

// User's preference...
// pick up a function: none, 1st, 2nd or 3rd
// write(whichcurve==3); // inform me in the terminal
real whichcurve=1; // 0, 1, 2 or 3

// The core of the program...
import graph3;
import solids;
size(300);
currentlight=Viewport; // no light;
pen colora=green;
pen colorb=blue;

//real f(real x) {return 0;} // :-) // line 20
real mallower;
real malupper;
string maldesc;

//if (whichcurve == 1) { // line 25
currentprojection=perspective(2,2,4,up=Y);
write("Creating first function...");
real f(real x) {return sqrt(3+x);} // line 28
maldesc="$\sqrt{3+x}$";
mallower=-1;
malupper=3;
//  } // == 1 // line 32

if (whichcurve == 2) {
currentprojection=perspective(0,2,2,up=Y);
write("Creating second function...");
real f(real x) {return sqrt((x-2)/(2x+1));} // line 37
maldesc="$\sqrt{\frac{x-2}{2x+1}}$";
mallower=2;
malupper=3;
} // == 2

if (whichcurve == 3) {
currentprojection=perspective(0,-0.5,2,up=Y);
write("Creating third function...");
real f(real x) {return sqrt((2-x)/(3+2x));} // line 46
maldesc="$\sqrt{\frac{2-x}{3+2x}}$";
mallower=1;
malupper=2;
} // == 3

pair F(real x) {return (x,f(x));}
triple F3(real x) {return (x,f(x),0);}

path p=graph(F,mallower,malupper,n=10,operator ..);
path3 p3=path3(p);
//triple pO=(0,0,0);

render render=render(merge=true);
revolution a=revolution(p3,X,140,360);
draw(surface(a),colora,render);
revolution b=revolution(p3,Y,0,220);
draw(surface(b),colorb,render);

real xmax=malupper+0.4;
real ymax=max(abs(f(mallower)),abs(f(malupper)))+0.2;
draw(Label("$x$",xmax,E),(0,0,0)--(xmax,0,0),Arrow3);
draw((0,0,0)--(-xmax,0,0),dashed);
draw(Label("$y(x)=$"+maldesc,ymax,N),(0,0,0)--(0,ymax,0),Arrow3);
draw((0,0,0)--(0,-ymax,0),dashed);
//draw(Label(maldesc,ymax),(0,ymax,0)--(xmax,ymax,0));
//draw((0,0,0)--(0,0,f(malupper)),Arrow3);


• You need to change the line 20 to a global declaration: just real f(real);. Also, inside the body of the if, do not declare a new local function f, but instead assign a new value (a new function) to the previously declared global f: f=new real(real x) {return sqrt(3+x);};. – g.kov Mar 29 '14 at 4:58
• @g.kov Thank you for your modifications! For the purpose of the post (that story), I am leaving it as it is. Next to the line 28, we will also modify lines 37 and 46 and then a switcher on line 10 (1, 2 or 3 -- the first, the second or the third graph) is working perfectly. – Malipivo Mar 29 '14 at 6:55