绘制三维圆环的轮廓

绘制三维圆环的轮廓

我想画一个圆环,就像问题上的圆环一样如何绘制圆环,使用 3d 包。我想避免使用 inkscape(或直接使用 tikz)在 2d 中绘制圆环,因为我需要添加其他元素(例如,在具有隐藏线的 3D 螺旋圆环,并从不同的角度去看待它们。

但是,生成的图形需要打印(黑白等),因此我只想使用轮廓图,而不是像具有隐藏线的 3D 螺旋圆环

最后,我想要一些类似的东西(但是这个是用 tikz 在 2d 中绘制的)

以 2d 形式绘制的轮廓圆环

所以我尝试使用渐近线,

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);

我得到了类似的东西:

渐近线中尝试性的轮廓环面确实出了问题

那么有没有一种方法可以做到这一点而又不产生可怕的错误?

我尝试使用 asymptote 来实现,但其他工具(tikz、sketch、black magic 等)也可以,至少如果它能提供矢量输出。不过,我对直接在 2d 中绘制结果不感兴趣(而且已经在如何绘制圆环)。

提前致谢。

答案1

更新:

这个新解决方案稍长一些,但克服了我在上一个解决方案中列出的所有三个难题(如下)。具体来说,这是一个矢量图形。

由于您表示有兴趣模仿带有隐藏线的 3D 螺旋,因此我也制作了一个示例(无阴影的灰度矢量图形):

要编译其中任何一个,首先将以下代码保存在名为的文件中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);
}

要获取第一张图像(包含两个圆圈),请在同一目录中运行以下*.asy代码:

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());

要使圆环包裹在螺旋中,请运行以下 Asymptote 代码:

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();

旧的(仅光栅化)解决方案:

这是一个可行的渐近线解决方案。
在此处输入图片描述

请注意,按照目前的表述,它有以下困难:

  1. 圆环必须是不透明的。
  2. 任何被圆环部分遮挡的东西都必须在光栅化输出中给出(尽管我已经包含了缩放因子以提供高分辨率)。图像的其余部分(即标签和箭头)是矢量输出。
  3. 它只适用于正交投影。

无论如何,这是代码:

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());

答案2

在此处输入图片描述

不确定这个技巧是否有帮助:

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)));

答案3

在这个答案中我使用了pic{torus}我在另一个答案并用pic{circle on torus}代码完成它。代码不是很可靠,从某种意义上说,绘制的圆的“不可见”部分对于所有可能的角度都不能很好地工作。

\documentclass[tikz,border=7pt]{standalone}
\usetikzlibrary{calc,3d}
\tikzset{
  % ------ define the torus pic
  pics/torus/.style n args={3}{
    code = {
      \begin{scope}
        \edef\shorten{\the\dimexpr-.5\pgflinewidth\relax}
        \providecolor{pgffillcolor}{rgb}{1,1,1}
        \tikzstyle{torus} = [
          line join=round,draw,line width/.expanded={\the\dimexpr2\pgflinewidth+#2*2\relax},
          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}
  \def\R{1cm} % <-- the big radius
  \def\r{2.8mm} % <-- the small radius
  \def\a{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}

在此处输入图片描述

相关内容