Does anyone know how to use Asymptote to draw a little wedge on a torus?

This is what I have so far, but I'd like to be able to somehow shade in my wedge-y cube. At the moment, however, my cube is made up of lots of segments, so I don't really know how to do this.

This is my code so far. You'll notice that the first part of it (the part that actually looks nice!) is rather shamelessly taken directly from the second answer here. I'd prefer the code to be in Asymptote, not in tikz, but I'm flexible.

Also, I don't really care exactly how the wedge looks, so if it's more convenient to make it a conical shape or a slightly different cuboid (or even centered on a different point on the torus), that's fine with me. The only thing is that I would like the wedge to end somewhere inside the torus, i.e., I don't want a slice of the torus.

Thanks so much!

settings.outformat = "pdf"; 
settings.prc = false; 
settings.render = 0; 

import graph3; 

currentprojection = orthographic(10,1,4);
defaultrender = render(merge = true);

int umax, vmax; 
umax = 40; 
vmax = 40; 

surface torus = surface(Circle(c=2Y, r=0.6, normal=X, n=vmax), c=O, axis=Z, n=umax);

pen meshpen = 0.3pt+black;

draw(torus, surfacepen=material(diffusepen=blue+opacity(0.3), emissivepen=white));
for (int u = 0; u < umax; ++u) {
    real op; 
    if (u <= 0 || u >= umax/2.0) {
        op = 0.5; 
    } else {
        if (u == 1 || u >= umax/2.0-1) {
            op = 0.35; 
        } else {
            op = 0.2; 
    draw(torus.uequals(u), p=meshpen+opacity(op));
for (int v = 0; v < vmax; ++v) {
    draw(graph(new triple(real u) {return torus.point(u,v); }, 0, umax, operator ..),p=meshpen+opacity(0.2));

int pos = floor(3*umax/4)-2;
pair p = (pos, 3);
dot(torus.point(p.x, p.y));

path3 toruspath(pair pt1, pair pt2, int ucycles, int vcycles) {
  pair pt2shift = (ucycles*umax, vcycles*vmax);
  triple f(real t) {
    pair uv = (1-t)*pt1 + t*(pt2+pt2shift);
    return torus.point(uv.x, uv.y);
  return graph(f, 0, 1, operator ..);

triple scaletriple(triple t, real scalefactor) {
    return (scalefactor*t.x,scalefactor*t.y,scalefactor*t.z); 

path3 scalepath(path3 initpath, real scalefactor) {
    triple f(real t) {
        return scaletriple(arcpoint(initpath,t),scalefactor); 
    return graph(f, 0, 1, operator ..); 

pair w,x,y,z; 
w = (pos-0.5,1); 
x = (pos+0.5,1); 
y = (pos+0.5,5); 
z = (pos-0.5,5); 
// draw(torus.point(w.x,w.y)--torus.point(x.x,x.y)--torus.point(y.x,y.y)--torus.point(z.x,z.y)--cycle); 

path3 sides[] = {toruspath(w,x,0,0), toruspath(x,y,0,0), toruspath(y,z,0,0), toruspath(z,w,0,0)};

for (path3 side : sides) {

pair points[] = {w,x,y,z}; 
for (pair pt : points) {

torus wedge


Since you've said "I'm flexible", I use TikZ to draw the torus and the small "cube" in it with one of its faces lying on the torus. I think that the vertices of this face must be points appearing in the drawing process of the torus. Consequently, the torus is drawn by using a quadrilateral mesh. The points of the mesh are constructed from a classical parametrization.

Below are some explanations about the code. The first two items are classical and well known. Please note that we need the three components of the 3D points and vectors for various computations. Since we cannot recuperate them from a TikZ coordinate definition, they are computed (too) many times; the code could be improved.

  1. The observer's point of view is defined by the unitary vector w that points towards the observer. Its components are \tox, \toy, and \toz, where
    \tox = x_w = sin\longit cos\latit
    \toy = y_w = sin\latit
    \toz = z_w = cos\longit cos\latit.
    The angles \longit and \latit represent the longitude and the latitude, respectively.
  1. The screen (plane on which the image is drawn) is the plane passing through the origin and orthogonal to w. The orthonormal basis that induces the coordinate system of the screen is (u, v, w), where
    u = ( cos\longit, 0 , - sin\longit)
    v = (- sin\longit sin\latit, cos\latit, - cos\longit sin\latit)

Note that the initial coordinate system is Oxyz, such that, when \longit=\latit=0, Oz is horizontal and is perceived by the observer as a point, and Ox is horizontal and points to the right for the observer. Consequently, u is parallel to Oxz; in particular \latit must be different from a right angle.

The points (1,0,0), (0,1,0), and (0,0,1) project onto points described in the global options of the drawing by x={(\newxx cm, \newxy cm)}, etc, where, for example, \newxx = <(1,0,0), u>, \newxy = <(1,0,0), v>.

I lingered on all these since drawing the 3D object depends on the observer's position vector, w.

  1. We see the torus as the surface of revolution around the Oy axis of a circle of radius \rz in the plane Oxy. The distance from the center of the circle to Oy is \ry. So, our mesh is defined by the points (P-\j-\k); as 3D points, their coordinates are as usual (see the code). I just want to point out that the y-coordinate has a minus sign,
    -\rz sin(360(\k/\Nz)), with 0<=\k<=\Nz.
    It is there since the quadrilaterals along a longitude cycle are to be considered clockwise (starting at 3 o'clock). This choice is the correct one leading to a good 3D image of the torus when w belongs to the first quadrant.

For \j fixed, the points describe a longitude cycle (a circle of radius \rz); for \k fixed, the points describe a latitude cycle. In particular, the longest latitude cycle (the intersection of the torus with the Ozx plane) is obtained for \k=0.

  1. The quadrilaterals of the mesh that are drawn are given by the function isSeen which returns 1 if the inner product of the position vector corresponding to (P-\j-\k) with w is positive.
\usetikzlibrary{cd, arrows, matrix, intersections, math, calc}

  real \ry, \rz, \longit, \latit, \tox, \toy, \toz;
  real \newxx, \newxy, \newyx, \newyy, \newzx, \newzy;  
  integer \Ny, \Nz, \prevj, \prevk;
  % \j moves around Oy and \k moves around Oz.
  % They must describe full circles of radii \ry and \rz respectively.
  \ry = 4;
  \rz = 1.5;
  \longit = 24;
  \latit = 35;
  \tox = sin(\longit)*cos(\latit);
  \toy = sin(\latit);
  \toz = cos(\longit)*cos(\latit);
  \newxx = cos(\longit); \newxy = -sin(\longit)*sin(\latit);
  \newyy = cos(\latit);
  \newzx = -sin(\longit); \newzy = -cos(\longit)*sin(\latit);
  \Nz = 36;
  \Ny = 84;
  \ktmp = \Nz-1; 
  \jtmp = \Ny-1;
  function isSeen(\j, \k) {
    let \px = cos(360*(\k/\Nz))*cos(360*(\j/\Ny));
    let \py = -sin(360*(\k/\Nz));
    let \pz = cos(360*(\k/\Nz))*sin(360*(\j/\Ny));
    let \res = \px*\tox + \py*\toy + \pz*\toz;
    if \res>0 then {return 1;} else {return 0;};
\begin{tikzpicture}[every node/.style={scale=.8},
  x={(\newxx cm, \newxy cm)},
  y={(0 cm, \newyy cm)},
  z={(\newzx cm, \newzy cm)},
    int \j, \k;
    for \j in {0, 1, ..., \Ny}{%   \Ny = 84
      for \k in {0, 1, ..., \Nz}{%  \Nz = 36
        \test{\j,\k} = isSeen(\j, \k);

  % coordinate system $Oxyz$; first layer
  % must be drawn in two steps (there are 2 objects in the final figure)
  (0, 0, 0) -- (\ry, 0, 0)
  % (0, 0, 0) -- (0, \ry+\rz, 0)
  (0, 0, 0) -- (0, 0, \ry);

  % points (P-\j-\k)
  % The minus sign for the y component is due to the fact that
  % the points (for a vertical circle) are to be considered 
  % clockwise starting with 3 o'clock.  Of course, it depends on the
  % observer's position, but in case this position is in the first
  % quadrant, this is the good order.
  \foreach \j in {0, ..., \Ny}{%
    \foreach \k in {0, ..., \Nz}{%
      ( {( \ry+\rz*cos(360*(\k/\Nz)) )*cos(360*(\j/\Ny))},
      {( \ry+\rz*cos(360*(\k/\Nz)) )*sin(360*(\j/\Ny))} )
      coordinate (P-\j-\k);

  % "squares"---the mesh
  % first j then k; in this way the upper "latitude bands" are drawn
  % at the end and the torus appears correctly.
  \foreach \k [remember=\k as \prevk (initially 0)] in {1, ..., \Nz}{%
    \foreach \j [remember=\j as \prevj (initially 0)] in {1, ..., \Ny}{%
        \draw[blue!50, very thin, fill=blue!15]
        (P-\j-\prevk) -- (P-\prevj-\prevk)
        -- (P-\prevj-\k) --(P-\j-\k) -- cycle;

  % cube inside the torus with one face on the torus defined by
  % latitude and longitude cycles
      for \j in {0, 1, 2}{ \a{\j} = int(\Ny/4+3+\j); };
      for \k in {0, 1, 2, 3}{ \b{\k} = int(\Nz-3+\k); };
    % face of the "cube"
    \filldraw[blue!25] (P-\a{0}-\b{0})
    \foreach \k in {1, 2, 3}{-- (P-\a{0}-\b{\k})}
    -- (P-\a{1}-\b{3}) -- (P-\a{2}-\b{3})
    \foreach \k in {2, 1, 0}{-- (P-\a{2}-\b{\k})}
    -- (P-\a{1}-\b{0}) -- cycle;    

    % the "cube"'s four other vertices
    \foreach \j in {0, 2}{%
      \foreach \k in {0, 3}{%
        ( {( \ry+.5*\rz*cos(360*(\b{\k}/\Nz)) )*cos(360*(\a{\j}/\Ny))},
        {( \ry+.5*\rz*cos(360*(\b{\k}/\Nz)) )*sin(360*(\a{\j}/\Ny))} )
        coordinate (Q-\j-\k);
    % faces of the cube inside the torus
    \filldraw[blue!80, very thin]
    (P-\a{0}-\b{0}) -- (Q-0-0) -- (Q-0-3) -- (P-\a{0}-\b{3}) -- cycle;
    \filldraw[B!50, very thin]
    (P-\a{0}-\b{0}) -- (Q-0-0) -- (Q-2-0) -- (P-\a{2}-\b{0}) -- cycle;

    % longitude cycles
    \foreach \j in {0, 2}{%
      \foreach \k [remember=\k as \prevk (initially 0)] in {1, ..., \Nz}{
          \draw[red] (P-\a{\j}-\prevk) -- (P-\a{\j}-\k);
    % latitude cycles
    \foreach \k in {0, 3}{%
      \foreach \j [remember=\j as \prevj (initially 0)] in {1, ..., \Ny}{%
          \draw[red] (P-\prevj-\b{\k}) -- (P-\j-\b{\k});
  % coordinate system $Oxyz$; second layer
  \draw[green!50!black, -{Latex[length=5pt, width=5pt]}]
  (\ry+\rz, 0, 0) -- (8, 0, 0) node[right] {$x$};
  \draw[green!50!black, -{Latex[length=5pt, width=5pt]}]
  (0, 0, 0) -- (0, 6, 0) node[above] {$y$};
  \draw[green!50!black, -{Latex[length=5pt, width=5pt]}]
  (0, 0, \ry+\rz) -- (0, 0, 8) node[below left] {$z$};

enter image description here

  • +1, that's beautiful! – AndréC Jul 20 '20 at 18:41
  • Oh, wow, that's amazing—thanks! (And thanks for the detailed explanation, too!) – boink Jul 20 '20 at 23:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.