用锥形尖端绘制 3D 箭头

用锥形尖端绘制 3D 箭头

我有下图来表示一些电磁场。 电磁场

该图与此图有些相似: 复制源

你可以看到原始的箭头有一些阴影,而我的没有。有没有有效的方法来实现它?我尝试根据这个解决方案制作锥形尖端这里。笔尖很完美,但如果没有阴影,就很难看清箭头指向的位置。

下面是重现我的身材的代码:

\documentclass[tikz,border=3mm]{standalone}

\usetikzlibrary{3d,arrows.meta}

\makeatletter

\pgfkeys{
  /pgf/arrow keys/.cd,
  pitch/.code={%
    \pgfmathsetmacro\pgfarrowpitch{#1}
    \pgfmathsetmacro\pgfarrowsinpitch{abs(sin(\pgfarrowpitch))}
    \pgfmathsetmacro\pgfarrowcospitch{abs(cos(\pgfarrowpitch))}
  },
}

\pgfdeclarearrow{
  name = Cone,
  defaults = {       % inherit from Kite
    length     = +3pt +2,
    width'     = +0pt +0.5,
    line width = +0pt 1 1,
    pitch      = +0, % lie on screen
  },
  cache = false,     % no need cache
  setup code = {},   % so no need setup
  drawing code = {   % but still need math
    % draw the base
    \pgfmathsetmacro\pgfarrowhalfwidth{.5\pgfarrowwidth}
    \pgfmathsetmacro\pgfarrowhalfwidthsin{\pgfarrowhalfwidth*\pgfarrowsinpitch}
    \pgfpathellipse{\pgfpointorigin}{\pgfqpoint{\pgfarrowhalfwidthsin pt}{0pt}}{\pgfqpoint{0pt}{\pgfarrowhalfwidth pt}}
    \pgfusepath{fill}
    % test if the cone part visible
    \pgfmathsetmacro\pgfarrowlengthcos{\pgfarrowlength*\pgfarrowcospitch}
    \pgfmathparse{\pgfarrowlengthcos>\pgfarrowhalfwidthsin}
    \ifnum\pgfmathresult=1
      % it is visible, so draw
      \pgfmathsetmacro\pgfarrowlengthtemp{\pgfarrowhalfwidthsin*\pgfarrowhalfwidthsin/\pgfarrowlengthcos}
      \pgfmathsetmacro\pgfarrowwidthtemp{\pgfarrowhalfwidth/\pgfarrowlengthcos*sqrt(\pgfarrowlengthcos*\pgfarrowlengthcos-\pgfarrowhalfwidthsin*\pgfarrowhalfwidthsin)}
      \pgfpathmoveto{\pgfqpoint{\pgfarrowlengthcos pt}{0pt}}
      \pgfpathlineto{\pgfqpoint{\pgfarrowlengthtemp pt}{ \pgfarrowwidthtemp pt}}
      \pgfpathlineto{\pgfqpoint{\pgfarrowlengthtemp pt}{-\pgfarrowwidthtemp pt}}
      \pgfpathclose
      \pgfusepath{fill}
    \fi
    \pgfpathmoveto{\pgfpointorigin}
  }
}

\begin{document}

\begin{tikzpicture}[z={(0,-0.5)}]
\begin{scope}[canvas is xz plane at y=0,line width=1.25pt,line cap=round]
%\draw (0,0) circle (2cm);
\draw[red!75!black,-{Cone[pitch=0]}](90:2) --++ (0:1cm);
\draw[red!75!black,-{Cone[pitch=36]}](126:2) --++ (36:1cm);
\draw[red!75!black,-{Cone[pitch=72]}](162:2) --++ (72:1cm);
\draw[red!75!black,-{Cone[pitch=108]}](198:2) --++ (108:1cm) node[above left] {$\vec{H}$};
\draw[red!75!black,-{Cone[pitch=144]}](234:2) --++ (144:1cm);
\draw[red!75!black,-{Cone[pitch=180]}](270:2) --++ (180:1cm);
\draw[red!75!black,-{Cone[pitch=216]}](306:2) --++ (216:1cm);
\draw[red!75!black,-{Cone[pitch=252]}](342:2) --++ (252:1cm);
\draw[red!75!black,-{Cone[pitch=288]}](378:2) --++ (288:1cm);
\draw[red!75!black,-{Cone[pitch=324]}](414:2) --++ (324:1cm);
\end{scope}
\draw[-{Cone},line width=1.5pt,line cap=round,blue!75!black] (0,0,0) -- (0,2,0) node[above]{$\vec{J}+\frac{\partial \vec{D}}{\partial t}$};
\end{tikzpicture}

\end{document}

答案1

在此处输入图片描述

代码很长,因为我将光中的一般锥体定义为对象pic。它取决于八个参数:底面和顶面的中心(6 个参数)以及两个相应的半径。

基于此元素,实心箭头也构造为pipc对象。这次只有六个参数(#4始终为“空”)。但有两个键控制箭头的外观,并且可以修改:arrowheadarrow radius。在示例中,它们都针对蓝色箭头进行了修改。

评论 我插入了一些可以与cone物体一起绘制的圆锥体。也许有人会觉得它们有用。

在此处输入图片描述

代码

\documentclass[11pt, margin=15pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{math}
\begin{document}

\tikzset{%
  view/.style 2 args={%  observer longitude and latitude (y upwards)
                      %  Remark. lomg=0 means x=0
    z={({-sin(#1)}, {-cos(#1)*sin(#2)})},
    x={({cos(#1)}, {-sin(#1)*sin(#2)})},
    y={(0, {cos(#2)})},
    evaluate={%
      \tox={sin(#1)*cos(#2)};
      \toy={sin(#2)};
      \toz={cos(#1)*cos(#2)};
    },
    longitude = #1,
    latitude = #2
  },
  sun/.style n args={3}{% longitude, latitude, light contrast in [0, 1]
    sun longitude = #1,
    sun latitude = #2,
    contrast = #3,
    evaluate={%
      real \sunx, \suny, \sunz, \lightC;
      \sunx = sin(\sLongit)*cos(\sLatit);
      \suny = sin(\sLatit);
      \sunz = cos(\sLongit)*cos(\sLatit);
    }
  }
}

\pgfkeys{/tikz/.cd,
  latitude/.store in=\aLatit,  % observer's latitude
  latitude=0
}
\pgfkeys{/tikz/.cd,
  longitude/.store in=\aLongit,  % observer's longitude
  longitude=0  % corresponds to x=0
}
\pgfkeys{/tikz/.cd,
  sun latitude/.store in=\sLatit,
  sun latitude = 80
}
\pgfkeys{/tikz/.cd,
  sun longitude/.store in=\sLongit, 
  sun longitude=90
}
\pgfkeys{/tikz/.cd,
  contrast/.store in=\lightC,  % light contrast \in [0, 1]
  contrast=.5
}
\pgfkeys{/tikz/.cd,
  arrowhead/.store in=\arrowLConstant,  % proportion of the head \in [0, 1]
  arrowhead=.3
}
\pgfkeys{/tikz/.cd,
  arrow radius/.store in=\arrowRadius,  % body radius in cm
  arrow radius=.08
}

\tikzmath{%
  function coneBaseColor(\k) {% \k = 1 or 2 for the two disks: bottom top
    \res = \nx*\tox + \ny*\toy + \nz*\toz;
    if \k==1 then {% bottom; the normal vector is -n
      \res = -\res;
    };
    if \res>0 then {% if seen
      \tmp = int(100*\lightC*(\nx*\sunx + \ny*\suny + \nz*\sunz));
      if \k==1 then {return -\tmp;} else {return \tmp;};
    } else {return -1000;};
  };
  function coneFaceColor(\j, \M) {% verifies if seen
    real \ang;
    \t = 360*((\j-.5)/\M);
    \ux = \vx*cos(\t) +\wx*sin(\t);
    \uy = \vy*cos(\t) +\wy*sin(\t);
    \uz = \vz*cos(\t) +\wz*sin(\t);
    % modification needed when the radii are different
    \ang = atan2(\coneRadiusB -\coneRadiusT, \tmpAB);
    \ux = \ux*cos(\ang) +\nx*sin(\ang);
    \uy = \uy*cos(\ang) +\ny*sin(\ang);
    \uz = \uz*cos(\ang) +\nz*sin(\ang);    
    \res = \ux*\tox + \uy*\toy + \uz*\toz;
    if \res>0 then {% if seen
      \tmp = int(100*\lightC*(\ux*\sunx + \uy*\suny + \uz*\sunz));
      return \tmp;
    } else {return -1000;};
  };  
}

\tikzset{%
  pics/cone/.style args={(#1,#2,#3), (#4,#5,#6), rB=#7, rT=#8}{%
    % x, y, z of the bottom and top centers, bottom and top radius
    code={%  
      \colorlet{mainColor}{.}
      \colorlet{leftRGB{1}}{white}
      \colorlet{leftRGB{0}}{mainColor!50!black}
      \colorlet{mainDark}{mainColor!50!black}
      \tikzmath{%  A, B, rB, rT, and construction of the orthonormal basis
        real \Ax, \Ay, \Az, \Bx, \By, \Bz, \coneRadiusB, \coneRadiusT;
        \Ax = #1;
        \Ay = #2;
        \Az = #3;
        \Bx = #4;
        \By = #5;
        \Bz = #6;
        \coneRadiusB = #7;
        \coneRadiusT = #8;
        integer \coneN, \k, \j, \prevj, \i;
        \coneN = 13 +int(max(40*#7, 40*#8));
        real \tmpAB, \tmpx, \tmpy, \tmpz, \tmpcst, \tmpForColor;
        real \nx, \ny, \nz, \vx, \vy, \vz, \wx, \wy, \wz;
        \tmpx = \Bx -\Ax;
        \tmpy = \By -\Ay;
        \tmpz = \Bz -\Az;
        \tmpAB = sqrt(\tmpx*\tmpx +\tmpy*\tmpy +\tmpz*\tmpz);
        \nx = \tmpx/\tmpAB;
        \ny = \tmpy/\tmpAB;
        \nz = \tmpz/\tmpAB;
        if abs(\ny)>=abs(\nz) then {%
          \tmpcst = sqrt(\nx*\nx +\ny*\ny);
          \vx = -\ny/\tmpcst;
          \vy = \nx/\tmpcst;
          \vz = 0;
          \wx = -\vy*\nz;
          \wy = \vx*\nz;
          \wz = (1 -\nz*\nz)/\tmpcst;
        } else {%
          \tmpcst = sqrt(\nx*\nx +\nz*\nz);
          \vx = -\nz/\tmpcst;
          \vy = 0;
          \vz = \nx/\tmpcst;
          \wx = \vz*\ny;
          \wy = (\ny*\ny -1)/\tmpcst;
          \wz = -\vx*\ny;
        };
        %% points \P {1,\j} for bottom, \P {2,\j} for top
        for \j in {0, ..., \coneN}{%
          \t = \j/\coneN*360;
          \Px{1,\j} = \Ax +\coneRadiusB*\vx*cos(\t) +\coneRadiusB*\wx*sin(\t);
          \Py{1,\j} = \Ay +\coneRadiusB*\vy*cos(\t) +\coneRadiusB*\wy*sin(\t);
          \Pz{1,\j} = \Az +\coneRadiusB*\vz*cos(\t) +\coneRadiusB*\wz*sin(\t);
          \Px{2,\j} = \Bx +\coneRadiusT*\vx*cos(\t) +\coneRadiusT*\wx*sin(\t);
          \Py{2,\j} = \By +\coneRadiusT*\vy*cos(\t) +\coneRadiusT*\wy*sin(\t);
          \Pz{2,\j} = \Bz +\coneRadiusT*\vz*cos(\t) +\coneRadiusT*\wz*sin(\t);
        };
        %% lateral faces
        for \j in {1, ..., \coneN}{%
          \prevj = \j -1;
          \tmpForColor = coneFaceColor(\j, \coneN);
          if \tmpForColor>-999 then {%
            if \tmpForColor>=0. then { \i = 1; } else {%
              \i = 0;  % in the shade for leftRGB{}
              \tmpForColor = int(abs(\tmpForColor));
            };
            {%
              \filldraw[leftRGB{\i}!\tmpForColor!mainColor, line width=.001pt]
              (\Px{1,\prevj}, \Py{1,\prevj}, \Pz{1,\prevj})
              -- (\Px{1,\j}, \Py{1,\j}, \Pz{1,\j})
              -- (\Px{2,\j}, \Py{2,\j}, \Pz{2,\j})
              -- (\Px{2,\prevj}, \Py{2,\prevj}, \Pz{2,\prevj})
              -- cycle;
            };            
          };
        };
        %% bottom or top face
        for \k in {1, 2}{
          \tmpForColor = coneBaseColor(\k);
          if \tmpForColor>-999. then {%
            if \tmpForColor>=0. then { \i = 1; } else {%
              \i = 0;  % in the shade for leftRGB{}
              \tmpForColor = int(abs(\tmpForColor));
            };
            {
              \fill[leftRGB{\i}!\tmpForColor!mainColor]
              (\Px{\k,0}, \Py{\k,0}, \Pz{\k,0}) 
              \foreach \j in {1, ..., \coneN}{%
                -- (\Px{\k,\j}, \Py{\k,\j}, \Pz{\k,\j}) 
              } -- cycle;
           };
          };
        };
      }  % end tikzmath
    }
  },
  pics/solid arrow/.style args={(#1,#2,#3)--#4+(#5,#6,#7)}{%
    code={%
      \colorlet{arrowColor}{.}
      \tikzmath{%
        real \aVx, \aVy, \aVz;
        \aVx = #5;
        \aVy = #6;
        \aVz = #7;
        real \CyBx, \CyBy, \CyBz, \Mx, \My, \Mz, \CoTx, \CoTy, \CoTz;
        \CyBx = #1;
        \CyBy = #2;
        \CyBz = #3;
        \CoTx = \CyBx +\aVx;
        \CoTy = \CyBy +\aVy;
        \CoTz = \CyBz +\aVz;
        \Mx = \CyBx +(1 -\arrowLConstant)*\aVx;
        \My = \CyBy +(1 -\arrowLConstant)*\aVy;
        \Mz = \CyBz +(1 -\arrowLConstant)*\aVz;
        real \cylinderRadius, \coneRadius, \arrowL;
        \arrowL = sqrt(\aVx*\aVx +\aVy*\aVy +\aVz*\aVz);
        \cylinderRadius = \arrowRadius;
        \coneRadius = 2*\cylinderRadius;
        \testTopSeen = \aVx*\tox + \aVy*\toy + \aVz*\toz;
        if \testTopSeen>0 then {%
          {
            \draw pic[arrowColor]
            {cone={(\CyBx, \CyBy, \CyBz), (\Mx, \My, \Mz),
                rB=\cylinderRadius, rT=\cylinderRadius}};
            \draw pic[arrowColor]
            {cone={(\Mx, \My, \Mz), (\CoTx, \CoTy, \CoTz),
                rB=\coneRadius, rT=0}};
          };
        } else {%
          {
            \draw pic[arrowColor]
            {cone={(\Mx, \My, \Mz), (\CoTx, \CoTy, \CoTz),
                rB=\coneRadius, rT=0}};
            \draw pic[arrowColor]
            {cone={(\CyBx, \CyBy, \CyBz), (\Mx, \My, \Mz),
                rB=\cylinderRadius, rT=\cylinderRadius}};
          };
        };
      }  % end tikzmath
    }
  }
}


\begin{tikzpicture}[view={20}{30}, sun={-110}{30}{.5},
  arrow radius=.07,
  evaluate={%
    real \r, \l;
    \r = 3; \l = 1.6;
    integer \N;
    \N = 11;
  }]
  \foreach \k [evaluate=\k as \t using {90 +(\k/\N)*360}]
  in {1, ..., \N}{%
    \path pic[red]
    {solid arrow={({\r*sin(\t)}, 0, {\r*cos(\t)})--
        +({\l*cos(\t)}, 0, {-\l*sin(\t)})}};
  }
  \path pic[blue, arrow radius=.075, arrowhead=.17]
  {solid arrow={(0, 0, 0)-- +(0, 3*\l, 0)}};
  
  \path (0, 0, 0) +(0, 3*\l, 0)
  node[right=1ex] {$\vec{J}+\frac{\partial\vec{D}}{\partial t}$};;
\end{tikzpicture}
\end{document}

答案2

用于 Asymptote 中的真实 3D 箭头。

在此处输入图片描述

// Run on http://asymptote.ualberta.ca/
import three;
usepackage("amsmath");
size(5cm);
currentprojection=orthographic(2,3,1,zoom=.9);
string s="$\vec{J}+\dfrac{\partial\vec{D}}{\partial t}$";
draw(Label(s,align=N,EndPoint,blue),O--3Z,blue+3pt,Arrow3(size=15pt));

// (x(t),y(t)) is the circle of radius r
real r=2;
real x(real s){return r*cos(s);}
real y(real s){return r*sin(s);}
// (x'(t),y'(t)) are tangents
real xt(real s){return -r*sin(s);}
real yt(real s){return r*cos(s);}

int n=10; // the number of tangent vectors
for (int i=0; i<n; ++i){
real s=i*2pi/n;
path3 vec=O--(xt(s),yt(s),0);
transform3 T=shift(x(s),y(s),0)*scale3(.5);  
draw(T*vec,magenta+2pt,Arrow3(size=10pt));
}

label("$\vec{H}$",(0,-r-1,0),magenta);

更新:为了获得更好的外观,每个箭体都被视为一个圆柱体(solids需要模块)。

在此处输入图片描述

// Run on http://asymptote.ualberta.ca/
import three;
import solids;
usepackage("amsmath");
size(5cm);
currentprojection=orthographic(2,3,1,zoom=.8);
string s="$\vec{J}+\dfrac{\partial\vec{D}}{\partial t}$";
draw(Label(s,align=N,EndPoint,blue),O--3Z,blue,Arrow3(size=15pt));
draw(scale(.05,.05,2.8)*unitcylinder,blue);

// (x(t),y(t)) is the circle of radius r
real r=2;
real x(real s){return r*cos(s);}
real y(real s){return r*sin(s);}
// (x'(t),y'(t)) are tangents
real xt(real s){return -r*sin(s);}
real yt(real s){return r*cos(s);}

int n=10; // the number of tangent vectors
for (int i=0; i<n; ++i){
real s=i*2pi/n;
triple B=(xt(s),yt(s),0),A=(x(s),y(s),0);  
path3 vec=A--A+.6B;
draw(vec,magenta,Arrow3(size=10pt));
draw(shift(A)*surface(cylinder(O,.05,1,B)),magenta);
}

label("$\vec{H}$",(0,-r-1,0),magenta);

相关内容