Drawing the contour of a 3d torus The Next CEO of Stack OverflowHow to draw a torus3D helix...

Is there such a thing as a proper verb, like a proper noun?

How to get the last not-null value in an ordered column of a huge table?

How to avoid supervisors with prejudiced views?

Is it correct to say moon starry nights?

Won the lottery - how do I keep the money?

Is it okay to majorly distort historical facts while writing a fiction story?

What happened in Rome, when the western empire "fell"?

Easy to read palindrome checker

Is there a reasonable and studied concept of reduction between regular languages?

Using multiple Nameservers to use different services

what's the use of '% to gdp' type of variables?

Do scriptures give a method to recognize a truly self-realized person/jivanmukta?

0-rank tensor vs vector in 1D

TikZ: How to fill area with a special pattern?

(How) Could a medieval fantasy world survive a magic-induced "nuclear winter"?

Why the last AS PATH item always is `I` or `?`?

Would a grinding machine be a simple and workable propulsion system for an interplanetary spacecraft?

Is there such a thing as never melting snow?

Is there a way to save my career from absolute disaster?

Does regularization penalize models that are simpler than needed?

Can someone explain this formula for calculating Manhattan distance?

Is "three point ish" an acceptable use of ish?

Deriving the equation for variance

Where do students learn to solve polynomial equations these days?



Drawing the contour of a 3d torus



The Next CEO of Stack OverflowHow to draw a torus3D helix torus with hidden linesHow can I visualize a Torus with three paths?Asymptote: Is there an equivalent to silhouette for surfaces?Automatically draw hidden portions of paths with dashed lines in asymptoteTorus with semi-dashed line in TikZDrawing Torus with semi-dashed line on itCommutative diagrams using MetaPost or AsymptoteDrawing on an image with TikZContour partially hidden by a surface in pgfplotsA simple (indented) integration contour with TikzHow to plot a lattice of points on the surface of a torus?Drawing rectilinear curves in Tikz, aka an Etch-a-Sketch drawingHow to draw a real corkscrewTiKZ: How to define new 2D canvasDrawing an imperfect spiral in TikZTikZ: Plot contour lines












12















I would like to draw a torus, like the one on the question How to draw a torus, with a 3d package. I would like to avoid drawing the torus in 2d with inkscape (or directly with tikz) because I will need to add other elements (like, say, in 3D helix torus with hidden lines, and view them from different points of view.



However, the resulting figure is to be printed (in black and white, etc.), so I want to use only contour figure, instead of shiny, color-shaded pictures like the ones in 3D helix torus with hidden lines.



In the end, I would like to have something like that (but this one was drawn in 2d with tikz)



contour torus drawn in 2d



So I tried to use asymptote, and, well, with



import solids;
settings.render=0;
settings.prc=false;
size(12cm,0);

currentprojection=orthographic(0,20,10);

real R=3, a=0.8, d=R+2a;
revolution tore=revolution(shift(R*X)*Circle(O,a,Y,32),Z);


draw(tore.silhouette(),black);


i get something like that :



a tentative silhouette torus in asymptote that did go wrong



So is there a method to do that without the hideous bug ?



I tried to do it with asymptote, but something else (tikz, sketch, black magic, etc.) would be fine, at least if it gives a vector output. However, I'm not interested in directly drawing the result in 2d (and it has already been done in How to draw a torus).



Thanks in advance.










share|improve this question

























  • Have you seen How to draw a torus?

    – clemens
    Jun 16 '13 at 13:09






  • 2





    Well cgnieder, did you read my question ? I mentioned it two times. But thanks.

    – Georg Sievelson
    Jun 16 '13 at 13:15






  • 2





    Sorry about that! I didn't see the link (BTW “this question” is not a very meanungful link-text IMHO, “1” is even more hidden: if you hadn't said “twice” I still wouldn't have seen it...) and for whatever reason it didn't show up in the “Linked” area on the left before.

    – clemens
    Jun 16 '13 at 13:27











  • You are quite right, I modified the links with more descriptive texts, and they somehow managed to appear in the linked questions.

    – Georg Sievelson
    Jun 16 '13 at 13:37
















12















I would like to draw a torus, like the one on the question How to draw a torus, with a 3d package. I would like to avoid drawing the torus in 2d with inkscape (or directly with tikz) because I will need to add other elements (like, say, in 3D helix torus with hidden lines, and view them from different points of view.



However, the resulting figure is to be printed (in black and white, etc.), so I want to use only contour figure, instead of shiny, color-shaded pictures like the ones in 3D helix torus with hidden lines.



In the end, I would like to have something like that (but this one was drawn in 2d with tikz)



contour torus drawn in 2d



So I tried to use asymptote, and, well, with



import solids;
settings.render=0;
settings.prc=false;
size(12cm,0);

currentprojection=orthographic(0,20,10);

real R=3, a=0.8, d=R+2a;
revolution tore=revolution(shift(R*X)*Circle(O,a,Y,32),Z);


draw(tore.silhouette(),black);


i get something like that :



a tentative silhouette torus in asymptote that did go wrong



So is there a method to do that without the hideous bug ?



I tried to do it with asymptote, but something else (tikz, sketch, black magic, etc.) would be fine, at least if it gives a vector output. However, I'm not interested in directly drawing the result in 2d (and it has already been done in How to draw a torus).



Thanks in advance.










share|improve this question

























  • Have you seen How to draw a torus?

    – clemens
    Jun 16 '13 at 13:09






  • 2





    Well cgnieder, did you read my question ? I mentioned it two times. But thanks.

    – Georg Sievelson
    Jun 16 '13 at 13:15






  • 2





    Sorry about that! I didn't see the link (BTW “this question” is not a very meanungful link-text IMHO, “1” is even more hidden: if you hadn't said “twice” I still wouldn't have seen it...) and for whatever reason it didn't show up in the “Linked” area on the left before.

    – clemens
    Jun 16 '13 at 13:27











  • You are quite right, I modified the links with more descriptive texts, and they somehow managed to appear in the linked questions.

    – Georg Sievelson
    Jun 16 '13 at 13:37














12












12








12


6






I would like to draw a torus, like the one on the question How to draw a torus, with a 3d package. I would like to avoid drawing the torus in 2d with inkscape (or directly with tikz) because I will need to add other elements (like, say, in 3D helix torus with hidden lines, and view them from different points of view.



However, the resulting figure is to be printed (in black and white, etc.), so I want to use only contour figure, instead of shiny, color-shaded pictures like the ones in 3D helix torus with hidden lines.



In the end, I would like to have something like that (but this one was drawn in 2d with tikz)



contour torus drawn in 2d



So I tried to use asymptote, and, well, with



import solids;
settings.render=0;
settings.prc=false;
size(12cm,0);

currentprojection=orthographic(0,20,10);

real R=3, a=0.8, d=R+2a;
revolution tore=revolution(shift(R*X)*Circle(O,a,Y,32),Z);


draw(tore.silhouette(),black);


i get something like that :



a tentative silhouette torus in asymptote that did go wrong



So is there a method to do that without the hideous bug ?



I tried to do it with asymptote, but something else (tikz, sketch, black magic, etc.) would be fine, at least if it gives a vector output. However, I'm not interested in directly drawing the result in 2d (and it has already been done in How to draw a torus).



Thanks in advance.










share|improve this question
















I would like to draw a torus, like the one on the question How to draw a torus, with a 3d package. I would like to avoid drawing the torus in 2d with inkscape (or directly with tikz) because I will need to add other elements (like, say, in 3D helix torus with hidden lines, and view them from different points of view.



However, the resulting figure is to be printed (in black and white, etc.), so I want to use only contour figure, instead of shiny, color-shaded pictures like the ones in 3D helix torus with hidden lines.



In the end, I would like to have something like that (but this one was drawn in 2d with tikz)



contour torus drawn in 2d



So I tried to use asymptote, and, well, with



import solids;
settings.render=0;
settings.prc=false;
size(12cm,0);

currentprojection=orthographic(0,20,10);

real R=3, a=0.8, d=R+2a;
revolution tore=revolution(shift(R*X)*Circle(O,a,Y,32),Z);


draw(tore.silhouette(),black);


i get something like that :



a tentative silhouette torus in asymptote that did go wrong



So is there a method to do that without the hideous bug ?



I tried to do it with asymptote, but something else (tikz, sketch, black magic, etc.) would be fine, at least if it gives a vector output. However, I'm not interested in directly drawing the result in 2d (and it has already been done in How to draw a torus).



Thanks in advance.







tikz-pgf diagrams 3d asymptote






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Apr 13 '17 at 12:35









Community

1




1










asked Jun 16 '13 at 13:05









Georg SievelsonGeorg Sievelson

404313




404313













  • Have you seen How to draw a torus?

    – clemens
    Jun 16 '13 at 13:09






  • 2





    Well cgnieder, did you read my question ? I mentioned it two times. But thanks.

    – Georg Sievelson
    Jun 16 '13 at 13:15






  • 2





    Sorry about that! I didn't see the link (BTW “this question” is not a very meanungful link-text IMHO, “1” is even more hidden: if you hadn't said “twice” I still wouldn't have seen it...) and for whatever reason it didn't show up in the “Linked” area on the left before.

    – clemens
    Jun 16 '13 at 13:27











  • You are quite right, I modified the links with more descriptive texts, and they somehow managed to appear in the linked questions.

    – Georg Sievelson
    Jun 16 '13 at 13:37



















  • Have you seen How to draw a torus?

    – clemens
    Jun 16 '13 at 13:09






  • 2





    Well cgnieder, did you read my question ? I mentioned it two times. But thanks.

    – Georg Sievelson
    Jun 16 '13 at 13:15






  • 2





    Sorry about that! I didn't see the link (BTW “this question” is not a very meanungful link-text IMHO, “1” is even more hidden: if you hadn't said “twice” I still wouldn't have seen it...) and for whatever reason it didn't show up in the “Linked” area on the left before.

    – clemens
    Jun 16 '13 at 13:27











  • You are quite right, I modified the links with more descriptive texts, and they somehow managed to appear in the linked questions.

    – Georg Sievelson
    Jun 16 '13 at 13:37

















Have you seen How to draw a torus?

– clemens
Jun 16 '13 at 13:09





Have you seen How to draw a torus?

– clemens
Jun 16 '13 at 13:09




2




2





Well cgnieder, did you read my question ? I mentioned it two times. But thanks.

– Georg Sievelson
Jun 16 '13 at 13:15





Well cgnieder, did you read my question ? I mentioned it two times. But thanks.

– Georg Sievelson
Jun 16 '13 at 13:15




2




2





Sorry about that! I didn't see the link (BTW “this question” is not a very meanungful link-text IMHO, “1” is even more hidden: if you hadn't said “twice” I still wouldn't have seen it...) and for whatever reason it didn't show up in the “Linked” area on the left before.

– clemens
Jun 16 '13 at 13:27





Sorry about that! I didn't see the link (BTW “this question” is not a very meanungful link-text IMHO, “1” is even more hidden: if you hadn't said “twice” I still wouldn't have seen it...) and for whatever reason it didn't show up in the “Linked” area on the left before.

– clemens
Jun 16 '13 at 13:27













You are quite right, I modified the links with more descriptive texts, and they somehow managed to appear in the linked questions.

– Georg Sievelson
Jun 16 '13 at 13:37





You are quite right, I modified the links with more descriptive texts, and they somehow managed to appear in the linked questions.

– Georg Sievelson
Jun 16 '13 at 13:37










3 Answers
3






active

oldest

votes


















11














Update:



This new solution is a bit longer, but overcomes all three difficulties I list for the previous solution (below). In particular, this is a vector graphic.





Since you expressed an interest in imitating a 3D helix with hidden lines, I also produced an example of that (in grayscale vector graphics with no shading):





To compile either of these, first save the following code in a file called surfacepaths.asy:



import graph3;
import contour;

// A bunch of auxiliary functions.

real fuzz = .001;

real umin(surface s) { return 0; }
real vmin(surface s) { return 0; }
pair uvmin(surface s) { return (umin(s), vmin(s)); }
real umax(surface s, real fuzz=fuzz) {
if (s.ucyclic()) return s.index.length;
else return s.index.length - fuzz;
}
real vmax(surface s, real fuzz=fuzz) {
if (s.vcyclic()) return s.index[0].length;
return s.index[0].length - fuzz;
}
pair uvmax(surface s, real fuzz=fuzz) { return (umax(s,fuzz), vmax(s,fuzz)); }

typedef real function(real, real);

function normalDot(surface s, triple eyedir(triple)) {
real toreturn(real u, real v) {
return dot(s.normal(u, v), eyedir(s.point(u,v)));
}
return toreturn;
}

struct patchWithCoords {
patch p;
real u;
real v;
void operator init(patch p, real u, real v) {
this.p = p;
this.u = u;
this.v = v;
}
void operator init(surface s, real u, real v) {
int U=floor(u);
int V=floor(v);
int index = (s.index.length == 0 ? U+V : s.index[U][V]);

this.p = s.s[index];
this.u = u-U;
this.v = v-V;
}
triple partialu() {
return p.partialu(u,v);
}
triple partialv() {
return p.partialv(u,v);
}
}

triple[] derivative(surface s, pair pt) {
patchWithCoords thepatch = patchWithCoords(s, pt.x, pt.y);
return new triple[] {thepatch.partialu(), thepatch.partialv()};
}

typedef triple paramsurface(pair);

paramsurface tangentplane(surface s, pair pt) {
patchWithCoords thepatch = patchWithCoords(s, pt.x, pt.y);
triple partialu = thepatch.partialu();
triple partialv = thepatch.partialv();
return new triple(pair tangentvector) {
return s.point(pt.x, pt.y) + (tangentvector.x * partialu) + (tangentvector.y * partialv);
};
}

guide[] normalpathuv(surface s, projection P = currentprojection, int n = ngraph) {
triple eyedir(triple a);
if (P.infinity) eyedir = new triple(triple) { return P.camera; };
else eyedir = new triple(triple pt) { return P.camera - pt; };
return contour(normalDot(s, eyedir), uvmin(s), uvmax(s), new real[] {0}, nx=n)[0];
}

path3 onSurface(surface s, path p) {
triple f(int t) {
pair point = point(p,t);
return s.point(point.x, point.y);
}

guide3 toreturn = f(0);
paramsurface thetangentplane = tangentplane(s, point(p,0));
triple oldcontrol, newcontrol;
int size = length(p);
for (int i = 1; i <= size; ++i) {
oldcontrol = thetangentplane(postcontrol(p,i-1) - point(p,i-1));
thetangentplane = tangentplane(s, point(p,i));
newcontrol = thetangentplane(precontrol(p, i) - point(p,i));
toreturn = toreturn .. controls oldcontrol and newcontrol .. f(i);
}

if (cyclic(p)) toreturn = toreturn & cycle;

return toreturn;

}

/*
* This method returns an array of paths that trace out all the
* points on s at which s is parallel to eyedir.
*/

path[] paramSilhouetteNoEdges(surface s, projection P = currentprojection, int n = ngraph) {
guide[] uvpaths = normalpathuv(s, P, n);
//Reduce the number of segments to conserve memory
for (int i = 0; i < uvpaths.length; ++i) {
real len = length(uvpaths[i]);
uvpaths[i] = graph(new pair(real t) {return point(uvpaths[i],t);}, 0, len, n=n);
}
return uvpaths;
}

private typedef real function2(real, real);
private typedef real function3(triple);

triple[] normalVectors(triple dir, triple surfacen) {
dir = unit(dir);
surfacen = unit(surfacen);
triple v1, v2;
int i = 0;
do {
v1 = unit(cross(dir, (unitrand(), unitrand(), unitrand())));
v2 = unit(cross(dir, (unitrand(), unitrand(), unitrand())));
++i;
} while ((abs(dot(v1,v2)) > Cos(10) || abs(dot(v1,surfacen)) > Cos(5) || abs(dot(v2,surfacen)) > Cos(5)) && i < 1000);
if (i >= 1000) {
write("problem: Unable to comply.");
write(" dir = " + (string)dir);
write(" surface normal = " + (string)surfacen);
}
return new triple[] {v1, v2};
}

function3 planeEqn(triple pt, triple normal) {
return new real(triple r) {
return dot(normal, r - pt);
};
}

function2 pullback(function3 eqn, surface s) {
return new real(real u, real v) {
return eqn(s.point(u,v));
};
}

/*
* returns the distinct points in which the surface intersects
* the line through the point pt in the direction dir
*/

triple[] intersectionPoints(surface s, pair parampt, triple dir) {
triple pt = s.point(parampt.x, parampt.y);
triple[] lineNormals = normalVectors(dir, s.normal(parampt.x, parampt.y));
path[][] curves;
for (triple n : lineNormals) {
function3 planeEn = planeEqn(pt, n);
function2 pullback = pullback(planeEn, s);
guide[] contour = contour(pullback, uvmin(s), uvmax(s), new real[]{0})[0];

curves.push(contour);
}
pair[] intersectionPoints;
for (path c1 : curves[0])
for (path c2 : curves[1])
intersectionPoints.append(intersectionpoints(c1, c2));
triple[] toreturn;
for (pair P : intersectionPoints)
toreturn.push(s.point(P.x, P.y));
return toreturn;
}



/*
* Returns those intersection points for which the vector from pt forms an
* acute angle with dir.
*/
int numPointsInDirection(surface s, pair parampt, triple dir, real fuzz=.05) {
triple pt = s.point(parampt.x, parampt.y);
dir = unit(dir);
triple[] intersections = intersectionPoints(s, parampt, dir);
int num = 0;
for (triple isection: intersections)
if (dot(isection - pt, dir) > fuzz) ++num;
return num;
}

bool3 increasing(real t0, real t1) {
if (t0 < t1) return true;
if (t0 > t1) return false;
return default;
}

int[] extremes(real[] f, bool cyclic = f.cyclic) {
bool3 lastIncreasing;
bool3 nextIncreasing;
int max;
if (cyclic) {
lastIncreasing = increasing(f[-1], f[0]);
max = f.length - 1;
} else {
max = f.length - 2;
if (increasing(f[0], f[1])) lastIncreasing = false;
else lastIncreasing = true;
}
int[] toreturn;
for (int i = 0; i <= max; ++i) {
nextIncreasing = increasing(f[i], f[i+1]);
if (lastIncreasing != nextIncreasing) {
toreturn.push(i);
}
lastIncreasing = nextIncreasing;
}
if (!cyclic) toreturn.push(f.length - 1);
toreturn.cyclic = cyclic;
return toreturn;
}

int[] extremes(path path, real f(pair) = new real(pair P) {return P.x;})
{
real[] fvalues = new real[size(path)];
for (int i = 0; i < fvalues.length; ++i) {
fvalues[i] = f(point(path, i));
}
fvalues.cyclic = cyclic(path);
int[] toreturn = extremes(fvalues);
fvalues.delete();
return toreturn;
}

path[] splitAtExtremes(path path, real f(pair) = new real(pair P) {return P.x;})
{
int[] splittingTimes = extremes(path, f);
path[] toreturn;
if (cyclic(path)) toreturn.push(subpath(path, splittingTimes[-1], splittingTimes[0]));
for (int i = 0; i+1 < splittingTimes.length; ++i) {
toreturn.push(subpath(path, splittingTimes[i], splittingTimes[i+1]));
}
return toreturn;
}

path[] splitAtExtremes(path[] paths, real f(pair P) = new real(pair P) {return P.x;})
{
path[] toreturn;
for (path path : paths) {
toreturn.append(splitAtExtremes(path, f));
}
return toreturn;
}

path3 toCamera(triple p, projection P=currentprojection, real fuzz = .01, real upperLimit = 100) {
if (!P.infinity) {
triple directionToCamera = unit(P.camera - p);
triple startingPoint = p + fuzz*directionToCamera;
return startingPoint -- P.camera;
}
else {
triple directionToCamera = unit(P.camera);
triple startingPoint = p + fuzz*directionToCamera;

return startingPoint -- (p + upperLimit*directionToCamera);
}
}

int numSheetsHiding(surface s, pair parampt, projection P = currentprojection) {
triple p = s.point(parampt.x, parampt.y);
path3 tocamera = toCamera(p, P);
triple pt = beginpoint(tocamera);
triple dir = endpoint(tocamera) - pt;
return numPointsInDirection(s, parampt, dir);
}

struct coloredPath {
path path;
pen pen;
void operator init(path path, pen p=currentpen) {
this.path = path;
this.pen = p;
}
/* draws the path with the pen having the specified weight (using colors)*/
void draw(real weight) {
draw(path, p=weight*pen + (1-weight)*white);
}
}
coloredPath[][] layeredPaths;
// onTop indicates whether the path should be added at the top or bottom of the specified layer
void addPath(path path, pen p=currentpen, int layer, bool onTop=true) {
coloredPath toAdd = coloredPath(path, p);
if (layer >= layeredPaths.length) {
layeredPaths[layer] = new coloredPath[] {toAdd};
} else if (onTop) {
layeredPaths[layer].push(toAdd);
} else layeredPaths[layer].insert(0, toAdd);
}

void drawLayeredPaths() {
for (int layer = layeredPaths.length - 1; layer >= 0; --layer) {
real layerfactor = (1/3)^layer;
for (coloredPath toDraw : layeredPaths[layer]) {
toDraw.draw(layerfactor);
}
}
layeredPaths.delete();
}

real[] cutTimes(path tocut, path[] knives) {
real[] intersectionTimes = new real[] {0, length(tocut)};
for (path knife : knives) {
real[][] complexIntersections = intersections(tocut, knife);
for (real[] times : complexIntersections) {
intersectionTimes.push(times[0]);
}
}
return sort(intersectionTimes);
}

path[] cut(path tocut, path[] knives) {
real[] cutTimes = cutTimes(tocut, knives);
path[] toreturn;
for (int i = 0; i + 1 < cutTimes.length; ++i) {
toreturn.push(subpath(tocut,cutTimes[i], cutTimes[i+1]));
}
return toreturn;
}

real[] condense(real[] values, real fuzz=.001) {
values = sort(values);
values.push(infinity);
real previous = -infinity;
real lastMin;
real[] toReturn;
for (real t : values) {
if (t - fuzz > previous) {
if (previous > -infinity) toReturn.push((lastMin + previous) / 2);
lastMin = t;
}
previous = t;
}
return toReturn;
}

/*
* A smooth surface parametrized by the domain [0,1] x [0,1]
*/
struct SmoothSurface {
surface s;
private real sumax;
private real svmax;
path[] paramSilhouette;
path[] projectedSilhouette;
projection theProjection;

triple camera;

path3 onSurface(path paramPath) {
return onSurface(s, scale(sumax,svmax)*paramPath);
}

triple point(real u, real v) { return s.point(sumax*u, svmax*v); }
triple point(pair uv) { return point(uv.x, uv.y); }
triple normal(real u, real v) { return s.normal(sumax*u, svmax*v); }
triple normal(pair uv) { return normal(uv.x, uv.y); }

triple[] derivative(real u, real v) { return derivative(s, (u/sumax, v/svmax)); }
triple[] derivative(pair uv) { return derivative(uv.x, uv.y); }

pen color(real u, real v, material m, light light) {
return color(normal=normal(u,v), m=m, light=light);
}
pen color(pair uv, material m, light light) {
return this.color(uv.x, uv.y, m, light);
}
pair project(triple pt) { return project(pt, theProjection); }

void operator init(surface s, projection P=currentprojection) {
this.s = s;
this.sumax = umax(s);
this.svmax = vmax(s);
this.theProjection = P;
this.paramSilhouette = scale(1/sumax, 1/svmax) * paramSilhouetteNoEdges(s,P);
this.projectedSilhouette = sequence(new path(int i) {
path3 truePath = onSurface(paramSilhouette[i]);
path projectedPath = project(truePath, theProjection, ninterpolate=1);
return projectedPath;
}, paramSilhouette.length);
camera=theProjection.camera;
if(theProjection.infinity) {
triple m=min(s);
triple M=max(s);
camera = theProjection.target
+ camerafactor*(abs(M-m)+abs(m-theProjection.target))*unit(theProjection.vector());
}

}

int numSheetsHiding(pair parampt) {
return numSheetsHiding(s, scale(sumax,svmax)*parampt);
}
int numSheetsHiding(real u, real v) {
return numSheetsHiding((u,v));
}

void drawSilhouette(pen p=currentpen,
bool includePathsBehind=false,
bool onTop = true)
{
int[][] extremes;
for (path path : projectedSilhouette) {
extremes.push(extremes(path));
}

path[] splitSilhouette;
path[] paramSplitSilhouette;

/*
* First, split at extremes to ensure that there are no
* self-intersections of any one subpath in the projected silhouette.
*/

for (int j = 0; j < paramSilhouette.length; ++j) {
path current = projectedSilhouette[j];

path currentParam = paramSilhouette[j];

int[] dividers = extremes[j];
for (int i = 0; i + 1 < dividers.length; ++i) {
int start = dividers[i];
int end = dividers[i+1];
splitSilhouette.push(subpath(current,start,end));
paramSplitSilhouette.push(subpath(currentParam, start, end));
}
}

/*
* Now, split at intersections of distinct subpaths.
*/

for (int j = 0; j < splitSilhouette.length; ++j) {
path current = splitSilhouette[j];
path currentParam = paramSplitSilhouette[j];

real[] splittingTimes = new real[] {0,length(current)};
for (int k = 0; k < splitSilhouette.length; ++k) {
if (j == k) continue;
real[][] times = intersections(current, splitSilhouette[k]);
for (real[] time : times) {
real relevantTime = time[0];
if (.01 < relevantTime && relevantTime < length(current) - .01) splittingTimes.push(relevantTime);
}
}
splittingTimes = sort(splittingTimes);
for (int i = 0; i + 1 < splittingTimes.length; ++i) {
real start = splittingTimes[i];
real end = splittingTimes[i+1];
real mid = start + ((end-start) / (2+0.1*unitrand()));
pair theparampoint = point(currentParam, mid);
int sheets = numSheetsHiding(theparampoint);

if (sheets == 0 || includePathsBehind) {
path currentSubpath = subpath(current, start, end);
addPath(currentSubpath, p=p, onTop=onTop, layer=sheets);
}

}
}
}

/*
Splits a parametrized path along the parametrized silhouette,
taking [0,1] x [0,1] as the
fundamental domain. Could be implemented more efficiently.
*/
private real[] splitTimes(path thepath) {
pair min = min(thepath);
pair max = max(thepath);
path[] baseknives = paramSilhouette;
path[] knives;
for (int u = floor(min.x); u < max.x + .001; ++u) {
for (int v = floor(min.y); v < max.y + .001; ++v) {
knives.append(shift(u,v)*baseknives);
}
}
return cutTimes(thepath, knives);
}

/*
Returns the times at which the projection of the given path3 intersects
the projection of the surface silhouette. This may miss unstable
intersections that can be detected by the previous method.
*/
private real[] silhouetteCrossingTimes(path3 thepath, real fuzz = .01) {
path projectedpath = project(thepath, theProjection, ninterpolate=1);
real[] crossingTimes = cutTimes(projectedpath, projectedSilhouette);
if (crossingTimes.length == 0) return crossingTimes;
real current = 0;
real[] toReturn = new real[] {0};
for (real prospective : crossingTimes) {
if (prospective > current + fuzz
&& prospective < length(thepath) - fuzz) {
toReturn.push(prospective);
current = prospective;
}
}
toReturn.push(length(thepath));
return toReturn;
}

path3 drawSurfacePath(path parampath, pen p=currentpen, bool onTop=true) {
path[] toDraw;
real[] crossingTimes = splitTimes(parampath);
crossingTimes.append(silhouetteCrossingTimes(onSurface(parampath)));
crossingTimes = condense(crossingTimes);
for (int i = 0; i+1 < crossingTimes.length; ++i) {
toDraw.push(subpath(parampath, crossingTimes[i], crossingTimes[i+1]));
}
for (path thepath : toDraw) {
pair midpoint = point(thepath, length(thepath) / (2+.1*unitrand()));
int sheets = numSheetsHiding(midpoint);
path path3d = project(onSurface(thepath), theProjection, ninterpolate = 1);
addPath(path3d, p=p, onTop=onTop, layer=sheets);
}
return onSurface(parampath);
}

}

SmoothSurface operator *(transform3 t, SmoothSurface s) {
return SmoothSurface(t*s.s);
}


To get the first image (with the two circles), in the same directory, run the following *.asy code:



settings.outformat="pdf";
import surfacepaths;

unitsize(1.5cm);
triple eye = 4*(0,1,3.5);
currentprojection=perspective(eye, up=Y);

surface torus = surface(O, shift(2X)*path3(scale(0.4)*unitcircle), axis=Y);
SmoothSurface Torus = SmoothSurface(torus);

Torus.drawSilhouette();
real diff = .07;
real midpoint = .75;
path circle1 = project(Torus.drawSurfacePath((midpoint-diff, 0) -- (midpoint-diff, 1)), ninterpolate=1);
path circle2 = project(Torus.drawSurfacePath((midpoint+diff,0) -- (midpoint+diff,1)),ninterpolate=1);

drawLayeredPaths(); // Draw the paths (with hidden path removal) that were computed in the previous code block.

real circle1mintime = mintimes(circle1)[1]; // the time at which circle1 reaches its minimum vertical extent
pair p1 = point(circle1, circle1mintime);
label("$mathcal C$", position=p1, align=SW);

real circle2mintime = mintimes(circle2)[1];
pair p2 = point(circle2, circle2mintime);
label("$mathcal C'$", position=p2, align=SE);

diff += .01;
path3 arc = Torus.onSurface((midpoint-diff,.75) -- (midpoint+diff,.75));
draw(shift(0,-1)*project(arc), arrow=ArcArrow());


To get the torus wrapped in a helix, run the following Asymptote code:



settings.outformat="pdf";
import surfacepaths;

size(400,0);
currentprojection=perspective(camera=(0,4,14), up=Y);

//function to return a line, but split into n parts (for better approximation when the line is transformed to lie on the surface)
path splitline(pair a, pair b, int n) {
pair f(real t) { return (1-t)*a + t*b; }
return graph(f, 0, 1, n=n);
}

surface torus = surface(O, Circle(c=2X,r=0.4,normal=Z,n=32), axis=Y, n=32);
/* Let Asymptote know (in case it did not already) that the u and v
* coordinates of this surface are both cyclic.
*/
torus.ucyclic(true);
torus.vcyclic(true);
SmoothSurface Torus = SmoothSurface(torus);

Torus.drawSilhouette(includePathsBehind=true);
Torus.drawSurfacePath(splitline((0,0), (1,20), n=100), p=gray);

drawLayeredPaths();




Old (rasterized-only) solution:



Here's an Asymptote solution that sort of works.
enter image description here



Note that as currently formulated, it has the following difficulties:




  1. The torus must be opaque.

  2. Anything that should be partially obscured by the torus has to be given in rasterized output (although I've got a scaling factor included to give high resolution). The rest of the image (i.e., the labels and the arrow) is vector output.

  3. It only works with an orthographic projection.


Anyway, here's the code:



import graph3;
import contour;

// A bunch of auxiliary functions for drawing silhouettes.

real fuzz = .001;

real umin(surface s) { return 0; }
real vmin(surface s) { return 0; }
pair uvmin(surface s) { return (umin(s), vmin(s)); }
real umax(surface s, real fuzz=fuzz) {
if (s.ucyclic()) return s.index.length;
else return s.index.length - fuzz;
}
real vmax(surface s, real fuzz=fuzz) {
if (s.vcyclic()) return s.index[0].length;
return s.index[0].length - fuzz;
}
pair uvmax(surface s, real fuzz=fuzz) { return (umax(s,fuzz), vmax(s,fuzz)); }

typedef real function(real, real);

function normalDot(surface s, triple eyedir) {
real toreturn(real u, real v) {
return dot(s.normal(u, v), eyedir);
}
return toreturn;
}

guide[] normalpathuv(surface s, triple eyedir) {
return contour(normalDot(s, eyedir), uvmin(s), uvmax(s), new real[] {0})[0];
}

path3 onSurface(surface s, path p) {
triple f(real t) {
pair point = point(p,t);
return s.point(point.x, point.y);
}
if (cyclic(p)) {
guide3 toreturn = f(0);
for (int i = 1; i < size(p); ++i)
toreturn = toreturn -- f(i);
toreturn = toreturn -- cycle;
return toreturn;
}
return graph(f, 0, length(p));
}

/*
* This method returns an array of paths that trace out all the
* points on s at which s is normal to eyedir.
*/
path3[] silhouetteNormal(surface s, triple eyedir) {
guide[] uvpaths = normalpathuv(s, eyedir);
path3[] toreturn = new path3[uvpaths.length];
for (int i = 0; i < uvpaths.length; ++i) {
toreturn[i] = onSurface(s, uvpaths[i]);
}
return toreturn;
}



/*
* Now, let's actually draw the picture.
*/

settings.outformat="pdf";
int resolutionfactor = 4;
settings.render=2*resolutionfactor;
settings.prc=false;

unitsize(1.5cm);
triple eye = (0,1,3);
currentprojection=orthographic(eye, up=Y);

surface torus = surface(O, shift(2X)*path3(scale(0.5)*unitcircle), axis=Y);

draw(silhouetteNormal(torus, eye));
draw(torus, surfacepen=emissive(white));

real umin = umin(torus);
real umax = umax(torus);
real diff = 0.07;
path3 circle1 = torus.uequals((.75-diff)*umax);
draw(circle1);
pair p1 = project(point(circle1, 3));
label("$mathcal C$", position=p1, align=2SW);

path3 circle2 = torus.uequals((.75+diff)*umax);
draw(circle2);
pair p2 = project(point(circle2,3));
label("$mathcal C'$", position=p2, align=2SE);

pair midpoint = (p1 + p2)/2 + (0,-.1);
path arc = p1 .. midpoint .. p2;

draw(shift(0,-1)*arc, arrow=ArcArrow());

shipout(scale(resolutionfactor)*currentpicture.fit());





share|improve this answer

































    6














    enter image description here



    Not sure, if this trick can help:



    import solids;
    settings.render=0;
    settings.prc=true;
    size(4cm,0);

    currentprojection=orthographic(0,20,10);
    currentlight=nolight;

    real R=3, a=0.8, d=R+2a;

    void drawTorus(transform3 t=zscale3(1),int n=4){
    revolution torus=t*revolution(shift(R*X)*Circle(O,a,Y,32),Z,0,360);
    draw(torus.surface(n),black+opacity(0.8));
    revolution torus=t*revolution(shift(R*X)*Circle(O,a*0.9,Y,32),Z,0,360);
    draw(torus.surface(n),white);
    }

    drawTorus();
    drawTorus(shift(0,0,-5)*rotate(-20,(0,0,0),(0,1,0)));
    drawTorus(shift(0,0,-10)*rotate(45,(0,0,0),(0,1,0)));





    share|improve this answer
























    • It's much better than the original, thanks ! However, the silhouette lacks the small portions of the curve in front that continue a little further in the sides, like in i.stack.imgur.com/aFVv7.png and help the reader to understand the 3d ... I believe it is possible to get them, as blender+freestyle manage to do it, but I still do not see how with a latex tool. Another problem of your trick is that it probably won't go nicely with e.g. curves on the torus ... but thanks anyway.

      – Georg Sievelson
      Jun 17 '13 at 17:01



















    0














    In this answer I use pic{torus} that I have defined in this other answer and complete it with pic{circle on torus} code. The code is not very solid, in a sens that the "invisible" part of the drawn circle do not work very well for all possible angles.



    documentclass[tikz,border=7pt]{standalone}
    usetikzlibrary{calc,3d}
    tikzset{
    % ------ define the torus pic
    pics/torus/.style n args={3}{
    code = {
    begin{scope}
    edefshorten{thedimexpr-.5pgflinewidthrelax}
    providecolor{pgffillcolor}{rgb}{1,1,1}
    tikzstyle{torus} = [
    line join=round,draw,line width/.expanded={thedimexpr2pgflinewidth+#2*2relax},
    postaction={draw=pgffillcolor,line width={#2*2},shorten <=shorten,shorten >=shorten},
    yscale=cos(#3)
    ]
    draw[torus] circle(#1);
    draw[torus] (180:#1) arc (180:360:#1);
    end{scope}
    }
    },
    % ------ define the circle pic
    pics/circle on torus/.style n args={4}{
    code = {
    begin{scope}[
    plane x={({cos(#4)},{-cos(#3)*sin(#4)})},
    plane y={(0,{sin(#3)})},
    canvas is plane]
    draw[xshift=#1, rotate=sign(-#4)*#3] (0:#2) arc(0:180:#2);
    draw[xshift=#1, rotate=sign(-#4)*#3-180, densely dotted,draw opacity=.5] (0:#2) arc(0:180:#2);
    end{scope}
    }
    }
    }
    begin{document}
    defR{1cm} % <-- the big radius
    defr{2.8mm} % <-- the small radius
    defa{70} % <-- the view perspective angle
    begin{tikzpicture}[rotate=30,transform shape]
    % draw the torus
    pic[fill=yellow!20]{torus={R}{r}{a}};
    % draw the circles
    pic[red]{circle on torus={R}{r}{a}{45}};
    pic[blue]{circle on torus={R}{r}{a}{110}};
    pic[violet]{circle on torus={R}{r}{a}{-70}};
    end{tikzpicture}
    end{document}


    enter image description here






    share|improve this answer
























      Your Answer








      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "85"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: false,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f119386%2fdrawing-the-contour-of-a-3d-torus%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      3 Answers
      3






      active

      oldest

      votes








      3 Answers
      3






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      11














      Update:



      This new solution is a bit longer, but overcomes all three difficulties I list for the previous solution (below). In particular, this is a vector graphic.





      Since you expressed an interest in imitating a 3D helix with hidden lines, I also produced an example of that (in grayscale vector graphics with no shading):





      To compile either of these, first save the following code in a file called surfacepaths.asy:



      import graph3;
      import contour;

      // A bunch of auxiliary functions.

      real fuzz = .001;

      real umin(surface s) { return 0; }
      real vmin(surface s) { return 0; }
      pair uvmin(surface s) { return (umin(s), vmin(s)); }
      real umax(surface s, real fuzz=fuzz) {
      if (s.ucyclic()) return s.index.length;
      else return s.index.length - fuzz;
      }
      real vmax(surface s, real fuzz=fuzz) {
      if (s.vcyclic()) return s.index[0].length;
      return s.index[0].length - fuzz;
      }
      pair uvmax(surface s, real fuzz=fuzz) { return (umax(s,fuzz), vmax(s,fuzz)); }

      typedef real function(real, real);

      function normalDot(surface s, triple eyedir(triple)) {
      real toreturn(real u, real v) {
      return dot(s.normal(u, v), eyedir(s.point(u,v)));
      }
      return toreturn;
      }

      struct patchWithCoords {
      patch p;
      real u;
      real v;
      void operator init(patch p, real u, real v) {
      this.p = p;
      this.u = u;
      this.v = v;
      }
      void operator init(surface s, real u, real v) {
      int U=floor(u);
      int V=floor(v);
      int index = (s.index.length == 0 ? U+V : s.index[U][V]);

      this.p = s.s[index];
      this.u = u-U;
      this.v = v-V;
      }
      triple partialu() {
      return p.partialu(u,v);
      }
      triple partialv() {
      return p.partialv(u,v);
      }
      }

      triple[] derivative(surface s, pair pt) {
      patchWithCoords thepatch = patchWithCoords(s, pt.x, pt.y);
      return new triple[] {thepatch.partialu(), thepatch.partialv()};
      }

      typedef triple paramsurface(pair);

      paramsurface tangentplane(surface s, pair pt) {
      patchWithCoords thepatch = patchWithCoords(s, pt.x, pt.y);
      triple partialu = thepatch.partialu();
      triple partialv = thepatch.partialv();
      return new triple(pair tangentvector) {
      return s.point(pt.x, pt.y) + (tangentvector.x * partialu) + (tangentvector.y * partialv);
      };
      }

      guide[] normalpathuv(surface s, projection P = currentprojection, int n = ngraph) {
      triple eyedir(triple a);
      if (P.infinity) eyedir = new triple(triple) { return P.camera; };
      else eyedir = new triple(triple pt) { return P.camera - pt; };
      return contour(normalDot(s, eyedir), uvmin(s), uvmax(s), new real[] {0}, nx=n)[0];
      }

      path3 onSurface(surface s, path p) {
      triple f(int t) {
      pair point = point(p,t);
      return s.point(point.x, point.y);
      }

      guide3 toreturn = f(0);
      paramsurface thetangentplane = tangentplane(s, point(p,0));
      triple oldcontrol, newcontrol;
      int size = length(p);
      for (int i = 1; i <= size; ++i) {
      oldcontrol = thetangentplane(postcontrol(p,i-1) - point(p,i-1));
      thetangentplane = tangentplane(s, point(p,i));
      newcontrol = thetangentplane(precontrol(p, i) - point(p,i));
      toreturn = toreturn .. controls oldcontrol and newcontrol .. f(i);
      }

      if (cyclic(p)) toreturn = toreturn & cycle;

      return toreturn;

      }

      /*
      * This method returns an array of paths that trace out all the
      * points on s at which s is parallel to eyedir.
      */

      path[] paramSilhouetteNoEdges(surface s, projection P = currentprojection, int n = ngraph) {
      guide[] uvpaths = normalpathuv(s, P, n);
      //Reduce the number of segments to conserve memory
      for (int i = 0; i < uvpaths.length; ++i) {
      real len = length(uvpaths[i]);
      uvpaths[i] = graph(new pair(real t) {return point(uvpaths[i],t);}, 0, len, n=n);
      }
      return uvpaths;
      }

      private typedef real function2(real, real);
      private typedef real function3(triple);

      triple[] normalVectors(triple dir, triple surfacen) {
      dir = unit(dir);
      surfacen = unit(surfacen);
      triple v1, v2;
      int i = 0;
      do {
      v1 = unit(cross(dir, (unitrand(), unitrand(), unitrand())));
      v2 = unit(cross(dir, (unitrand(), unitrand(), unitrand())));
      ++i;
      } while ((abs(dot(v1,v2)) > Cos(10) || abs(dot(v1,surfacen)) > Cos(5) || abs(dot(v2,surfacen)) > Cos(5)) && i < 1000);
      if (i >= 1000) {
      write("problem: Unable to comply.");
      write(" dir = " + (string)dir);
      write(" surface normal = " + (string)surfacen);
      }
      return new triple[] {v1, v2};
      }

      function3 planeEqn(triple pt, triple normal) {
      return new real(triple r) {
      return dot(normal, r - pt);
      };
      }

      function2 pullback(function3 eqn, surface s) {
      return new real(real u, real v) {
      return eqn(s.point(u,v));
      };
      }

      /*
      * returns the distinct points in which the surface intersects
      * the line through the point pt in the direction dir
      */

      triple[] intersectionPoints(surface s, pair parampt, triple dir) {
      triple pt = s.point(parampt.x, parampt.y);
      triple[] lineNormals = normalVectors(dir, s.normal(parampt.x, parampt.y));
      path[][] curves;
      for (triple n : lineNormals) {
      function3 planeEn = planeEqn(pt, n);
      function2 pullback = pullback(planeEn, s);
      guide[] contour = contour(pullback, uvmin(s), uvmax(s), new real[]{0})[0];

      curves.push(contour);
      }
      pair[] intersectionPoints;
      for (path c1 : curves[0])
      for (path c2 : curves[1])
      intersectionPoints.append(intersectionpoints(c1, c2));
      triple[] toreturn;
      for (pair P : intersectionPoints)
      toreturn.push(s.point(P.x, P.y));
      return toreturn;
      }



      /*
      * Returns those intersection points for which the vector from pt forms an
      * acute angle with dir.
      */
      int numPointsInDirection(surface s, pair parampt, triple dir, real fuzz=.05) {
      triple pt = s.point(parampt.x, parampt.y);
      dir = unit(dir);
      triple[] intersections = intersectionPoints(s, parampt, dir);
      int num = 0;
      for (triple isection: intersections)
      if (dot(isection - pt, dir) > fuzz) ++num;
      return num;
      }

      bool3 increasing(real t0, real t1) {
      if (t0 < t1) return true;
      if (t0 > t1) return false;
      return default;
      }

      int[] extremes(real[] f, bool cyclic = f.cyclic) {
      bool3 lastIncreasing;
      bool3 nextIncreasing;
      int max;
      if (cyclic) {
      lastIncreasing = increasing(f[-1], f[0]);
      max = f.length - 1;
      } else {
      max = f.length - 2;
      if (increasing(f[0], f[1])) lastIncreasing = false;
      else lastIncreasing = true;
      }
      int[] toreturn;
      for (int i = 0; i <= max; ++i) {
      nextIncreasing = increasing(f[i], f[i+1]);
      if (lastIncreasing != nextIncreasing) {
      toreturn.push(i);
      }
      lastIncreasing = nextIncreasing;
      }
      if (!cyclic) toreturn.push(f.length - 1);
      toreturn.cyclic = cyclic;
      return toreturn;
      }

      int[] extremes(path path, real f(pair) = new real(pair P) {return P.x;})
      {
      real[] fvalues = new real[size(path)];
      for (int i = 0; i < fvalues.length; ++i) {
      fvalues[i] = f(point(path, i));
      }
      fvalues.cyclic = cyclic(path);
      int[] toreturn = extremes(fvalues);
      fvalues.delete();
      return toreturn;
      }

      path[] splitAtExtremes(path path, real f(pair) = new real(pair P) {return P.x;})
      {
      int[] splittingTimes = extremes(path, f);
      path[] toreturn;
      if (cyclic(path)) toreturn.push(subpath(path, splittingTimes[-1], splittingTimes[0]));
      for (int i = 0; i+1 < splittingTimes.length; ++i) {
      toreturn.push(subpath(path, splittingTimes[i], splittingTimes[i+1]));
      }
      return toreturn;
      }

      path[] splitAtExtremes(path[] paths, real f(pair P) = new real(pair P) {return P.x;})
      {
      path[] toreturn;
      for (path path : paths) {
      toreturn.append(splitAtExtremes(path, f));
      }
      return toreturn;
      }

      path3 toCamera(triple p, projection P=currentprojection, real fuzz = .01, real upperLimit = 100) {
      if (!P.infinity) {
      triple directionToCamera = unit(P.camera - p);
      triple startingPoint = p + fuzz*directionToCamera;
      return startingPoint -- P.camera;
      }
      else {
      triple directionToCamera = unit(P.camera);
      triple startingPoint = p + fuzz*directionToCamera;

      return startingPoint -- (p + upperLimit*directionToCamera);
      }
      }

      int numSheetsHiding(surface s, pair parampt, projection P = currentprojection) {
      triple p = s.point(parampt.x, parampt.y);
      path3 tocamera = toCamera(p, P);
      triple pt = beginpoint(tocamera);
      triple dir = endpoint(tocamera) - pt;
      return numPointsInDirection(s, parampt, dir);
      }

      struct coloredPath {
      path path;
      pen pen;
      void operator init(path path, pen p=currentpen) {
      this.path = path;
      this.pen = p;
      }
      /* draws the path with the pen having the specified weight (using colors)*/
      void draw(real weight) {
      draw(path, p=weight*pen + (1-weight)*white);
      }
      }
      coloredPath[][] layeredPaths;
      // onTop indicates whether the path should be added at the top or bottom of the specified layer
      void addPath(path path, pen p=currentpen, int layer, bool onTop=true) {
      coloredPath toAdd = coloredPath(path, p);
      if (layer >= layeredPaths.length) {
      layeredPaths[layer] = new coloredPath[] {toAdd};
      } else if (onTop) {
      layeredPaths[layer].push(toAdd);
      } else layeredPaths[layer].insert(0, toAdd);
      }

      void drawLayeredPaths() {
      for (int layer = layeredPaths.length - 1; layer >= 0; --layer) {
      real layerfactor = (1/3)^layer;
      for (coloredPath toDraw : layeredPaths[layer]) {
      toDraw.draw(layerfactor);
      }
      }
      layeredPaths.delete();
      }

      real[] cutTimes(path tocut, path[] knives) {
      real[] intersectionTimes = new real[] {0, length(tocut)};
      for (path knife : knives) {
      real[][] complexIntersections = intersections(tocut, knife);
      for (real[] times : complexIntersections) {
      intersectionTimes.push(times[0]);
      }
      }
      return sort(intersectionTimes);
      }

      path[] cut(path tocut, path[] knives) {
      real[] cutTimes = cutTimes(tocut, knives);
      path[] toreturn;
      for (int i = 0; i + 1 < cutTimes.length; ++i) {
      toreturn.push(subpath(tocut,cutTimes[i], cutTimes[i+1]));
      }
      return toreturn;
      }

      real[] condense(real[] values, real fuzz=.001) {
      values = sort(values);
      values.push(infinity);
      real previous = -infinity;
      real lastMin;
      real[] toReturn;
      for (real t : values) {
      if (t - fuzz > previous) {
      if (previous > -infinity) toReturn.push((lastMin + previous) / 2);
      lastMin = t;
      }
      previous = t;
      }
      return toReturn;
      }

      /*
      * A smooth surface parametrized by the domain [0,1] x [0,1]
      */
      struct SmoothSurface {
      surface s;
      private real sumax;
      private real svmax;
      path[] paramSilhouette;
      path[] projectedSilhouette;
      projection theProjection;

      triple camera;

      path3 onSurface(path paramPath) {
      return onSurface(s, scale(sumax,svmax)*paramPath);
      }

      triple point(real u, real v) { return s.point(sumax*u, svmax*v); }
      triple point(pair uv) { return point(uv.x, uv.y); }
      triple normal(real u, real v) { return s.normal(sumax*u, svmax*v); }
      triple normal(pair uv) { return normal(uv.x, uv.y); }

      triple[] derivative(real u, real v) { return derivative(s, (u/sumax, v/svmax)); }
      triple[] derivative(pair uv) { return derivative(uv.x, uv.y); }

      pen color(real u, real v, material m, light light) {
      return color(normal=normal(u,v), m=m, light=light);
      }
      pen color(pair uv, material m, light light) {
      return this.color(uv.x, uv.y, m, light);
      }
      pair project(triple pt) { return project(pt, theProjection); }

      void operator init(surface s, projection P=currentprojection) {
      this.s = s;
      this.sumax = umax(s);
      this.svmax = vmax(s);
      this.theProjection = P;
      this.paramSilhouette = scale(1/sumax, 1/svmax) * paramSilhouetteNoEdges(s,P);
      this.projectedSilhouette = sequence(new path(int i) {
      path3 truePath = onSurface(paramSilhouette[i]);
      path projectedPath = project(truePath, theProjection, ninterpolate=1);
      return projectedPath;
      }, paramSilhouette.length);
      camera=theProjection.camera;
      if(theProjection.infinity) {
      triple m=min(s);
      triple M=max(s);
      camera = theProjection.target
      + camerafactor*(abs(M-m)+abs(m-theProjection.target))*unit(theProjection.vector());
      }

      }

      int numSheetsHiding(pair parampt) {
      return numSheetsHiding(s, scale(sumax,svmax)*parampt);
      }
      int numSheetsHiding(real u, real v) {
      return numSheetsHiding((u,v));
      }

      void drawSilhouette(pen p=currentpen,
      bool includePathsBehind=false,
      bool onTop = true)
      {
      int[][] extremes;
      for (path path : projectedSilhouette) {
      extremes.push(extremes(path));
      }

      path[] splitSilhouette;
      path[] paramSplitSilhouette;

      /*
      * First, split at extremes to ensure that there are no
      * self-intersections of any one subpath in the projected silhouette.
      */

      for (int j = 0; j < paramSilhouette.length; ++j) {
      path current = projectedSilhouette[j];

      path currentParam = paramSilhouette[j];

      int[] dividers = extremes[j];
      for (int i = 0; i + 1 < dividers.length; ++i) {
      int start = dividers[i];
      int end = dividers[i+1];
      splitSilhouette.push(subpath(current,start,end));
      paramSplitSilhouette.push(subpath(currentParam, start, end));
      }
      }

      /*
      * Now, split at intersections of distinct subpaths.
      */

      for (int j = 0; j < splitSilhouette.length; ++j) {
      path current = splitSilhouette[j];
      path currentParam = paramSplitSilhouette[j];

      real[] splittingTimes = new real[] {0,length(current)};
      for (int k = 0; k < splitSilhouette.length; ++k) {
      if (j == k) continue;
      real[][] times = intersections(current, splitSilhouette[k]);
      for (real[] time : times) {
      real relevantTime = time[0];
      if (.01 < relevantTime && relevantTime < length(current) - .01) splittingTimes.push(relevantTime);
      }
      }
      splittingTimes = sort(splittingTimes);
      for (int i = 0; i + 1 < splittingTimes.length; ++i) {
      real start = splittingTimes[i];
      real end = splittingTimes[i+1];
      real mid = start + ((end-start) / (2+0.1*unitrand()));
      pair theparampoint = point(currentParam, mid);
      int sheets = numSheetsHiding(theparampoint);

      if (sheets == 0 || includePathsBehind) {
      path currentSubpath = subpath(current, start, end);
      addPath(currentSubpath, p=p, onTop=onTop, layer=sheets);
      }

      }
      }
      }

      /*
      Splits a parametrized path along the parametrized silhouette,
      taking [0,1] x [0,1] as the
      fundamental domain. Could be implemented more efficiently.
      */
      private real[] splitTimes(path thepath) {
      pair min = min(thepath);
      pair max = max(thepath);
      path[] baseknives = paramSilhouette;
      path[] knives;
      for (int u = floor(min.x); u < max.x + .001; ++u) {
      for (int v = floor(min.y); v < max.y + .001; ++v) {
      knives.append(shift(u,v)*baseknives);
      }
      }
      return cutTimes(thepath, knives);
      }

      /*
      Returns the times at which the projection of the given path3 intersects
      the projection of the surface silhouette. This may miss unstable
      intersections that can be detected by the previous method.
      */
      private real[] silhouetteCrossingTimes(path3 thepath, real fuzz = .01) {
      path projectedpath = project(thepath, theProjection, ninterpolate=1);
      real[] crossingTimes = cutTimes(projectedpath, projectedSilhouette);
      if (crossingTimes.length == 0) return crossingTimes;
      real current = 0;
      real[] toReturn = new real[] {0};
      for (real prospective : crossingTimes) {
      if (prospective > current + fuzz
      && prospective < length(thepath) - fuzz) {
      toReturn.push(prospective);
      current = prospective;
      }
      }
      toReturn.push(length(thepath));
      return toReturn;
      }

      path3 drawSurfacePath(path parampath, pen p=currentpen, bool onTop=true) {
      path[] toDraw;
      real[] crossingTimes = splitTimes(parampath);
      crossingTimes.append(silhouetteCrossingTimes(onSurface(parampath)));
      crossingTimes = condense(crossingTimes);
      for (int i = 0; i+1 < crossingTimes.length; ++i) {
      toDraw.push(subpath(parampath, crossingTimes[i], crossingTimes[i+1]));
      }
      for (path thepath : toDraw) {
      pair midpoint = point(thepath, length(thepath) / (2+.1*unitrand()));
      int sheets = numSheetsHiding(midpoint);
      path path3d = project(onSurface(thepath), theProjection, ninterpolate = 1);
      addPath(path3d, p=p, onTop=onTop, layer=sheets);
      }
      return onSurface(parampath);
      }

      }

      SmoothSurface operator *(transform3 t, SmoothSurface s) {
      return SmoothSurface(t*s.s);
      }


      To get the first image (with the two circles), in the same directory, run the following *.asy code:



      settings.outformat="pdf";
      import surfacepaths;

      unitsize(1.5cm);
      triple eye = 4*(0,1,3.5);
      currentprojection=perspective(eye, up=Y);

      surface torus = surface(O, shift(2X)*path3(scale(0.4)*unitcircle), axis=Y);
      SmoothSurface Torus = SmoothSurface(torus);

      Torus.drawSilhouette();
      real diff = .07;
      real midpoint = .75;
      path circle1 = project(Torus.drawSurfacePath((midpoint-diff, 0) -- (midpoint-diff, 1)), ninterpolate=1);
      path circle2 = project(Torus.drawSurfacePath((midpoint+diff,0) -- (midpoint+diff,1)),ninterpolate=1);

      drawLayeredPaths(); // Draw the paths (with hidden path removal) that were computed in the previous code block.

      real circle1mintime = mintimes(circle1)[1]; // the time at which circle1 reaches its minimum vertical extent
      pair p1 = point(circle1, circle1mintime);
      label("$mathcal C$", position=p1, align=SW);

      real circle2mintime = mintimes(circle2)[1];
      pair p2 = point(circle2, circle2mintime);
      label("$mathcal C'$", position=p2, align=SE);

      diff += .01;
      path3 arc = Torus.onSurface((midpoint-diff,.75) -- (midpoint+diff,.75));
      draw(shift(0,-1)*project(arc), arrow=ArcArrow());


      To get the torus wrapped in a helix, run the following Asymptote code:



      settings.outformat="pdf";
      import surfacepaths;

      size(400,0);
      currentprojection=perspective(camera=(0,4,14), up=Y);

      //function to return a line, but split into n parts (for better approximation when the line is transformed to lie on the surface)
      path splitline(pair a, pair b, int n) {
      pair f(real t) { return (1-t)*a + t*b; }
      return graph(f, 0, 1, n=n);
      }

      surface torus = surface(O, Circle(c=2X,r=0.4,normal=Z,n=32), axis=Y, n=32);
      /* Let Asymptote know (in case it did not already) that the u and v
      * coordinates of this surface are both cyclic.
      */
      torus.ucyclic(true);
      torus.vcyclic(true);
      SmoothSurface Torus = SmoothSurface(torus);

      Torus.drawSilhouette(includePathsBehind=true);
      Torus.drawSurfacePath(splitline((0,0), (1,20), n=100), p=gray);

      drawLayeredPaths();




      Old (rasterized-only) solution:



      Here's an Asymptote solution that sort of works.
      enter image description here



      Note that as currently formulated, it has the following difficulties:




      1. The torus must be opaque.

      2. Anything that should be partially obscured by the torus has to be given in rasterized output (although I've got a scaling factor included to give high resolution). The rest of the image (i.e., the labels and the arrow) is vector output.

      3. It only works with an orthographic projection.


      Anyway, here's the code:



      import graph3;
      import contour;

      // A bunch of auxiliary functions for drawing silhouettes.

      real fuzz = .001;

      real umin(surface s) { return 0; }
      real vmin(surface s) { return 0; }
      pair uvmin(surface s) { return (umin(s), vmin(s)); }
      real umax(surface s, real fuzz=fuzz) {
      if (s.ucyclic()) return s.index.length;
      else return s.index.length - fuzz;
      }
      real vmax(surface s, real fuzz=fuzz) {
      if (s.vcyclic()) return s.index[0].length;
      return s.index[0].length - fuzz;
      }
      pair uvmax(surface s, real fuzz=fuzz) { return (umax(s,fuzz), vmax(s,fuzz)); }

      typedef real function(real, real);

      function normalDot(surface s, triple eyedir) {
      real toreturn(real u, real v) {
      return dot(s.normal(u, v), eyedir);
      }
      return toreturn;
      }

      guide[] normalpathuv(surface s, triple eyedir) {
      return contour(normalDot(s, eyedir), uvmin(s), uvmax(s), new real[] {0})[0];
      }

      path3 onSurface(surface s, path p) {
      triple f(real t) {
      pair point = point(p,t);
      return s.point(point.x, point.y);
      }
      if (cyclic(p)) {
      guide3 toreturn = f(0);
      for (int i = 1; i < size(p); ++i)
      toreturn = toreturn -- f(i);
      toreturn = toreturn -- cycle;
      return toreturn;
      }
      return graph(f, 0, length(p));
      }

      /*
      * This method returns an array of paths that trace out all the
      * points on s at which s is normal to eyedir.
      */
      path3[] silhouetteNormal(surface s, triple eyedir) {
      guide[] uvpaths = normalpathuv(s, eyedir);
      path3[] toreturn = new path3[uvpaths.length];
      for (int i = 0; i < uvpaths.length; ++i) {
      toreturn[i] = onSurface(s, uvpaths[i]);
      }
      return toreturn;
      }



      /*
      * Now, let's actually draw the picture.
      */

      settings.outformat="pdf";
      int resolutionfactor = 4;
      settings.render=2*resolutionfactor;
      settings.prc=false;

      unitsize(1.5cm);
      triple eye = (0,1,3);
      currentprojection=orthographic(eye, up=Y);

      surface torus = surface(O, shift(2X)*path3(scale(0.5)*unitcircle), axis=Y);

      draw(silhouetteNormal(torus, eye));
      draw(torus, surfacepen=emissive(white));

      real umin = umin(torus);
      real umax = umax(torus);
      real diff = 0.07;
      path3 circle1 = torus.uequals((.75-diff)*umax);
      draw(circle1);
      pair p1 = project(point(circle1, 3));
      label("$mathcal C$", position=p1, align=2SW);

      path3 circle2 = torus.uequals((.75+diff)*umax);
      draw(circle2);
      pair p2 = project(point(circle2,3));
      label("$mathcal C'$", position=p2, align=2SE);

      pair midpoint = (p1 + p2)/2 + (0,-.1);
      path arc = p1 .. midpoint .. p2;

      draw(shift(0,-1)*arc, arrow=ArcArrow());

      shipout(scale(resolutionfactor)*currentpicture.fit());





      share|improve this answer






























        11














        Update:



        This new solution is a bit longer, but overcomes all three difficulties I list for the previous solution (below). In particular, this is a vector graphic.





        Since you expressed an interest in imitating a 3D helix with hidden lines, I also produced an example of that (in grayscale vector graphics with no shading):





        To compile either of these, first save the following code in a file called surfacepaths.asy:



        import graph3;
        import contour;

        // A bunch of auxiliary functions.

        real fuzz = .001;

        real umin(surface s) { return 0; }
        real vmin(surface s) { return 0; }
        pair uvmin(surface s) { return (umin(s), vmin(s)); }
        real umax(surface s, real fuzz=fuzz) {
        if (s.ucyclic()) return s.index.length;
        else return s.index.length - fuzz;
        }
        real vmax(surface s, real fuzz=fuzz) {
        if (s.vcyclic()) return s.index[0].length;
        return s.index[0].length - fuzz;
        }
        pair uvmax(surface s, real fuzz=fuzz) { return (umax(s,fuzz), vmax(s,fuzz)); }

        typedef real function(real, real);

        function normalDot(surface s, triple eyedir(triple)) {
        real toreturn(real u, real v) {
        return dot(s.normal(u, v), eyedir(s.point(u,v)));
        }
        return toreturn;
        }

        struct patchWithCoords {
        patch p;
        real u;
        real v;
        void operator init(patch p, real u, real v) {
        this.p = p;
        this.u = u;
        this.v = v;
        }
        void operator init(surface s, real u, real v) {
        int U=floor(u);
        int V=floor(v);
        int index = (s.index.length == 0 ? U+V : s.index[U][V]);

        this.p = s.s[index];
        this.u = u-U;
        this.v = v-V;
        }
        triple partialu() {
        return p.partialu(u,v);
        }
        triple partialv() {
        return p.partialv(u,v);
        }
        }

        triple[] derivative(surface s, pair pt) {
        patchWithCoords thepatch = patchWithCoords(s, pt.x, pt.y);
        return new triple[] {thepatch.partialu(), thepatch.partialv()};
        }

        typedef triple paramsurface(pair);

        paramsurface tangentplane(surface s, pair pt) {
        patchWithCoords thepatch = patchWithCoords(s, pt.x, pt.y);
        triple partialu = thepatch.partialu();
        triple partialv = thepatch.partialv();
        return new triple(pair tangentvector) {
        return s.point(pt.x, pt.y) + (tangentvector.x * partialu) + (tangentvector.y * partialv);
        };
        }

        guide[] normalpathuv(surface s, projection P = currentprojection, int n = ngraph) {
        triple eyedir(triple a);
        if (P.infinity) eyedir = new triple(triple) { return P.camera; };
        else eyedir = new triple(triple pt) { return P.camera - pt; };
        return contour(normalDot(s, eyedir), uvmin(s), uvmax(s), new real[] {0}, nx=n)[0];
        }

        path3 onSurface(surface s, path p) {
        triple f(int t) {
        pair point = point(p,t);
        return s.point(point.x, point.y);
        }

        guide3 toreturn = f(0);
        paramsurface thetangentplane = tangentplane(s, point(p,0));
        triple oldcontrol, newcontrol;
        int size = length(p);
        for (int i = 1; i <= size; ++i) {
        oldcontrol = thetangentplane(postcontrol(p,i-1) - point(p,i-1));
        thetangentplane = tangentplane(s, point(p,i));
        newcontrol = thetangentplane(precontrol(p, i) - point(p,i));
        toreturn = toreturn .. controls oldcontrol and newcontrol .. f(i);
        }

        if (cyclic(p)) toreturn = toreturn & cycle;

        return toreturn;

        }

        /*
        * This method returns an array of paths that trace out all the
        * points on s at which s is parallel to eyedir.
        */

        path[] paramSilhouetteNoEdges(surface s, projection P = currentprojection, int n = ngraph) {
        guide[] uvpaths = normalpathuv(s, P, n);
        //Reduce the number of segments to conserve memory
        for (int i = 0; i < uvpaths.length; ++i) {
        real len = length(uvpaths[i]);
        uvpaths[i] = graph(new pair(real t) {return point(uvpaths[i],t);}, 0, len, n=n);
        }
        return uvpaths;
        }

        private typedef real function2(real, real);
        private typedef real function3(triple);

        triple[] normalVectors(triple dir, triple surfacen) {
        dir = unit(dir);
        surfacen = unit(surfacen);
        triple v1, v2;
        int i = 0;
        do {
        v1 = unit(cross(dir, (unitrand(), unitrand(), unitrand())));
        v2 = unit(cross(dir, (unitrand(), unitrand(), unitrand())));
        ++i;
        } while ((abs(dot(v1,v2)) > Cos(10) || abs(dot(v1,surfacen)) > Cos(5) || abs(dot(v2,surfacen)) > Cos(5)) && i < 1000);
        if (i >= 1000) {
        write("problem: Unable to comply.");
        write(" dir = " + (string)dir);
        write(" surface normal = " + (string)surfacen);
        }
        return new triple[] {v1, v2};
        }

        function3 planeEqn(triple pt, triple normal) {
        return new real(triple r) {
        return dot(normal, r - pt);
        };
        }

        function2 pullback(function3 eqn, surface s) {
        return new real(real u, real v) {
        return eqn(s.point(u,v));
        };
        }

        /*
        * returns the distinct points in which the surface intersects
        * the line through the point pt in the direction dir
        */

        triple[] intersectionPoints(surface s, pair parampt, triple dir) {
        triple pt = s.point(parampt.x, parampt.y);
        triple[] lineNormals = normalVectors(dir, s.normal(parampt.x, parampt.y));
        path[][] curves;
        for (triple n : lineNormals) {
        function3 planeEn = planeEqn(pt, n);
        function2 pullback = pullback(planeEn, s);
        guide[] contour = contour(pullback, uvmin(s), uvmax(s), new real[]{0})[0];

        curves.push(contour);
        }
        pair[] intersectionPoints;
        for (path c1 : curves[0])
        for (path c2 : curves[1])
        intersectionPoints.append(intersectionpoints(c1, c2));
        triple[] toreturn;
        for (pair P : intersectionPoints)
        toreturn.push(s.point(P.x, P.y));
        return toreturn;
        }



        /*
        * Returns those intersection points for which the vector from pt forms an
        * acute angle with dir.
        */
        int numPointsInDirection(surface s, pair parampt, triple dir, real fuzz=.05) {
        triple pt = s.point(parampt.x, parampt.y);
        dir = unit(dir);
        triple[] intersections = intersectionPoints(s, parampt, dir);
        int num = 0;
        for (triple isection: intersections)
        if (dot(isection - pt, dir) > fuzz) ++num;
        return num;
        }

        bool3 increasing(real t0, real t1) {
        if (t0 < t1) return true;
        if (t0 > t1) return false;
        return default;
        }

        int[] extremes(real[] f, bool cyclic = f.cyclic) {
        bool3 lastIncreasing;
        bool3 nextIncreasing;
        int max;
        if (cyclic) {
        lastIncreasing = increasing(f[-1], f[0]);
        max = f.length - 1;
        } else {
        max = f.length - 2;
        if (increasing(f[0], f[1])) lastIncreasing = false;
        else lastIncreasing = true;
        }
        int[] toreturn;
        for (int i = 0; i <= max; ++i) {
        nextIncreasing = increasing(f[i], f[i+1]);
        if (lastIncreasing != nextIncreasing) {
        toreturn.push(i);
        }
        lastIncreasing = nextIncreasing;
        }
        if (!cyclic) toreturn.push(f.length - 1);
        toreturn.cyclic = cyclic;
        return toreturn;
        }

        int[] extremes(path path, real f(pair) = new real(pair P) {return P.x;})
        {
        real[] fvalues = new real[size(path)];
        for (int i = 0; i < fvalues.length; ++i) {
        fvalues[i] = f(point(path, i));
        }
        fvalues.cyclic = cyclic(path);
        int[] toreturn = extremes(fvalues);
        fvalues.delete();
        return toreturn;
        }

        path[] splitAtExtremes(path path, real f(pair) = new real(pair P) {return P.x;})
        {
        int[] splittingTimes = extremes(path, f);
        path[] toreturn;
        if (cyclic(path)) toreturn.push(subpath(path, splittingTimes[-1], splittingTimes[0]));
        for (int i = 0; i+1 < splittingTimes.length; ++i) {
        toreturn.push(subpath(path, splittingTimes[i], splittingTimes[i+1]));
        }
        return toreturn;
        }

        path[] splitAtExtremes(path[] paths, real f(pair P) = new real(pair P) {return P.x;})
        {
        path[] toreturn;
        for (path path : paths) {
        toreturn.append(splitAtExtremes(path, f));
        }
        return toreturn;
        }

        path3 toCamera(triple p, projection P=currentprojection, real fuzz = .01, real upperLimit = 100) {
        if (!P.infinity) {
        triple directionToCamera = unit(P.camera - p);
        triple startingPoint = p + fuzz*directionToCamera;
        return startingPoint -- P.camera;
        }
        else {
        triple directionToCamera = unit(P.camera);
        triple startingPoint = p + fuzz*directionToCamera;

        return startingPoint -- (p + upperLimit*directionToCamera);
        }
        }

        int numSheetsHiding(surface s, pair parampt, projection P = currentprojection) {
        triple p = s.point(parampt.x, parampt.y);
        path3 tocamera = toCamera(p, P);
        triple pt = beginpoint(tocamera);
        triple dir = endpoint(tocamera) - pt;
        return numPointsInDirection(s, parampt, dir);
        }

        struct coloredPath {
        path path;
        pen pen;
        void operator init(path path, pen p=currentpen) {
        this.path = path;
        this.pen = p;
        }
        /* draws the path with the pen having the specified weight (using colors)*/
        void draw(real weight) {
        draw(path, p=weight*pen + (1-weight)*white);
        }
        }
        coloredPath[][] layeredPaths;
        // onTop indicates whether the path should be added at the top or bottom of the specified layer
        void addPath(path path, pen p=currentpen, int layer, bool onTop=true) {
        coloredPath toAdd = coloredPath(path, p);
        if (layer >= layeredPaths.length) {
        layeredPaths[layer] = new coloredPath[] {toAdd};
        } else if (onTop) {
        layeredPaths[layer].push(toAdd);
        } else layeredPaths[layer].insert(0, toAdd);
        }

        void drawLayeredPaths() {
        for (int layer = layeredPaths.length - 1; layer >= 0; --layer) {
        real layerfactor = (1/3)^layer;
        for (coloredPath toDraw : layeredPaths[layer]) {
        toDraw.draw(layerfactor);
        }
        }
        layeredPaths.delete();
        }

        real[] cutTimes(path tocut, path[] knives) {
        real[] intersectionTimes = new real[] {0, length(tocut)};
        for (path knife : knives) {
        real[][] complexIntersections = intersections(tocut, knife);
        for (real[] times : complexIntersections) {
        intersectionTimes.push(times[0]);
        }
        }
        return sort(intersectionTimes);
        }

        path[] cut(path tocut, path[] knives) {
        real[] cutTimes = cutTimes(tocut, knives);
        path[] toreturn;
        for (int i = 0; i + 1 < cutTimes.length; ++i) {
        toreturn.push(subpath(tocut,cutTimes[i], cutTimes[i+1]));
        }
        return toreturn;
        }

        real[] condense(real[] values, real fuzz=.001) {
        values = sort(values);
        values.push(infinity);
        real previous = -infinity;
        real lastMin;
        real[] toReturn;
        for (real t : values) {
        if (t - fuzz > previous) {
        if (previous > -infinity) toReturn.push((lastMin + previous) / 2);
        lastMin = t;
        }
        previous = t;
        }
        return toReturn;
        }

        /*
        * A smooth surface parametrized by the domain [0,1] x [0,1]
        */
        struct SmoothSurface {
        surface s;
        private real sumax;
        private real svmax;
        path[] paramSilhouette;
        path[] projectedSilhouette;
        projection theProjection;

        triple camera;

        path3 onSurface(path paramPath) {
        return onSurface(s, scale(sumax,svmax)*paramPath);
        }

        triple point(real u, real v) { return s.point(sumax*u, svmax*v); }
        triple point(pair uv) { return point(uv.x, uv.y); }
        triple normal(real u, real v) { return s.normal(sumax*u, svmax*v); }
        triple normal(pair uv) { return normal(uv.x, uv.y); }

        triple[] derivative(real u, real v) { return derivative(s, (u/sumax, v/svmax)); }
        triple[] derivative(pair uv) { return derivative(uv.x, uv.y); }

        pen color(real u, real v, material m, light light) {
        return color(normal=normal(u,v), m=m, light=light);
        }
        pen color(pair uv, material m, light light) {
        return this.color(uv.x, uv.y, m, light);
        }
        pair project(triple pt) { return project(pt, theProjection); }

        void operator init(surface s, projection P=currentprojection) {
        this.s = s;
        this.sumax = umax(s);
        this.svmax = vmax(s);
        this.theProjection = P;
        this.paramSilhouette = scale(1/sumax, 1/svmax) * paramSilhouetteNoEdges(s,P);
        this.projectedSilhouette = sequence(new path(int i) {
        path3 truePath = onSurface(paramSilhouette[i]);
        path projectedPath = project(truePath, theProjection, ninterpolate=1);
        return projectedPath;
        }, paramSilhouette.length);
        camera=theProjection.camera;
        if(theProjection.infinity) {
        triple m=min(s);
        triple M=max(s);
        camera = theProjection.target
        + camerafactor*(abs(M-m)+abs(m-theProjection.target))*unit(theProjection.vector());
        }

        }

        int numSheetsHiding(pair parampt) {
        return numSheetsHiding(s, scale(sumax,svmax)*parampt);
        }
        int numSheetsHiding(real u, real v) {
        return numSheetsHiding((u,v));
        }

        void drawSilhouette(pen p=currentpen,
        bool includePathsBehind=false,
        bool onTop = true)
        {
        int[][] extremes;
        for (path path : projectedSilhouette) {
        extremes.push(extremes(path));
        }

        path[] splitSilhouette;
        path[] paramSplitSilhouette;

        /*
        * First, split at extremes to ensure that there are no
        * self-intersections of any one subpath in the projected silhouette.
        */

        for (int j = 0; j < paramSilhouette.length; ++j) {
        path current = projectedSilhouette[j];

        path currentParam = paramSilhouette[j];

        int[] dividers = extremes[j];
        for (int i = 0; i + 1 < dividers.length; ++i) {
        int start = dividers[i];
        int end = dividers[i+1];
        splitSilhouette.push(subpath(current,start,end));
        paramSplitSilhouette.push(subpath(currentParam, start, end));
        }
        }

        /*
        * Now, split at intersections of distinct subpaths.
        */

        for (int j = 0; j < splitSilhouette.length; ++j) {
        path current = splitSilhouette[j];
        path currentParam = paramSplitSilhouette[j];

        real[] splittingTimes = new real[] {0,length(current)};
        for (int k = 0; k < splitSilhouette.length; ++k) {
        if (j == k) continue;
        real[][] times = intersections(current, splitSilhouette[k]);
        for (real[] time : times) {
        real relevantTime = time[0];
        if (.01 < relevantTime && relevantTime < length(current) - .01) splittingTimes.push(relevantTime);
        }
        }
        splittingTimes = sort(splittingTimes);
        for (int i = 0; i + 1 < splittingTimes.length; ++i) {
        real start = splittingTimes[i];
        real end = splittingTimes[i+1];
        real mid = start + ((end-start) / (2+0.1*unitrand()));
        pair theparampoint = point(currentParam, mid);
        int sheets = numSheetsHiding(theparampoint);

        if (sheets == 0 || includePathsBehind) {
        path currentSubpath = subpath(current, start, end);
        addPath(currentSubpath, p=p, onTop=onTop, layer=sheets);
        }

        }
        }
        }

        /*
        Splits a parametrized path along the parametrized silhouette,
        taking [0,1] x [0,1] as the
        fundamental domain. Could be implemented more efficiently.
        */
        private real[] splitTimes(path thepath) {
        pair min = min(thepath);
        pair max = max(thepath);
        path[] baseknives = paramSilhouette;
        path[] knives;
        for (int u = floor(min.x); u < max.x + .001; ++u) {
        for (int v = floor(min.y); v < max.y + .001; ++v) {
        knives.append(shift(u,v)*baseknives);
        }
        }
        return cutTimes(thepath, knives);
        }

        /*
        Returns the times at which the projection of the given path3 intersects
        the projection of the surface silhouette. This may miss unstable
        intersections that can be detected by the previous method.
        */
        private real[] silhouetteCrossingTimes(path3 thepath, real fuzz = .01) {
        path projectedpath = project(thepath, theProjection, ninterpolate=1);
        real[] crossingTimes = cutTimes(projectedpath, projectedSilhouette);
        if (crossingTimes.length == 0) return crossingTimes;
        real current = 0;
        real[] toReturn = new real[] {0};
        for (real prospective : crossingTimes) {
        if (prospective > current + fuzz
        && prospective < length(thepath) - fuzz) {
        toReturn.push(prospective);
        current = prospective;
        }
        }
        toReturn.push(length(thepath));
        return toReturn;
        }

        path3 drawSurfacePath(path parampath, pen p=currentpen, bool onTop=true) {
        path[] toDraw;
        real[] crossingTimes = splitTimes(parampath);
        crossingTimes.append(silhouetteCrossingTimes(onSurface(parampath)));
        crossingTimes = condense(crossingTimes);
        for (int i = 0; i+1 < crossingTimes.length; ++i) {
        toDraw.push(subpath(parampath, crossingTimes[i], crossingTimes[i+1]));
        }
        for (path thepath : toDraw) {
        pair midpoint = point(thepath, length(thepath) / (2+.1*unitrand()));
        int sheets = numSheetsHiding(midpoint);
        path path3d = project(onSurface(thepath), theProjection, ninterpolate = 1);
        addPath(path3d, p=p, onTop=onTop, layer=sheets);
        }
        return onSurface(parampath);
        }

        }

        SmoothSurface operator *(transform3 t, SmoothSurface s) {
        return SmoothSurface(t*s.s);
        }


        To get the first image (with the two circles), in the same directory, run the following *.asy code:



        settings.outformat="pdf";
        import surfacepaths;

        unitsize(1.5cm);
        triple eye = 4*(0,1,3.5);
        currentprojection=perspective(eye, up=Y);

        surface torus = surface(O, shift(2X)*path3(scale(0.4)*unitcircle), axis=Y);
        SmoothSurface Torus = SmoothSurface(torus);

        Torus.drawSilhouette();
        real diff = .07;
        real midpoint = .75;
        path circle1 = project(Torus.drawSurfacePath((midpoint-diff, 0) -- (midpoint-diff, 1)), ninterpolate=1);
        path circle2 = project(Torus.drawSurfacePath((midpoint+diff,0) -- (midpoint+diff,1)),ninterpolate=1);

        drawLayeredPaths(); // Draw the paths (with hidden path removal) that were computed in the previous code block.

        real circle1mintime = mintimes(circle1)[1]; // the time at which circle1 reaches its minimum vertical extent
        pair p1 = point(circle1, circle1mintime);
        label("$mathcal C$", position=p1, align=SW);

        real circle2mintime = mintimes(circle2)[1];
        pair p2 = point(circle2, circle2mintime);
        label("$mathcal C'$", position=p2, align=SE);

        diff += .01;
        path3 arc = Torus.onSurface((midpoint-diff,.75) -- (midpoint+diff,.75));
        draw(shift(0,-1)*project(arc), arrow=ArcArrow());


        To get the torus wrapped in a helix, run the following Asymptote code:



        settings.outformat="pdf";
        import surfacepaths;

        size(400,0);
        currentprojection=perspective(camera=(0,4,14), up=Y);

        //function to return a line, but split into n parts (for better approximation when the line is transformed to lie on the surface)
        path splitline(pair a, pair b, int n) {
        pair f(real t) { return (1-t)*a + t*b; }
        return graph(f, 0, 1, n=n);
        }

        surface torus = surface(O, Circle(c=2X,r=0.4,normal=Z,n=32), axis=Y, n=32);
        /* Let Asymptote know (in case it did not already) that the u and v
        * coordinates of this surface are both cyclic.
        */
        torus.ucyclic(true);
        torus.vcyclic(true);
        SmoothSurface Torus = SmoothSurface(torus);

        Torus.drawSilhouette(includePathsBehind=true);
        Torus.drawSurfacePath(splitline((0,0), (1,20), n=100), p=gray);

        drawLayeredPaths();




        Old (rasterized-only) solution:



        Here's an Asymptote solution that sort of works.
        enter image description here



        Note that as currently formulated, it has the following difficulties:




        1. The torus must be opaque.

        2. Anything that should be partially obscured by the torus has to be given in rasterized output (although I've got a scaling factor included to give high resolution). The rest of the image (i.e., the labels and the arrow) is vector output.

        3. It only works with an orthographic projection.


        Anyway, here's the code:



        import graph3;
        import contour;

        // A bunch of auxiliary functions for drawing silhouettes.

        real fuzz = .001;

        real umin(surface s) { return 0; }
        real vmin(surface s) { return 0; }
        pair uvmin(surface s) { return (umin(s), vmin(s)); }
        real umax(surface s, real fuzz=fuzz) {
        if (s.ucyclic()) return s.index.length;
        else return s.index.length - fuzz;
        }
        real vmax(surface s, real fuzz=fuzz) {
        if (s.vcyclic()) return s.index[0].length;
        return s.index[0].length - fuzz;
        }
        pair uvmax(surface s, real fuzz=fuzz) { return (umax(s,fuzz), vmax(s,fuzz)); }

        typedef real function(real, real);

        function normalDot(surface s, triple eyedir) {
        real toreturn(real u, real v) {
        return dot(s.normal(u, v), eyedir);
        }
        return toreturn;
        }

        guide[] normalpathuv(surface s, triple eyedir) {
        return contour(normalDot(s, eyedir), uvmin(s), uvmax(s), new real[] {0})[0];
        }

        path3 onSurface(surface s, path p) {
        triple f(real t) {
        pair point = point(p,t);
        return s.point(point.x, point.y);
        }
        if (cyclic(p)) {
        guide3 toreturn = f(0);
        for (int i = 1; i < size(p); ++i)
        toreturn = toreturn -- f(i);
        toreturn = toreturn -- cycle;
        return toreturn;
        }
        return graph(f, 0, length(p));
        }

        /*
        * This method returns an array of paths that trace out all the
        * points on s at which s is normal to eyedir.
        */
        path3[] silhouetteNormal(surface s, triple eyedir) {
        guide[] uvpaths = normalpathuv(s, eyedir);
        path3[] toreturn = new path3[uvpaths.length];
        for (int i = 0; i < uvpaths.length; ++i) {
        toreturn[i] = onSurface(s, uvpaths[i]);
        }
        return toreturn;
        }



        /*
        * Now, let's actually draw the picture.
        */

        settings.outformat="pdf";
        int resolutionfactor = 4;
        settings.render=2*resolutionfactor;
        settings.prc=false;

        unitsize(1.5cm);
        triple eye = (0,1,3);
        currentprojection=orthographic(eye, up=Y);

        surface torus = surface(O, shift(2X)*path3(scale(0.5)*unitcircle), axis=Y);

        draw(silhouetteNormal(torus, eye));
        draw(torus, surfacepen=emissive(white));

        real umin = umin(torus);
        real umax = umax(torus);
        real diff = 0.07;
        path3 circle1 = torus.uequals((.75-diff)*umax);
        draw(circle1);
        pair p1 = project(point(circle1, 3));
        label("$mathcal C$", position=p1, align=2SW);

        path3 circle2 = torus.uequals((.75+diff)*umax);
        draw(circle2);
        pair p2 = project(point(circle2,3));
        label("$mathcal C'$", position=p2, align=2SE);

        pair midpoint = (p1 + p2)/2 + (0,-.1);
        path arc = p1 .. midpoint .. p2;

        draw(shift(0,-1)*arc, arrow=ArcArrow());

        shipout(scale(resolutionfactor)*currentpicture.fit());





        share|improve this answer




























          11












          11








          11







          Update:



          This new solution is a bit longer, but overcomes all three difficulties I list for the previous solution (below). In particular, this is a vector graphic.





          Since you expressed an interest in imitating a 3D helix with hidden lines, I also produced an example of that (in grayscale vector graphics with no shading):





          To compile either of these, first save the following code in a file called surfacepaths.asy:



          import graph3;
          import contour;

          // A bunch of auxiliary functions.

          real fuzz = .001;

          real umin(surface s) { return 0; }
          real vmin(surface s) { return 0; }
          pair uvmin(surface s) { return (umin(s), vmin(s)); }
          real umax(surface s, real fuzz=fuzz) {
          if (s.ucyclic()) return s.index.length;
          else return s.index.length - fuzz;
          }
          real vmax(surface s, real fuzz=fuzz) {
          if (s.vcyclic()) return s.index[0].length;
          return s.index[0].length - fuzz;
          }
          pair uvmax(surface s, real fuzz=fuzz) { return (umax(s,fuzz), vmax(s,fuzz)); }

          typedef real function(real, real);

          function normalDot(surface s, triple eyedir(triple)) {
          real toreturn(real u, real v) {
          return dot(s.normal(u, v), eyedir(s.point(u,v)));
          }
          return toreturn;
          }

          struct patchWithCoords {
          patch p;
          real u;
          real v;
          void operator init(patch p, real u, real v) {
          this.p = p;
          this.u = u;
          this.v = v;
          }
          void operator init(surface s, real u, real v) {
          int U=floor(u);
          int V=floor(v);
          int index = (s.index.length == 0 ? U+V : s.index[U][V]);

          this.p = s.s[index];
          this.u = u-U;
          this.v = v-V;
          }
          triple partialu() {
          return p.partialu(u,v);
          }
          triple partialv() {
          return p.partialv(u,v);
          }
          }

          triple[] derivative(surface s, pair pt) {
          patchWithCoords thepatch = patchWithCoords(s, pt.x, pt.y);
          return new triple[] {thepatch.partialu(), thepatch.partialv()};
          }

          typedef triple paramsurface(pair);

          paramsurface tangentplane(surface s, pair pt) {
          patchWithCoords thepatch = patchWithCoords(s, pt.x, pt.y);
          triple partialu = thepatch.partialu();
          triple partialv = thepatch.partialv();
          return new triple(pair tangentvector) {
          return s.point(pt.x, pt.y) + (tangentvector.x * partialu) + (tangentvector.y * partialv);
          };
          }

          guide[] normalpathuv(surface s, projection P = currentprojection, int n = ngraph) {
          triple eyedir(triple a);
          if (P.infinity) eyedir = new triple(triple) { return P.camera; };
          else eyedir = new triple(triple pt) { return P.camera - pt; };
          return contour(normalDot(s, eyedir), uvmin(s), uvmax(s), new real[] {0}, nx=n)[0];
          }

          path3 onSurface(surface s, path p) {
          triple f(int t) {
          pair point = point(p,t);
          return s.point(point.x, point.y);
          }

          guide3 toreturn = f(0);
          paramsurface thetangentplane = tangentplane(s, point(p,0));
          triple oldcontrol, newcontrol;
          int size = length(p);
          for (int i = 1; i <= size; ++i) {
          oldcontrol = thetangentplane(postcontrol(p,i-1) - point(p,i-1));
          thetangentplane = tangentplane(s, point(p,i));
          newcontrol = thetangentplane(precontrol(p, i) - point(p,i));
          toreturn = toreturn .. controls oldcontrol and newcontrol .. f(i);
          }

          if (cyclic(p)) toreturn = toreturn & cycle;

          return toreturn;

          }

          /*
          * This method returns an array of paths that trace out all the
          * points on s at which s is parallel to eyedir.
          */

          path[] paramSilhouetteNoEdges(surface s, projection P = currentprojection, int n = ngraph) {
          guide[] uvpaths = normalpathuv(s, P, n);
          //Reduce the number of segments to conserve memory
          for (int i = 0; i < uvpaths.length; ++i) {
          real len = length(uvpaths[i]);
          uvpaths[i] = graph(new pair(real t) {return point(uvpaths[i],t);}, 0, len, n=n);
          }
          return uvpaths;
          }

          private typedef real function2(real, real);
          private typedef real function3(triple);

          triple[] normalVectors(triple dir, triple surfacen) {
          dir = unit(dir);
          surfacen = unit(surfacen);
          triple v1, v2;
          int i = 0;
          do {
          v1 = unit(cross(dir, (unitrand(), unitrand(), unitrand())));
          v2 = unit(cross(dir, (unitrand(), unitrand(), unitrand())));
          ++i;
          } while ((abs(dot(v1,v2)) > Cos(10) || abs(dot(v1,surfacen)) > Cos(5) || abs(dot(v2,surfacen)) > Cos(5)) && i < 1000);
          if (i >= 1000) {
          write("problem: Unable to comply.");
          write(" dir = " + (string)dir);
          write(" surface normal = " + (string)surfacen);
          }
          return new triple[] {v1, v2};
          }

          function3 planeEqn(triple pt, triple normal) {
          return new real(triple r) {
          return dot(normal, r - pt);
          };
          }

          function2 pullback(function3 eqn, surface s) {
          return new real(real u, real v) {
          return eqn(s.point(u,v));
          };
          }

          /*
          * returns the distinct points in which the surface intersects
          * the line through the point pt in the direction dir
          */

          triple[] intersectionPoints(surface s, pair parampt, triple dir) {
          triple pt = s.point(parampt.x, parampt.y);
          triple[] lineNormals = normalVectors(dir, s.normal(parampt.x, parampt.y));
          path[][] curves;
          for (triple n : lineNormals) {
          function3 planeEn = planeEqn(pt, n);
          function2 pullback = pullback(planeEn, s);
          guide[] contour = contour(pullback, uvmin(s), uvmax(s), new real[]{0})[0];

          curves.push(contour);
          }
          pair[] intersectionPoints;
          for (path c1 : curves[0])
          for (path c2 : curves[1])
          intersectionPoints.append(intersectionpoints(c1, c2));
          triple[] toreturn;
          for (pair P : intersectionPoints)
          toreturn.push(s.point(P.x, P.y));
          return toreturn;
          }



          /*
          * Returns those intersection points for which the vector from pt forms an
          * acute angle with dir.
          */
          int numPointsInDirection(surface s, pair parampt, triple dir, real fuzz=.05) {
          triple pt = s.point(parampt.x, parampt.y);
          dir = unit(dir);
          triple[] intersections = intersectionPoints(s, parampt, dir);
          int num = 0;
          for (triple isection: intersections)
          if (dot(isection - pt, dir) > fuzz) ++num;
          return num;
          }

          bool3 increasing(real t0, real t1) {
          if (t0 < t1) return true;
          if (t0 > t1) return false;
          return default;
          }

          int[] extremes(real[] f, bool cyclic = f.cyclic) {
          bool3 lastIncreasing;
          bool3 nextIncreasing;
          int max;
          if (cyclic) {
          lastIncreasing = increasing(f[-1], f[0]);
          max = f.length - 1;
          } else {
          max = f.length - 2;
          if (increasing(f[0], f[1])) lastIncreasing = false;
          else lastIncreasing = true;
          }
          int[] toreturn;
          for (int i = 0; i <= max; ++i) {
          nextIncreasing = increasing(f[i], f[i+1]);
          if (lastIncreasing != nextIncreasing) {
          toreturn.push(i);
          }
          lastIncreasing = nextIncreasing;
          }
          if (!cyclic) toreturn.push(f.length - 1);
          toreturn.cyclic = cyclic;
          return toreturn;
          }

          int[] extremes(path path, real f(pair) = new real(pair P) {return P.x;})
          {
          real[] fvalues = new real[size(path)];
          for (int i = 0; i < fvalues.length; ++i) {
          fvalues[i] = f(point(path, i));
          }
          fvalues.cyclic = cyclic(path);
          int[] toreturn = extremes(fvalues);
          fvalues.delete();
          return toreturn;
          }

          path[] splitAtExtremes(path path, real f(pair) = new real(pair P) {return P.x;})
          {
          int[] splittingTimes = extremes(path, f);
          path[] toreturn;
          if (cyclic(path)) toreturn.push(subpath(path, splittingTimes[-1], splittingTimes[0]));
          for (int i = 0; i+1 < splittingTimes.length; ++i) {
          toreturn.push(subpath(path, splittingTimes[i], splittingTimes[i+1]));
          }
          return toreturn;
          }

          path[] splitAtExtremes(path[] paths, real f(pair P) = new real(pair P) {return P.x;})
          {
          path[] toreturn;
          for (path path : paths) {
          toreturn.append(splitAtExtremes(path, f));
          }
          return toreturn;
          }

          path3 toCamera(triple p, projection P=currentprojection, real fuzz = .01, real upperLimit = 100) {
          if (!P.infinity) {
          triple directionToCamera = unit(P.camera - p);
          triple startingPoint = p + fuzz*directionToCamera;
          return startingPoint -- P.camera;
          }
          else {
          triple directionToCamera = unit(P.camera);
          triple startingPoint = p + fuzz*directionToCamera;

          return startingPoint -- (p + upperLimit*directionToCamera);
          }
          }

          int numSheetsHiding(surface s, pair parampt, projection P = currentprojection) {
          triple p = s.point(parampt.x, parampt.y);
          path3 tocamera = toCamera(p, P);
          triple pt = beginpoint(tocamera);
          triple dir = endpoint(tocamera) - pt;
          return numPointsInDirection(s, parampt, dir);
          }

          struct coloredPath {
          path path;
          pen pen;
          void operator init(path path, pen p=currentpen) {
          this.path = path;
          this.pen = p;
          }
          /* draws the path with the pen having the specified weight (using colors)*/
          void draw(real weight) {
          draw(path, p=weight*pen + (1-weight)*white);
          }
          }
          coloredPath[][] layeredPaths;
          // onTop indicates whether the path should be added at the top or bottom of the specified layer
          void addPath(path path, pen p=currentpen, int layer, bool onTop=true) {
          coloredPath toAdd = coloredPath(path, p);
          if (layer >= layeredPaths.length) {
          layeredPaths[layer] = new coloredPath[] {toAdd};
          } else if (onTop) {
          layeredPaths[layer].push(toAdd);
          } else layeredPaths[layer].insert(0, toAdd);
          }

          void drawLayeredPaths() {
          for (int layer = layeredPaths.length - 1; layer >= 0; --layer) {
          real layerfactor = (1/3)^layer;
          for (coloredPath toDraw : layeredPaths[layer]) {
          toDraw.draw(layerfactor);
          }
          }
          layeredPaths.delete();
          }

          real[] cutTimes(path tocut, path[] knives) {
          real[] intersectionTimes = new real[] {0, length(tocut)};
          for (path knife : knives) {
          real[][] complexIntersections = intersections(tocut, knife);
          for (real[] times : complexIntersections) {
          intersectionTimes.push(times[0]);
          }
          }
          return sort(intersectionTimes);
          }

          path[] cut(path tocut, path[] knives) {
          real[] cutTimes = cutTimes(tocut, knives);
          path[] toreturn;
          for (int i = 0; i + 1 < cutTimes.length; ++i) {
          toreturn.push(subpath(tocut,cutTimes[i], cutTimes[i+1]));
          }
          return toreturn;
          }

          real[] condense(real[] values, real fuzz=.001) {
          values = sort(values);
          values.push(infinity);
          real previous = -infinity;
          real lastMin;
          real[] toReturn;
          for (real t : values) {
          if (t - fuzz > previous) {
          if (previous > -infinity) toReturn.push((lastMin + previous) / 2);
          lastMin = t;
          }
          previous = t;
          }
          return toReturn;
          }

          /*
          * A smooth surface parametrized by the domain [0,1] x [0,1]
          */
          struct SmoothSurface {
          surface s;
          private real sumax;
          private real svmax;
          path[] paramSilhouette;
          path[] projectedSilhouette;
          projection theProjection;

          triple camera;

          path3 onSurface(path paramPath) {
          return onSurface(s, scale(sumax,svmax)*paramPath);
          }

          triple point(real u, real v) { return s.point(sumax*u, svmax*v); }
          triple point(pair uv) { return point(uv.x, uv.y); }
          triple normal(real u, real v) { return s.normal(sumax*u, svmax*v); }
          triple normal(pair uv) { return normal(uv.x, uv.y); }

          triple[] derivative(real u, real v) { return derivative(s, (u/sumax, v/svmax)); }
          triple[] derivative(pair uv) { return derivative(uv.x, uv.y); }

          pen color(real u, real v, material m, light light) {
          return color(normal=normal(u,v), m=m, light=light);
          }
          pen color(pair uv, material m, light light) {
          return this.color(uv.x, uv.y, m, light);
          }
          pair project(triple pt) { return project(pt, theProjection); }

          void operator init(surface s, projection P=currentprojection) {
          this.s = s;
          this.sumax = umax(s);
          this.svmax = vmax(s);
          this.theProjection = P;
          this.paramSilhouette = scale(1/sumax, 1/svmax) * paramSilhouetteNoEdges(s,P);
          this.projectedSilhouette = sequence(new path(int i) {
          path3 truePath = onSurface(paramSilhouette[i]);
          path projectedPath = project(truePath, theProjection, ninterpolate=1);
          return projectedPath;
          }, paramSilhouette.length);
          camera=theProjection.camera;
          if(theProjection.infinity) {
          triple m=min(s);
          triple M=max(s);
          camera = theProjection.target
          + camerafactor*(abs(M-m)+abs(m-theProjection.target))*unit(theProjection.vector());
          }

          }

          int numSheetsHiding(pair parampt) {
          return numSheetsHiding(s, scale(sumax,svmax)*parampt);
          }
          int numSheetsHiding(real u, real v) {
          return numSheetsHiding((u,v));
          }

          void drawSilhouette(pen p=currentpen,
          bool includePathsBehind=false,
          bool onTop = true)
          {
          int[][] extremes;
          for (path path : projectedSilhouette) {
          extremes.push(extremes(path));
          }

          path[] splitSilhouette;
          path[] paramSplitSilhouette;

          /*
          * First, split at extremes to ensure that there are no
          * self-intersections of any one subpath in the projected silhouette.
          */

          for (int j = 0; j < paramSilhouette.length; ++j) {
          path current = projectedSilhouette[j];

          path currentParam = paramSilhouette[j];

          int[] dividers = extremes[j];
          for (int i = 0; i + 1 < dividers.length; ++i) {
          int start = dividers[i];
          int end = dividers[i+1];
          splitSilhouette.push(subpath(current,start,end));
          paramSplitSilhouette.push(subpath(currentParam, start, end));
          }
          }

          /*
          * Now, split at intersections of distinct subpaths.
          */

          for (int j = 0; j < splitSilhouette.length; ++j) {
          path current = splitSilhouette[j];
          path currentParam = paramSplitSilhouette[j];

          real[] splittingTimes = new real[] {0,length(current)};
          for (int k = 0; k < splitSilhouette.length; ++k) {
          if (j == k) continue;
          real[][] times = intersections(current, splitSilhouette[k]);
          for (real[] time : times) {
          real relevantTime = time[0];
          if (.01 < relevantTime && relevantTime < length(current) - .01) splittingTimes.push(relevantTime);
          }
          }
          splittingTimes = sort(splittingTimes);
          for (int i = 0; i + 1 < splittingTimes.length; ++i) {
          real start = splittingTimes[i];
          real end = splittingTimes[i+1];
          real mid = start + ((end-start) / (2+0.1*unitrand()));
          pair theparampoint = point(currentParam, mid);
          int sheets = numSheetsHiding(theparampoint);

          if (sheets == 0 || includePathsBehind) {
          path currentSubpath = subpath(current, start, end);
          addPath(currentSubpath, p=p, onTop=onTop, layer=sheets);
          }

          }
          }
          }

          /*
          Splits a parametrized path along the parametrized silhouette,
          taking [0,1] x [0,1] as the
          fundamental domain. Could be implemented more efficiently.
          */
          private real[] splitTimes(path thepath) {
          pair min = min(thepath);
          pair max = max(thepath);
          path[] baseknives = paramSilhouette;
          path[] knives;
          for (int u = floor(min.x); u < max.x + .001; ++u) {
          for (int v = floor(min.y); v < max.y + .001; ++v) {
          knives.append(shift(u,v)*baseknives);
          }
          }
          return cutTimes(thepath, knives);
          }

          /*
          Returns the times at which the projection of the given path3 intersects
          the projection of the surface silhouette. This may miss unstable
          intersections that can be detected by the previous method.
          */
          private real[] silhouetteCrossingTimes(path3 thepath, real fuzz = .01) {
          path projectedpath = project(thepath, theProjection, ninterpolate=1);
          real[] crossingTimes = cutTimes(projectedpath, projectedSilhouette);
          if (crossingTimes.length == 0) return crossingTimes;
          real current = 0;
          real[] toReturn = new real[] {0};
          for (real prospective : crossingTimes) {
          if (prospective > current + fuzz
          && prospective < length(thepath) - fuzz) {
          toReturn.push(prospective);
          current = prospective;
          }
          }
          toReturn.push(length(thepath));
          return toReturn;
          }

          path3 drawSurfacePath(path parampath, pen p=currentpen, bool onTop=true) {
          path[] toDraw;
          real[] crossingTimes = splitTimes(parampath);
          crossingTimes.append(silhouetteCrossingTimes(onSurface(parampath)));
          crossingTimes = condense(crossingTimes);
          for (int i = 0; i+1 < crossingTimes.length; ++i) {
          toDraw.push(subpath(parampath, crossingTimes[i], crossingTimes[i+1]));
          }
          for (path thepath : toDraw) {
          pair midpoint = point(thepath, length(thepath) / (2+.1*unitrand()));
          int sheets = numSheetsHiding(midpoint);
          path path3d = project(onSurface(thepath), theProjection, ninterpolate = 1);
          addPath(path3d, p=p, onTop=onTop, layer=sheets);
          }
          return onSurface(parampath);
          }

          }

          SmoothSurface operator *(transform3 t, SmoothSurface s) {
          return SmoothSurface(t*s.s);
          }


          To get the first image (with the two circles), in the same directory, run the following *.asy code:



          settings.outformat="pdf";
          import surfacepaths;

          unitsize(1.5cm);
          triple eye = 4*(0,1,3.5);
          currentprojection=perspective(eye, up=Y);

          surface torus = surface(O, shift(2X)*path3(scale(0.4)*unitcircle), axis=Y);
          SmoothSurface Torus = SmoothSurface(torus);

          Torus.drawSilhouette();
          real diff = .07;
          real midpoint = .75;
          path circle1 = project(Torus.drawSurfacePath((midpoint-diff, 0) -- (midpoint-diff, 1)), ninterpolate=1);
          path circle2 = project(Torus.drawSurfacePath((midpoint+diff,0) -- (midpoint+diff,1)),ninterpolate=1);

          drawLayeredPaths(); // Draw the paths (with hidden path removal) that were computed in the previous code block.

          real circle1mintime = mintimes(circle1)[1]; // the time at which circle1 reaches its minimum vertical extent
          pair p1 = point(circle1, circle1mintime);
          label("$mathcal C$", position=p1, align=SW);

          real circle2mintime = mintimes(circle2)[1];
          pair p2 = point(circle2, circle2mintime);
          label("$mathcal C'$", position=p2, align=SE);

          diff += .01;
          path3 arc = Torus.onSurface((midpoint-diff,.75) -- (midpoint+diff,.75));
          draw(shift(0,-1)*project(arc), arrow=ArcArrow());


          To get the torus wrapped in a helix, run the following Asymptote code:



          settings.outformat="pdf";
          import surfacepaths;

          size(400,0);
          currentprojection=perspective(camera=(0,4,14), up=Y);

          //function to return a line, but split into n parts (for better approximation when the line is transformed to lie on the surface)
          path splitline(pair a, pair b, int n) {
          pair f(real t) { return (1-t)*a + t*b; }
          return graph(f, 0, 1, n=n);
          }

          surface torus = surface(O, Circle(c=2X,r=0.4,normal=Z,n=32), axis=Y, n=32);
          /* Let Asymptote know (in case it did not already) that the u and v
          * coordinates of this surface are both cyclic.
          */
          torus.ucyclic(true);
          torus.vcyclic(true);
          SmoothSurface Torus = SmoothSurface(torus);

          Torus.drawSilhouette(includePathsBehind=true);
          Torus.drawSurfacePath(splitline((0,0), (1,20), n=100), p=gray);

          drawLayeredPaths();




          Old (rasterized-only) solution:



          Here's an Asymptote solution that sort of works.
          enter image description here



          Note that as currently formulated, it has the following difficulties:




          1. The torus must be opaque.

          2. Anything that should be partially obscured by the torus has to be given in rasterized output (although I've got a scaling factor included to give high resolution). The rest of the image (i.e., the labels and the arrow) is vector output.

          3. It only works with an orthographic projection.


          Anyway, here's the code:



          import graph3;
          import contour;

          // A bunch of auxiliary functions for drawing silhouettes.

          real fuzz = .001;

          real umin(surface s) { return 0; }
          real vmin(surface s) { return 0; }
          pair uvmin(surface s) { return (umin(s), vmin(s)); }
          real umax(surface s, real fuzz=fuzz) {
          if (s.ucyclic()) return s.index.length;
          else return s.index.length - fuzz;
          }
          real vmax(surface s, real fuzz=fuzz) {
          if (s.vcyclic()) return s.index[0].length;
          return s.index[0].length - fuzz;
          }
          pair uvmax(surface s, real fuzz=fuzz) { return (umax(s,fuzz), vmax(s,fuzz)); }

          typedef real function(real, real);

          function normalDot(surface s, triple eyedir) {
          real toreturn(real u, real v) {
          return dot(s.normal(u, v), eyedir);
          }
          return toreturn;
          }

          guide[] normalpathuv(surface s, triple eyedir) {
          return contour(normalDot(s, eyedir), uvmin(s), uvmax(s), new real[] {0})[0];
          }

          path3 onSurface(surface s, path p) {
          triple f(real t) {
          pair point = point(p,t);
          return s.point(point.x, point.y);
          }
          if (cyclic(p)) {
          guide3 toreturn = f(0);
          for (int i = 1; i < size(p); ++i)
          toreturn = toreturn -- f(i);
          toreturn = toreturn -- cycle;
          return toreturn;
          }
          return graph(f, 0, length(p));
          }

          /*
          * This method returns an array of paths that trace out all the
          * points on s at which s is normal to eyedir.
          */
          path3[] silhouetteNormal(surface s, triple eyedir) {
          guide[] uvpaths = normalpathuv(s, eyedir);
          path3[] toreturn = new path3[uvpaths.length];
          for (int i = 0; i < uvpaths.length; ++i) {
          toreturn[i] = onSurface(s, uvpaths[i]);
          }
          return toreturn;
          }



          /*
          * Now, let's actually draw the picture.
          */

          settings.outformat="pdf";
          int resolutionfactor = 4;
          settings.render=2*resolutionfactor;
          settings.prc=false;

          unitsize(1.5cm);
          triple eye = (0,1,3);
          currentprojection=orthographic(eye, up=Y);

          surface torus = surface(O, shift(2X)*path3(scale(0.5)*unitcircle), axis=Y);

          draw(silhouetteNormal(torus, eye));
          draw(torus, surfacepen=emissive(white));

          real umin = umin(torus);
          real umax = umax(torus);
          real diff = 0.07;
          path3 circle1 = torus.uequals((.75-diff)*umax);
          draw(circle1);
          pair p1 = project(point(circle1, 3));
          label("$mathcal C$", position=p1, align=2SW);

          path3 circle2 = torus.uequals((.75+diff)*umax);
          draw(circle2);
          pair p2 = project(point(circle2,3));
          label("$mathcal C'$", position=p2, align=2SE);

          pair midpoint = (p1 + p2)/2 + (0,-.1);
          path arc = p1 .. midpoint .. p2;

          draw(shift(0,-1)*arc, arrow=ArcArrow());

          shipout(scale(resolutionfactor)*currentpicture.fit());





          share|improve this answer















          Update:



          This new solution is a bit longer, but overcomes all three difficulties I list for the previous solution (below). In particular, this is a vector graphic.





          Since you expressed an interest in imitating a 3D helix with hidden lines, I also produced an example of that (in grayscale vector graphics with no shading):





          To compile either of these, first save the following code in a file called surfacepaths.asy:



          import graph3;
          import contour;

          // A bunch of auxiliary functions.

          real fuzz = .001;

          real umin(surface s) { return 0; }
          real vmin(surface s) { return 0; }
          pair uvmin(surface s) { return (umin(s), vmin(s)); }
          real umax(surface s, real fuzz=fuzz) {
          if (s.ucyclic()) return s.index.length;
          else return s.index.length - fuzz;
          }
          real vmax(surface s, real fuzz=fuzz) {
          if (s.vcyclic()) return s.index[0].length;
          return s.index[0].length - fuzz;
          }
          pair uvmax(surface s, real fuzz=fuzz) { return (umax(s,fuzz), vmax(s,fuzz)); }

          typedef real function(real, real);

          function normalDot(surface s, triple eyedir(triple)) {
          real toreturn(real u, real v) {
          return dot(s.normal(u, v), eyedir(s.point(u,v)));
          }
          return toreturn;
          }

          struct patchWithCoords {
          patch p;
          real u;
          real v;
          void operator init(patch p, real u, real v) {
          this.p = p;
          this.u = u;
          this.v = v;
          }
          void operator init(surface s, real u, real v) {
          int U=floor(u);
          int V=floor(v);
          int index = (s.index.length == 0 ? U+V : s.index[U][V]);

          this.p = s.s[index];
          this.u = u-U;
          this.v = v-V;
          }
          triple partialu() {
          return p.partialu(u,v);
          }
          triple partialv() {
          return p.partialv(u,v);
          }
          }

          triple[] derivative(surface s, pair pt) {
          patchWithCoords thepatch = patchWithCoords(s, pt.x, pt.y);
          return new triple[] {thepatch.partialu(), thepatch.partialv()};
          }

          typedef triple paramsurface(pair);

          paramsurface tangentplane(surface s, pair pt) {
          patchWithCoords thepatch = patchWithCoords(s, pt.x, pt.y);
          triple partialu = thepatch.partialu();
          triple partialv = thepatch.partialv();
          return new triple(pair tangentvector) {
          return s.point(pt.x, pt.y) + (tangentvector.x * partialu) + (tangentvector.y * partialv);
          };
          }

          guide[] normalpathuv(surface s, projection P = currentprojection, int n = ngraph) {
          triple eyedir(triple a);
          if (P.infinity) eyedir = new triple(triple) { return P.camera; };
          else eyedir = new triple(triple pt) { return P.camera - pt; };
          return contour(normalDot(s, eyedir), uvmin(s), uvmax(s), new real[] {0}, nx=n)[0];
          }

          path3 onSurface(surface s, path p) {
          triple f(int t) {
          pair point = point(p,t);
          return s.point(point.x, point.y);
          }

          guide3 toreturn = f(0);
          paramsurface thetangentplane = tangentplane(s, point(p,0));
          triple oldcontrol, newcontrol;
          int size = length(p);
          for (int i = 1; i <= size; ++i) {
          oldcontrol = thetangentplane(postcontrol(p,i-1) - point(p,i-1));
          thetangentplane = tangentplane(s, point(p,i));
          newcontrol = thetangentplane(precontrol(p, i) - point(p,i));
          toreturn = toreturn .. controls oldcontrol and newcontrol .. f(i);
          }

          if (cyclic(p)) toreturn = toreturn & cycle;

          return toreturn;

          }

          /*
          * This method returns an array of paths that trace out all the
          * points on s at which s is parallel to eyedir.
          */

          path[] paramSilhouetteNoEdges(surface s, projection P = currentprojection, int n = ngraph) {
          guide[] uvpaths = normalpathuv(s, P, n);
          //Reduce the number of segments to conserve memory
          for (int i = 0; i < uvpaths.length; ++i) {
          real len = length(uvpaths[i]);
          uvpaths[i] = graph(new pair(real t) {return point(uvpaths[i],t);}, 0, len, n=n);
          }
          return uvpaths;
          }

          private typedef real function2(real, real);
          private typedef real function3(triple);

          triple[] normalVectors(triple dir, triple surfacen) {
          dir = unit(dir);
          surfacen = unit(surfacen);
          triple v1, v2;
          int i = 0;
          do {
          v1 = unit(cross(dir, (unitrand(), unitrand(), unitrand())));
          v2 = unit(cross(dir, (unitrand(), unitrand(), unitrand())));
          ++i;
          } while ((abs(dot(v1,v2)) > Cos(10) || abs(dot(v1,surfacen)) > Cos(5) || abs(dot(v2,surfacen)) > Cos(5)) && i < 1000);
          if (i >= 1000) {
          write("problem: Unable to comply.");
          write(" dir = " + (string)dir);
          write(" surface normal = " + (string)surfacen);
          }
          return new triple[] {v1, v2};
          }

          function3 planeEqn(triple pt, triple normal) {
          return new real(triple r) {
          return dot(normal, r - pt);
          };
          }

          function2 pullback(function3 eqn, surface s) {
          return new real(real u, real v) {
          return eqn(s.point(u,v));
          };
          }

          /*
          * returns the distinct points in which the surface intersects
          * the line through the point pt in the direction dir
          */

          triple[] intersectionPoints(surface s, pair parampt, triple dir) {
          triple pt = s.point(parampt.x, parampt.y);
          triple[] lineNormals = normalVectors(dir, s.normal(parampt.x, parampt.y));
          path[][] curves;
          for (triple n : lineNormals) {
          function3 planeEn = planeEqn(pt, n);
          function2 pullback = pullback(planeEn, s);
          guide[] contour = contour(pullback, uvmin(s), uvmax(s), new real[]{0})[0];

          curves.push(contour);
          }
          pair[] intersectionPoints;
          for (path c1 : curves[0])
          for (path c2 : curves[1])
          intersectionPoints.append(intersectionpoints(c1, c2));
          triple[] toreturn;
          for (pair P : intersectionPoints)
          toreturn.push(s.point(P.x, P.y));
          return toreturn;
          }



          /*
          * Returns those intersection points for which the vector from pt forms an
          * acute angle with dir.
          */
          int numPointsInDirection(surface s, pair parampt, triple dir, real fuzz=.05) {
          triple pt = s.point(parampt.x, parampt.y);
          dir = unit(dir);
          triple[] intersections = intersectionPoints(s, parampt, dir);
          int num = 0;
          for (triple isection: intersections)
          if (dot(isection - pt, dir) > fuzz) ++num;
          return num;
          }

          bool3 increasing(real t0, real t1) {
          if (t0 < t1) return true;
          if (t0 > t1) return false;
          return default;
          }

          int[] extremes(real[] f, bool cyclic = f.cyclic) {
          bool3 lastIncreasing;
          bool3 nextIncreasing;
          int max;
          if (cyclic) {
          lastIncreasing = increasing(f[-1], f[0]);
          max = f.length - 1;
          } else {
          max = f.length - 2;
          if (increasing(f[0], f[1])) lastIncreasing = false;
          else lastIncreasing = true;
          }
          int[] toreturn;
          for (int i = 0; i <= max; ++i) {
          nextIncreasing = increasing(f[i], f[i+1]);
          if (lastIncreasing != nextIncreasing) {
          toreturn.push(i);
          }
          lastIncreasing = nextIncreasing;
          }
          if (!cyclic) toreturn.push(f.length - 1);
          toreturn.cyclic = cyclic;
          return toreturn;
          }

          int[] extremes(path path, real f(pair) = new real(pair P) {return P.x;})
          {
          real[] fvalues = new real[size(path)];
          for (int i = 0; i < fvalues.length; ++i) {
          fvalues[i] = f(point(path, i));
          }
          fvalues.cyclic = cyclic(path);
          int[] toreturn = extremes(fvalues);
          fvalues.delete();
          return toreturn;
          }

          path[] splitAtExtremes(path path, real f(pair) = new real(pair P) {return P.x;})
          {
          int[] splittingTimes = extremes(path, f);
          path[] toreturn;
          if (cyclic(path)) toreturn.push(subpath(path, splittingTimes[-1], splittingTimes[0]));
          for (int i = 0; i+1 < splittingTimes.length; ++i) {
          toreturn.push(subpath(path, splittingTimes[i], splittingTimes[i+1]));
          }
          return toreturn;
          }

          path[] splitAtExtremes(path[] paths, real f(pair P) = new real(pair P) {return P.x;})
          {
          path[] toreturn;
          for (path path : paths) {
          toreturn.append(splitAtExtremes(path, f));
          }
          return toreturn;
          }

          path3 toCamera(triple p, projection P=currentprojection, real fuzz = .01, real upperLimit = 100) {
          if (!P.infinity) {
          triple directionToCamera = unit(P.camera - p);
          triple startingPoint = p + fuzz*directionToCamera;
          return startingPoint -- P.camera;
          }
          else {
          triple directionToCamera = unit(P.camera);
          triple startingPoint = p + fuzz*directionToCamera;

          return startingPoint -- (p + upperLimit*directionToCamera);
          }
          }

          int numSheetsHiding(surface s, pair parampt, projection P = currentprojection) {
          triple p = s.point(parampt.x, parampt.y);
          path3 tocamera = toCamera(p, P);
          triple pt = beginpoint(tocamera);
          triple dir = endpoint(tocamera) - pt;
          return numPointsInDirection(s, parampt, dir);
          }

          struct coloredPath {
          path path;
          pen pen;
          void operator init(path path, pen p=currentpen) {
          this.path = path;
          this.pen = p;
          }
          /* draws the path with the pen having the specified weight (using colors)*/
          void draw(real weight) {
          draw(path, p=weight*pen + (1-weight)*white);
          }
          }
          coloredPath[][] layeredPaths;
          // onTop indicates whether the path should be added at the top or bottom of the specified layer
          void addPath(path path, pen p=currentpen, int layer, bool onTop=true) {
          coloredPath toAdd = coloredPath(path, p);
          if (layer >= layeredPaths.length) {
          layeredPaths[layer] = new coloredPath[] {toAdd};
          } else if (onTop) {
          layeredPaths[layer].push(toAdd);
          } else layeredPaths[layer].insert(0, toAdd);
          }

          void drawLayeredPaths() {
          for (int layer = layeredPaths.length - 1; layer >= 0; --layer) {
          real layerfactor = (1/3)^layer;
          for (coloredPath toDraw : layeredPaths[layer]) {
          toDraw.draw(layerfactor);
          }
          }
          layeredPaths.delete();
          }

          real[] cutTimes(path tocut, path[] knives) {
          real[] intersectionTimes = new real[] {0, length(tocut)};
          for (path knife : knives) {
          real[][] complexIntersections = intersections(tocut, knife);
          for (real[] times : complexIntersections) {
          intersectionTimes.push(times[0]);
          }
          }
          return sort(intersectionTimes);
          }

          path[] cut(path tocut, path[] knives) {
          real[] cutTimes = cutTimes(tocut, knives);
          path[] toreturn;
          for (int i = 0; i + 1 < cutTimes.length; ++i) {
          toreturn.push(subpath(tocut,cutTimes[i], cutTimes[i+1]));
          }
          return toreturn;
          }

          real[] condense(real[] values, real fuzz=.001) {
          values = sort(values);
          values.push(infinity);
          real previous = -infinity;
          real lastMin;
          real[] toReturn;
          for (real t : values) {
          if (t - fuzz > previous) {
          if (previous > -infinity) toReturn.push((lastMin + previous) / 2);
          lastMin = t;
          }
          previous = t;
          }
          return toReturn;
          }

          /*
          * A smooth surface parametrized by the domain [0,1] x [0,1]
          */
          struct SmoothSurface {
          surface s;
          private real sumax;
          private real svmax;
          path[] paramSilhouette;
          path[] projectedSilhouette;
          projection theProjection;

          triple camera;

          path3 onSurface(path paramPath) {
          return onSurface(s, scale(sumax,svmax)*paramPath);
          }

          triple point(real u, real v) { return s.point(sumax*u, svmax*v); }
          triple point(pair uv) { return point(uv.x, uv.y); }
          triple normal(real u, real v) { return s.normal(sumax*u, svmax*v); }
          triple normal(pair uv) { return normal(uv.x, uv.y); }

          triple[] derivative(real u, real v) { return derivative(s, (u/sumax, v/svmax)); }
          triple[] derivative(pair uv) { return derivative(uv.x, uv.y); }

          pen color(real u, real v, material m, light light) {
          return color(normal=normal(u,v), m=m, light=light);
          }
          pen color(pair uv, material m, light light) {
          return this.color(uv.x, uv.y, m, light);
          }
          pair project(triple pt) { return project(pt, theProjection); }

          void operator init(surface s, projection P=currentprojection) {
          this.s = s;
          this.sumax = umax(s);
          this.svmax = vmax(s);
          this.theProjection = P;
          this.paramSilhouette = scale(1/sumax, 1/svmax) * paramSilhouetteNoEdges(s,P);
          this.projectedSilhouette = sequence(new path(int i) {
          path3 truePath = onSurface(paramSilhouette[i]);
          path projectedPath = project(truePath, theProjection, ninterpolate=1);
          return projectedPath;
          }, paramSilhouette.length);
          camera=theProjection.camera;
          if(theProjection.infinity) {
          triple m=min(s);
          triple M=max(s);
          camera = theProjection.target
          + camerafactor*(abs(M-m)+abs(m-theProjection.target))*unit(theProjection.vector());
          }

          }

          int numSheetsHiding(pair parampt) {
          return numSheetsHiding(s, scale(sumax,svmax)*parampt);
          }
          int numSheetsHiding(real u, real v) {
          return numSheetsHiding((u,v));
          }

          void drawSilhouette(pen p=currentpen,
          bool includePathsBehind=false,
          bool onTop = true)
          {
          int[][] extremes;
          for (path path : projectedSilhouette) {
          extremes.push(extremes(path));
          }

          path[] splitSilhouette;
          path[] paramSplitSilhouette;

          /*
          * First, split at extremes to ensure that there are no
          * self-intersections of any one subpath in the projected silhouette.
          */

          for (int j = 0; j < paramSilhouette.length; ++j) {
          path current = projectedSilhouette[j];

          path currentParam = paramSilhouette[j];

          int[] dividers = extremes[j];
          for (int i = 0; i + 1 < dividers.length; ++i) {
          int start = dividers[i];
          int end = dividers[i+1];
          splitSilhouette.push(subpath(current,start,end));
          paramSplitSilhouette.push(subpath(currentParam, start, end));
          }
          }

          /*
          * Now, split at intersections of distinct subpaths.
          */

          for (int j = 0; j < splitSilhouette.length; ++j) {
          path current = splitSilhouette[j];
          path currentParam = paramSplitSilhouette[j];

          real[] splittingTimes = new real[] {0,length(current)};
          for (int k = 0; k < splitSilhouette.length; ++k) {
          if (j == k) continue;
          real[][] times = intersections(current, splitSilhouette[k]);
          for (real[] time : times) {
          real relevantTime = time[0];
          if (.01 < relevantTime && relevantTime < length(current) - .01) splittingTimes.push(relevantTime);
          }
          }
          splittingTimes = sort(splittingTimes);
          for (int i = 0; i + 1 < splittingTimes.length; ++i) {
          real start = splittingTimes[i];
          real end = splittingTimes[i+1];
          real mid = start + ((end-start) / (2+0.1*unitrand()));
          pair theparampoint = point(currentParam, mid);
          int sheets = numSheetsHiding(theparampoint);

          if (sheets == 0 || includePathsBehind) {
          path currentSubpath = subpath(current, start, end);
          addPath(currentSubpath, p=p, onTop=onTop, layer=sheets);
          }

          }
          }
          }

          /*
          Splits a parametrized path along the parametrized silhouette,
          taking [0,1] x [0,1] as the
          fundamental domain. Could be implemented more efficiently.
          */
          private real[] splitTimes(path thepath) {
          pair min = min(thepath);
          pair max = max(thepath);
          path[] baseknives = paramSilhouette;
          path[] knives;
          for (int u = floor(min.x); u < max.x + .001; ++u) {
          for (int v = floor(min.y); v < max.y + .001; ++v) {
          knives.append(shift(u,v)*baseknives);
          }
          }
          return cutTimes(thepath, knives);
          }

          /*
          Returns the times at which the projection of the given path3 intersects
          the projection of the surface silhouette. This may miss unstable
          intersections that can be detected by the previous method.
          */
          private real[] silhouetteCrossingTimes(path3 thepath, real fuzz = .01) {
          path projectedpath = project(thepath, theProjection, ninterpolate=1);
          real[] crossingTimes = cutTimes(projectedpath, projectedSilhouette);
          if (crossingTimes.length == 0) return crossingTimes;
          real current = 0;
          real[] toReturn = new real[] {0};
          for (real prospective : crossingTimes) {
          if (prospective > current + fuzz
          && prospective < length(thepath) - fuzz) {
          toReturn.push(prospective);
          current = prospective;
          }
          }
          toReturn.push(length(thepath));
          return toReturn;
          }

          path3 drawSurfacePath(path parampath, pen p=currentpen, bool onTop=true) {
          path[] toDraw;
          real[] crossingTimes = splitTimes(parampath);
          crossingTimes.append(silhouetteCrossingTimes(onSurface(parampath)));
          crossingTimes = condense(crossingTimes);
          for (int i = 0; i+1 < crossingTimes.length; ++i) {
          toDraw.push(subpath(parampath, crossingTimes[i], crossingTimes[i+1]));
          }
          for (path thepath : toDraw) {
          pair midpoint = point(thepath, length(thepath) / (2+.1*unitrand()));
          int sheets = numSheetsHiding(midpoint);
          path path3d = project(onSurface(thepath), theProjection, ninterpolate = 1);
          addPath(path3d, p=p, onTop=onTop, layer=sheets);
          }
          return onSurface(parampath);
          }

          }

          SmoothSurface operator *(transform3 t, SmoothSurface s) {
          return SmoothSurface(t*s.s);
          }


          To get the first image (with the two circles), in the same directory, run the following *.asy code:



          settings.outformat="pdf";
          import surfacepaths;

          unitsize(1.5cm);
          triple eye = 4*(0,1,3.5);
          currentprojection=perspective(eye, up=Y);

          surface torus = surface(O, shift(2X)*path3(scale(0.4)*unitcircle), axis=Y);
          SmoothSurface Torus = SmoothSurface(torus);

          Torus.drawSilhouette();
          real diff = .07;
          real midpoint = .75;
          path circle1 = project(Torus.drawSurfacePath((midpoint-diff, 0) -- (midpoint-diff, 1)), ninterpolate=1);
          path circle2 = project(Torus.drawSurfacePath((midpoint+diff,0) -- (midpoint+diff,1)),ninterpolate=1);

          drawLayeredPaths(); // Draw the paths (with hidden path removal) that were computed in the previous code block.

          real circle1mintime = mintimes(circle1)[1]; // the time at which circle1 reaches its minimum vertical extent
          pair p1 = point(circle1, circle1mintime);
          label("$mathcal C$", position=p1, align=SW);

          real circle2mintime = mintimes(circle2)[1];
          pair p2 = point(circle2, circle2mintime);
          label("$mathcal C'$", position=p2, align=SE);

          diff += .01;
          path3 arc = Torus.onSurface((midpoint-diff,.75) -- (midpoint+diff,.75));
          draw(shift(0,-1)*project(arc), arrow=ArcArrow());


          To get the torus wrapped in a helix, run the following Asymptote code:



          settings.outformat="pdf";
          import surfacepaths;

          size(400,0);
          currentprojection=perspective(camera=(0,4,14), up=Y);

          //function to return a line, but split into n parts (for better approximation when the line is transformed to lie on the surface)
          path splitline(pair a, pair b, int n) {
          pair f(real t) { return (1-t)*a + t*b; }
          return graph(f, 0, 1, n=n);
          }

          surface torus = surface(O, Circle(c=2X,r=0.4,normal=Z,n=32), axis=Y, n=32);
          /* Let Asymptote know (in case it did not already) that the u and v
          * coordinates of this surface are both cyclic.
          */
          torus.ucyclic(true);
          torus.vcyclic(true);
          SmoothSurface Torus = SmoothSurface(torus);

          Torus.drawSilhouette(includePathsBehind=true);
          Torus.drawSurfacePath(splitline((0,0), (1,20), n=100), p=gray);

          drawLayeredPaths();




          Old (rasterized-only) solution:



          Here's an Asymptote solution that sort of works.
          enter image description here



          Note that as currently formulated, it has the following difficulties:




          1. The torus must be opaque.

          2. Anything that should be partially obscured by the torus has to be given in rasterized output (although I've got a scaling factor included to give high resolution). The rest of the image (i.e., the labels and the arrow) is vector output.

          3. It only works with an orthographic projection.


          Anyway, here's the code:



          import graph3;
          import contour;

          // A bunch of auxiliary functions for drawing silhouettes.

          real fuzz = .001;

          real umin(surface s) { return 0; }
          real vmin(surface s) { return 0; }
          pair uvmin(surface s) { return (umin(s), vmin(s)); }
          real umax(surface s, real fuzz=fuzz) {
          if (s.ucyclic()) return s.index.length;
          else return s.index.length - fuzz;
          }
          real vmax(surface s, real fuzz=fuzz) {
          if (s.vcyclic()) return s.index[0].length;
          return s.index[0].length - fuzz;
          }
          pair uvmax(surface s, real fuzz=fuzz) { return (umax(s,fuzz), vmax(s,fuzz)); }

          typedef real function(real, real);

          function normalDot(surface s, triple eyedir) {
          real toreturn(real u, real v) {
          return dot(s.normal(u, v), eyedir);
          }
          return toreturn;
          }

          guide[] normalpathuv(surface s, triple eyedir) {
          return contour(normalDot(s, eyedir), uvmin(s), uvmax(s), new real[] {0})[0];
          }

          path3 onSurface(surface s, path p) {
          triple f(real t) {
          pair point = point(p,t);
          return s.point(point.x, point.y);
          }
          if (cyclic(p)) {
          guide3 toreturn = f(0);
          for (int i = 1; i < size(p); ++i)
          toreturn = toreturn -- f(i);
          toreturn = toreturn -- cycle;
          return toreturn;
          }
          return graph(f, 0, length(p));
          }

          /*
          * This method returns an array of paths that trace out all the
          * points on s at which s is normal to eyedir.
          */
          path3[] silhouetteNormal(surface s, triple eyedir) {
          guide[] uvpaths = normalpathuv(s, eyedir);
          path3[] toreturn = new path3[uvpaths.length];
          for (int i = 0; i < uvpaths.length; ++i) {
          toreturn[i] = onSurface(s, uvpaths[i]);
          }
          return toreturn;
          }



          /*
          * Now, let's actually draw the picture.
          */

          settings.outformat="pdf";
          int resolutionfactor = 4;
          settings.render=2*resolutionfactor;
          settings.prc=false;

          unitsize(1.5cm);
          triple eye = (0,1,3);
          currentprojection=orthographic(eye, up=Y);

          surface torus = surface(O, shift(2X)*path3(scale(0.5)*unitcircle), axis=Y);

          draw(silhouetteNormal(torus, eye));
          draw(torus, surfacepen=emissive(white));

          real umin = umin(torus);
          real umax = umax(torus);
          real diff = 0.07;
          path3 circle1 = torus.uequals((.75-diff)*umax);
          draw(circle1);
          pair p1 = project(point(circle1, 3));
          label("$mathcal C$", position=p1, align=2SW);

          path3 circle2 = torus.uequals((.75+diff)*umax);
          draw(circle2);
          pair p2 = project(point(circle2,3));
          label("$mathcal C'$", position=p2, align=2SE);

          pair midpoint = (p1 + p2)/2 + (0,-.1);
          path arc = p1 .. midpoint .. p2;

          draw(shift(0,-1)*arc, arrow=ArcArrow());

          shipout(scale(resolutionfactor)*currentpicture.fit());






          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Jan 7 '14 at 23:39

























          answered Sep 28 '13 at 5:18









          Charles StaatsCharles Staats

          13.5k553115




          13.5k553115























              6














              enter image description here



              Not sure, if this trick can help:



              import solids;
              settings.render=0;
              settings.prc=true;
              size(4cm,0);

              currentprojection=orthographic(0,20,10);
              currentlight=nolight;

              real R=3, a=0.8, d=R+2a;

              void drawTorus(transform3 t=zscale3(1),int n=4){
              revolution torus=t*revolution(shift(R*X)*Circle(O,a,Y,32),Z,0,360);
              draw(torus.surface(n),black+opacity(0.8));
              revolution torus=t*revolution(shift(R*X)*Circle(O,a*0.9,Y,32),Z,0,360);
              draw(torus.surface(n),white);
              }

              drawTorus();
              drawTorus(shift(0,0,-5)*rotate(-20,(0,0,0),(0,1,0)));
              drawTorus(shift(0,0,-10)*rotate(45,(0,0,0),(0,1,0)));





              share|improve this answer
























              • It's much better than the original, thanks ! However, the silhouette lacks the small portions of the curve in front that continue a little further in the sides, like in i.stack.imgur.com/aFVv7.png and help the reader to understand the 3d ... I believe it is possible to get them, as blender+freestyle manage to do it, but I still do not see how with a latex tool. Another problem of your trick is that it probably won't go nicely with e.g. curves on the torus ... but thanks anyway.

                – Georg Sievelson
                Jun 17 '13 at 17:01
















              6














              enter image description here



              Not sure, if this trick can help:



              import solids;
              settings.render=0;
              settings.prc=true;
              size(4cm,0);

              currentprojection=orthographic(0,20,10);
              currentlight=nolight;

              real R=3, a=0.8, d=R+2a;

              void drawTorus(transform3 t=zscale3(1),int n=4){
              revolution torus=t*revolution(shift(R*X)*Circle(O,a,Y,32),Z,0,360);
              draw(torus.surface(n),black+opacity(0.8));
              revolution torus=t*revolution(shift(R*X)*Circle(O,a*0.9,Y,32),Z,0,360);
              draw(torus.surface(n),white);
              }

              drawTorus();
              drawTorus(shift(0,0,-5)*rotate(-20,(0,0,0),(0,1,0)));
              drawTorus(shift(0,0,-10)*rotate(45,(0,0,0),(0,1,0)));





              share|improve this answer
























              • It's much better than the original, thanks ! However, the silhouette lacks the small portions of the curve in front that continue a little further in the sides, like in i.stack.imgur.com/aFVv7.png and help the reader to understand the 3d ... I believe it is possible to get them, as blender+freestyle manage to do it, but I still do not see how with a latex tool. Another problem of your trick is that it probably won't go nicely with e.g. curves on the torus ... but thanks anyway.

                – Georg Sievelson
                Jun 17 '13 at 17:01














              6












              6








              6







              enter image description here



              Not sure, if this trick can help:



              import solids;
              settings.render=0;
              settings.prc=true;
              size(4cm,0);

              currentprojection=orthographic(0,20,10);
              currentlight=nolight;

              real R=3, a=0.8, d=R+2a;

              void drawTorus(transform3 t=zscale3(1),int n=4){
              revolution torus=t*revolution(shift(R*X)*Circle(O,a,Y,32),Z,0,360);
              draw(torus.surface(n),black+opacity(0.8));
              revolution torus=t*revolution(shift(R*X)*Circle(O,a*0.9,Y,32),Z,0,360);
              draw(torus.surface(n),white);
              }

              drawTorus();
              drawTorus(shift(0,0,-5)*rotate(-20,(0,0,0),(0,1,0)));
              drawTorus(shift(0,0,-10)*rotate(45,(0,0,0),(0,1,0)));





              share|improve this answer













              enter image description here



              Not sure, if this trick can help:



              import solids;
              settings.render=0;
              settings.prc=true;
              size(4cm,0);

              currentprojection=orthographic(0,20,10);
              currentlight=nolight;

              real R=3, a=0.8, d=R+2a;

              void drawTorus(transform3 t=zscale3(1),int n=4){
              revolution torus=t*revolution(shift(R*X)*Circle(O,a,Y,32),Z,0,360);
              draw(torus.surface(n),black+opacity(0.8));
              revolution torus=t*revolution(shift(R*X)*Circle(O,a*0.9,Y,32),Z,0,360);
              draw(torus.surface(n),white);
              }

              drawTorus();
              drawTorus(shift(0,0,-5)*rotate(-20,(0,0,0),(0,1,0)));
              drawTorus(shift(0,0,-10)*rotate(45,(0,0,0),(0,1,0)));






              share|improve this answer












              share|improve this answer



              share|improve this answer










              answered Jun 16 '13 at 18:25









              g.kovg.kov

              17.5k14178




              17.5k14178













              • It's much better than the original, thanks ! However, the silhouette lacks the small portions of the curve in front that continue a little further in the sides, like in i.stack.imgur.com/aFVv7.png and help the reader to understand the 3d ... I believe it is possible to get them, as blender+freestyle manage to do it, but I still do not see how with a latex tool. Another problem of your trick is that it probably won't go nicely with e.g. curves on the torus ... but thanks anyway.

                – Georg Sievelson
                Jun 17 '13 at 17:01



















              • It's much better than the original, thanks ! However, the silhouette lacks the small portions of the curve in front that continue a little further in the sides, like in i.stack.imgur.com/aFVv7.png and help the reader to understand the 3d ... I believe it is possible to get them, as blender+freestyle manage to do it, but I still do not see how with a latex tool. Another problem of your trick is that it probably won't go nicely with e.g. curves on the torus ... but thanks anyway.

                – Georg Sievelson
                Jun 17 '13 at 17:01

















              It's much better than the original, thanks ! However, the silhouette lacks the small portions of the curve in front that continue a little further in the sides, like in i.stack.imgur.com/aFVv7.png and help the reader to understand the 3d ... I believe it is possible to get them, as blender+freestyle manage to do it, but I still do not see how with a latex tool. Another problem of your trick is that it probably won't go nicely with e.g. curves on the torus ... but thanks anyway.

              – Georg Sievelson
              Jun 17 '13 at 17:01





              It's much better than the original, thanks ! However, the silhouette lacks the small portions of the curve in front that continue a little further in the sides, like in i.stack.imgur.com/aFVv7.png and help the reader to understand the 3d ... I believe it is possible to get them, as blender+freestyle manage to do it, but I still do not see how with a latex tool. Another problem of your trick is that it probably won't go nicely with e.g. curves on the torus ... but thanks anyway.

              – Georg Sievelson
              Jun 17 '13 at 17:01











              0














              In this answer I use pic{torus} that I have defined in this other answer and complete it with pic{circle on torus} code. The code is not very solid, in a sens that the "invisible" part of the drawn circle do not work very well for all possible angles.



              documentclass[tikz,border=7pt]{standalone}
              usetikzlibrary{calc,3d}
              tikzset{
              % ------ define the torus pic
              pics/torus/.style n args={3}{
              code = {
              begin{scope}
              edefshorten{thedimexpr-.5pgflinewidthrelax}
              providecolor{pgffillcolor}{rgb}{1,1,1}
              tikzstyle{torus} = [
              line join=round,draw,line width/.expanded={thedimexpr2pgflinewidth+#2*2relax},
              postaction={draw=pgffillcolor,line width={#2*2},shorten <=shorten,shorten >=shorten},
              yscale=cos(#3)
              ]
              draw[torus] circle(#1);
              draw[torus] (180:#1) arc (180:360:#1);
              end{scope}
              }
              },
              % ------ define the circle pic
              pics/circle on torus/.style n args={4}{
              code = {
              begin{scope}[
              plane x={({cos(#4)},{-cos(#3)*sin(#4)})},
              plane y={(0,{sin(#3)})},
              canvas is plane]
              draw[xshift=#1, rotate=sign(-#4)*#3] (0:#2) arc(0:180:#2);
              draw[xshift=#1, rotate=sign(-#4)*#3-180, densely dotted,draw opacity=.5] (0:#2) arc(0:180:#2);
              end{scope}
              }
              }
              }
              begin{document}
              defR{1cm} % <-- the big radius
              defr{2.8mm} % <-- the small radius
              defa{70} % <-- the view perspective angle
              begin{tikzpicture}[rotate=30,transform shape]
              % draw the torus
              pic[fill=yellow!20]{torus={R}{r}{a}};
              % draw the circles
              pic[red]{circle on torus={R}{r}{a}{45}};
              pic[blue]{circle on torus={R}{r}{a}{110}};
              pic[violet]{circle on torus={R}{r}{a}{-70}};
              end{tikzpicture}
              end{document}


              enter image description here






              share|improve this answer




























                0














                In this answer I use pic{torus} that I have defined in this other answer and complete it with pic{circle on torus} code. The code is not very solid, in a sens that the "invisible" part of the drawn circle do not work very well for all possible angles.



                documentclass[tikz,border=7pt]{standalone}
                usetikzlibrary{calc,3d}
                tikzset{
                % ------ define the torus pic
                pics/torus/.style n args={3}{
                code = {
                begin{scope}
                edefshorten{thedimexpr-.5pgflinewidthrelax}
                providecolor{pgffillcolor}{rgb}{1,1,1}
                tikzstyle{torus} = [
                line join=round,draw,line width/.expanded={thedimexpr2pgflinewidth+#2*2relax},
                postaction={draw=pgffillcolor,line width={#2*2},shorten <=shorten,shorten >=shorten},
                yscale=cos(#3)
                ]
                draw[torus] circle(#1);
                draw[torus] (180:#1) arc (180:360:#1);
                end{scope}
                }
                },
                % ------ define the circle pic
                pics/circle on torus/.style n args={4}{
                code = {
                begin{scope}[
                plane x={({cos(#4)},{-cos(#3)*sin(#4)})},
                plane y={(0,{sin(#3)})},
                canvas is plane]
                draw[xshift=#1, rotate=sign(-#4)*#3] (0:#2) arc(0:180:#2);
                draw[xshift=#1, rotate=sign(-#4)*#3-180, densely dotted,draw opacity=.5] (0:#2) arc(0:180:#2);
                end{scope}
                }
                }
                }
                begin{document}
                defR{1cm} % <-- the big radius
                defr{2.8mm} % <-- the small radius
                defa{70} % <-- the view perspective angle
                begin{tikzpicture}[rotate=30,transform shape]
                % draw the torus
                pic[fill=yellow!20]{torus={R}{r}{a}};
                % draw the circles
                pic[red]{circle on torus={R}{r}{a}{45}};
                pic[blue]{circle on torus={R}{r}{a}{110}};
                pic[violet]{circle on torus={R}{r}{a}{-70}};
                end{tikzpicture}
                end{document}


                enter image description here






                share|improve this answer


























                  0












                  0








                  0







                  In this answer I use pic{torus} that I have defined in this other answer and complete it with pic{circle on torus} code. The code is not very solid, in a sens that the "invisible" part of the drawn circle do not work very well for all possible angles.



                  documentclass[tikz,border=7pt]{standalone}
                  usetikzlibrary{calc,3d}
                  tikzset{
                  % ------ define the torus pic
                  pics/torus/.style n args={3}{
                  code = {
                  begin{scope}
                  edefshorten{thedimexpr-.5pgflinewidthrelax}
                  providecolor{pgffillcolor}{rgb}{1,1,1}
                  tikzstyle{torus} = [
                  line join=round,draw,line width/.expanded={thedimexpr2pgflinewidth+#2*2relax},
                  postaction={draw=pgffillcolor,line width={#2*2},shorten <=shorten,shorten >=shorten},
                  yscale=cos(#3)
                  ]
                  draw[torus] circle(#1);
                  draw[torus] (180:#1) arc (180:360:#1);
                  end{scope}
                  }
                  },
                  % ------ define the circle pic
                  pics/circle on torus/.style n args={4}{
                  code = {
                  begin{scope}[
                  plane x={({cos(#4)},{-cos(#3)*sin(#4)})},
                  plane y={(0,{sin(#3)})},
                  canvas is plane]
                  draw[xshift=#1, rotate=sign(-#4)*#3] (0:#2) arc(0:180:#2);
                  draw[xshift=#1, rotate=sign(-#4)*#3-180, densely dotted,draw opacity=.5] (0:#2) arc(0:180:#2);
                  end{scope}
                  }
                  }
                  }
                  begin{document}
                  defR{1cm} % <-- the big radius
                  defr{2.8mm} % <-- the small radius
                  defa{70} % <-- the view perspective angle
                  begin{tikzpicture}[rotate=30,transform shape]
                  % draw the torus
                  pic[fill=yellow!20]{torus={R}{r}{a}};
                  % draw the circles
                  pic[red]{circle on torus={R}{r}{a}{45}};
                  pic[blue]{circle on torus={R}{r}{a}{110}};
                  pic[violet]{circle on torus={R}{r}{a}{-70}};
                  end{tikzpicture}
                  end{document}


                  enter image description here






                  share|improve this answer













                  In this answer I use pic{torus} that I have defined in this other answer and complete it with pic{circle on torus} code. The code is not very solid, in a sens that the "invisible" part of the drawn circle do not work very well for all possible angles.



                  documentclass[tikz,border=7pt]{standalone}
                  usetikzlibrary{calc,3d}
                  tikzset{
                  % ------ define the torus pic
                  pics/torus/.style n args={3}{
                  code = {
                  begin{scope}
                  edefshorten{thedimexpr-.5pgflinewidthrelax}
                  providecolor{pgffillcolor}{rgb}{1,1,1}
                  tikzstyle{torus} = [
                  line join=round,draw,line width/.expanded={thedimexpr2pgflinewidth+#2*2relax},
                  postaction={draw=pgffillcolor,line width={#2*2},shorten <=shorten,shorten >=shorten},
                  yscale=cos(#3)
                  ]
                  draw[torus] circle(#1);
                  draw[torus] (180:#1) arc (180:360:#1);
                  end{scope}
                  }
                  },
                  % ------ define the circle pic
                  pics/circle on torus/.style n args={4}{
                  code = {
                  begin{scope}[
                  plane x={({cos(#4)},{-cos(#3)*sin(#4)})},
                  plane y={(0,{sin(#3)})},
                  canvas is plane]
                  draw[xshift=#1, rotate=sign(-#4)*#3] (0:#2) arc(0:180:#2);
                  draw[xshift=#1, rotate=sign(-#4)*#3-180, densely dotted,draw opacity=.5] (0:#2) arc(0:180:#2);
                  end{scope}
                  }
                  }
                  }
                  begin{document}
                  defR{1cm} % <-- the big radius
                  defr{2.8mm} % <-- the small radius
                  defa{70} % <-- the view perspective angle
                  begin{tikzpicture}[rotate=30,transform shape]
                  % draw the torus
                  pic[fill=yellow!20]{torus={R}{r}{a}};
                  % draw the circles
                  pic[red]{circle on torus={R}{r}{a}{45}};
                  pic[blue]{circle on torus={R}{r}{a}{110}};
                  pic[violet]{circle on torus={R}{r}{a}{-70}};
                  end{tikzpicture}
                  end{document}


                  enter image description here







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered 23 mins ago









                  KpymKpym

                  17.3k24090




                  17.3k24090






























                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to TeX - LaTeX Stack Exchange!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f119386%2fdrawing-the-contour-of-a-3d-torus%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      IEEEtran - How to include ORCID in TeX/PDF with PdfLatexIs there a standard way to include ORCID in TeX /...

                      Cicindela nigrior Przypisy | Menu nawigacyjneCicindela varians unicolorManual for the Identification of the...

                      Glossaries-extra: Adding glossaries package to “Clas­sicTh­e­sis” template by Dr. André Miede v. 4.6 ...