14

Asymptote does give 3D arrows when drawing three dimensional figures (they even have shade!). Could we fake it with TikZ? Here's an example, manually written, with just one arrow worked:

\documentclass{scrartcl}
\usepackage{tikz}
\usetikzlibrary{arrows.meta}
\begin{document}
\begin{tikzpicture}[->,scale=2]
  \draw[>={Triangle[width=3pt,length=5pt]}] (0,0,0) -- (1,0,0);
  \draw[>={Triangle[width=3pt,length=5pt]}] (0,0,0) -- (0,1,0);
  \draw[>={Circle[sep=-.9pt,width=3.08pt,length=2pt]Triangle[width=3pt,length=5pt]}] (0,0,0) -- (0,0,1);
\end{tikzpicture}
\end{document}

enter image description here

But that's (wrongly) manually calculated by trial and error (apart from the fact that it's not perfect, seeing how the other two axis —x and y— are oriented, it should have certain slant). It would be nice to have an interface like the other arrows

>={3D[length=⟨real length⟩,width=⟨real width of the base⟩]}

Which would then calculate from those values (and, of course, also taking into account x=⟨x vect⟩,y=⟨y vect⟩,z=⟨z vect⟩)

Circle[sep=⟨value⟩,width=⟨value⟩,length=⟨value⟩,slant=⟨value⟩]
Triangle[width=⟨value⟩,length=⟨value⟩] % And I'm not sure if `slant=⟨value⟩`
                                       % is necessary in this Triangle case

Example

Using the code from the answer (thanks to Symbol1), for instance, this code (taking the idea from another question, link in the comments)

\begin{tikzpicture}[x={(0cm,1cm)},
                    y={({cos(30)*1cm},{sin(30)*-1cm})},
                    z={({cos(20)*1cm},{sin(20)*1cm})},
                    thick,
                    scale=3]
  \pgfmathsetmacro{\ax}{.4}
  \pgfmathsetmacro{\ay}{.6}
  \pgfmathsetmacro{\az}{-.6}
  \pgfmathsetmacro{\bx}{.4}
  \pgfmathsetmacro{\by}{-.7}
  \pgfmathsetmacro{\bz}{-.8}
  \draw[densely dotted,thin] (\ax,\ay,\az) -- (\ax,0,\az);
  \draw[densely dotted,thin] (\ax,\ay,\az) -- (0,\ay,\az);
  \draw[densely dotted,thin] (\ax,0,0) -- (\ax,0,\az) -- (0,0,\az);
  \draw[densely dotted,thin] (0,0,\az) -- (0,\ay,\az) -- (0,\ay,0);
  \draw[densely dotted,thin] (\bx,\by,\bz) -- (\bx,0,\bz);
  \draw[densely dotted,thin] (\bx,\by,\bz) -- (0,\by,\bz);
  \draw[densely dotted,thin] (\bx,0,0) -- (\bx,0,\bz) -- (0,0,\bz);
  \draw[densely dotted,thin] (0,0,\bz) -- (0,\by,\bz) -- (0,\by,0);
  \draw[-{Cone3}] (0,0,0) -- (1,0,0) node[right] {$x$};
  \draw[-{Cone3}] (0,0,0) -- (0,1,0) node[above right] {$y$};
  \draw[-{Cone3}] (0,0,0) -- (0,0,1) node[below right] {$z$};
  \draw[-{Cone3}] (0,0,0) -- (0,0,-1) node[above left] {$-z$};
  \draw[thin] (0,0,0) -- (0,-.9,0);
  \draw[-{Cone3}] (0,0,0) -- (\ax,\ay,\az) node[below left] {source};
  \draw[-{Cone3}] (0,0,0) -- (\bx,\by,\bz) node[above right] {sim};
\end{tikzpicture}

gives this figure

enter image description here

Manuel
  • 27,118
  • Related, but not near the solution, IMHO. In any case, in this question the shading is not important :) – Manuel Sep 12 '15 at 16:21
  • Is it not the exact same questions? (This and "3D Arrow Tips for TikZ/pgfplots" given by Gonzalo Medina) – hpekristiansen Sep 12 '15 at 16:38
  • In asymptote the arrowheads are cone solids. TikZ has to draw a cone at the edge of the axes which is pretty nontrivial if you don't have pgfplots. And imho, asymptote arrows are ugly. – percusse Sep 12 '15 at 17:20
  • @Hans-PeterE.Kristiansen I think the question is probably the same question (more or less), but since the accepted answer there has nothing to do with what an hypothetical answer here would have, I think they are not exactly the same. – Manuel Sep 12 '15 at 17:39
  • @percusse I do not want cone solids, I want to draw the shadow of a cone solid (i.e., the “base” of the cone, which is a modified circle in the end, and a triangle which is tangent to that circle; even if it's all black, the reader would certainly see some 3D, just look at the image I uploaded). – Manuel Sep 12 '15 at 17:42
  • @percusse I just read this. I would like to be able to draw that and say >={3D[blah,blah,blah]} and then get different arrows for each line so when you look at it the realism is higher. – Manuel Sep 12 '15 at 17:51
  • After some researches, I found that the third dimension is forgotten once the triple (coordinate in space) is read and translated to the pair (coordinate in plane) at a very first stage. On the other hand the drawing of arrows is very late (at least after bend). Since TikZ does not define 3D bezier curve, it cannot calculated the tangent line in space and thus draw the arrow accordingly. – Symbol 1 Sep 14 '15 at 08:55
  • @Symbol1 That removes the possibility of using the standard pgfkey >=3D (unless someone hacks the way three dimensional coordinates are parsed, to save for later the three dimensions). Any other user interface would be okey, e.g., \spacearrow{⟨arrow options⟩}{⟨origin⟩}{⟨end⟩};. My problem with pgf/TikZ is that I never understood the basics, so I can't dive into the source to find this critical parts (I don't even know how the files are organized). – Manuel Sep 14 '15 at 09:33

2 Answers2

14

This approach allows you to assign a pitch from 0 to 90, where 0 means lying on screen and 90 means perpendicular to screen. (Certainly you can assign 91.1 or 5566 or any good number. Currently the abs in the code wipe out all funny situations.)

Once you fix the pitch, the drawing code part will do some necessary calculations such as positions of the tangent points. Notice that while predefined arrow tips have some additional options such as open and harpoon, my tip does not implement them. I even use fill to get rid of line-width issue.

\documentclass[tikz,border=9]{standalone}
\usepgflibrary{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     = +3.6pt +5.4,
    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}[line width=5]
    \draw[-{Cone          }](0,0,0)--(1,0,0);
    \draw[-{Cone          }](0,0,0)--(0,1,0);
    \draw[-{Cone[pitch=60]}](0,0,0)--(0,0,1);
    \path(2,0,0)(0,2,0)(0,0,2);
\end{tikzpicture}

\foreach\theta in{0,10,...,350}{
    \tikz[line width=5]\draw[-{Cone[pitch=\theta]}](-2,0)(2,0)(0,0)--({cos(\theta)},0);
}

\foreach\theta in{0,10,...,350}{
    \tikz[line width=5,opacity=.5]\draw[-{Cone[pitch=\theta]}](-2,0)(2,0)(0,0)--({cos(\theta)},0);
}

\end{document}

Update

I a wrote three tips Cone1, Cone2, and Cone3.

Cone1

It uses pitch like the Cone above except that it now checks if sin(pitch) is positive. If so, it presumes that the arrow is pointing to your eyes and hence add a white dot. (At the same time, cos(pitch) is mandatorily positive.)

Speaking of dots, it is hard to decide the correct size and the color of it. Currently Cone1 reads the setting in line width and fill a white circle of which the diameter is the width. This is a good idea since the line width is not used anywhere else but also a bad idea if you do care about code legibility.

Cone2

This tip accepts a tangent option by tangent={(1,2,3)} so that it can calculate the pitch (actually the sine and cosine of it).

The problem is, throughout the world of TikZ no one has ever cared about projections of 3D-vectors perpendicular to the screen. If we are lucky enough that the current projection to the screen is of orthogonal type, which is probably assigned by tikz-3dplot, then the projection perpendicular to the screen is well defined in mathematics manner up to a sign. (We cannot tell the difference between into screen and out of screen given the projection to the screen.)

Unfortunately in most cases if you assign x=, y=, and z= by hand it would not be a orthogonal projection.

Here I simply use a cross-product to calculate the missing projection subjected to the condition that the result will be correct if one uses tikz-3dplot to assign the projection.

More precisely, \tikz@scan@one@point is a command used in TikZ to parse the coordinates such as (1,2), (3:4), (A), or (5,6,7). When I write

\tikz@scan@one@point\pgfarrowtangenttosincos#1

and #1 is, say, (5,6,7), TikZ will end up with

\pgfarrowtangenttosincos{\pgfpointxyz{5}{6}{7}}

And then, according to the definition of \pgfarrowtangenttosincos, PGF will executes

\pgfpointxyz{5}{6}{7}
\tdplotcrossprod(\pgf@xx,\pgf@yx,\pgf@zx)(\pgf@xy,\pgf@yy,\pgf@zy)

Now

  • (\pgftemp@x,\pgftemp@y,\pgftemp@z) is (5,6,7).
  • (\pgf@x,\pgf@y) is the projection of (5,6,7) on the screen.
  • (\tdplotresx,\tdplotresy,\tdplotresz) is the result of cross product.

So

  • sqrt(\pgf@x*\pgf@x+\pgf@y*\pgf@y) is the length on the screen
  • inner product of (\tdplotresx,\tdplotresy,\tdplotresz) and (\tdplotresx,\tdplotresy,\tdplotresz) is the length off the screen
  • the root-sum-square of the above two is the length of the whole vector.

So

  • sine is a/c
  • cosine is b/c

After that everything works like Cone1.

Cone3

I hack \pgfpointxyz so that it buffers two recent 3D-coordinates. While the arrow tip is being drawn, I presume that the old, buffered coordinate is the end and the older, buffered coordinate is the start. So the tangent should be the difference of these two coordinate.

Code

\documentclass[tikz]{standalone}
\usepgflibrary{arrows.meta}
\usepackage{tikz-3dplot}
\begin{document}


\makeatletter

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

\pgfdeclarearrow{
  name = Cone1,
  defaults = {       % inherit from Kite
    length     = +3.6pt +5.4,
    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*abs(\pgfarrowsinpitch)}
    \pgfpathellipse{\pgfpointorigin}{\pgfqpoint{\pgfarrowhalfwidthsin pt}{0pt}}{\pgfqpoint{0pt}{\pgfarrowhalfwidth pt}}
    \pgfusepath{fill}
    % test if the cone part visible
    \pgfmathsetmacro\pgfarrowlengthcos{\pgfarrowlength*\pgfarrowcospitch}
    \ifdim\pgfarrowlengthcos pt>\pgfarrowhalfwidthsin pt
      % 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}
    \else
      % it is invisible, check in pointing your eye
      \ifdim\pgfarrowsinpitch pt>0pt
      \pgfpathcircle{\pgfqpoint{\pgfarrowlengthcos pt}{0pt}}{.5\pgfarrowlinewidth}
      \pgfsetcolor{white}
      \pgfusepath{fill}
      \fi
    \fi
    \pgfpathmoveto{\pgfpointorigin}
  }
}
%\begin{tikzpicture}[line width=5]
%   \draw[-{Cone1          }](0,0,0)--(1,0,0);
%   \draw[-{Cone1          }](0,0,0)--(0,1,0);
%   \draw[-{Cone1[pitch=60]}](0,0,0)--(0,0,1);
%   \path(3,0,0)(0,3,0)(0,0,3);
%\end{tikzpicture}
%\foreach\theta in{0,10,...,350}{
%   \tikz[line width=5]\draw[-{Cone1[width'=0 1,pitch=\theta]}](-2,-.5)(2,.5)(0,0)--({cos(\theta)},0);
%}
%\foreach\theta in{0,10,...,350}{
%   \tikz[line width=5,opacity=.5]\draw[-{Cone1[width'=0 1,pitch=\theta]}](-2,-.5)(2,.5)(0,0)--({cos(\theta)},0);
%}








\def\pgfarrowtangenttosincos#1{
    #1
    \tdplotcrossprod(\pgf@xx,\pgf@yx,\pgf@zx)(\pgf@xy,\pgf@yy,\pgf@zy)
    \pgfmathsetmacro\pgfarrowtangentxxyy{\pgf@x*\pgf@x+\pgf@y*\pgf@y}
    \pgfmathsetmacro\pgfarrowtangentxy{sqrt(\pgfarrowtangentxxyy)}
    \pgfmathsetmacro\pgfarrowtangentz{(\pgftemp@x*\tdplotresx+\pgftemp@y*\tdplotresy+\pgftemp@z*\tdplotresz)/72.27*2.54}
    %\message{^^J^^J(\tdplotresx,\tdplotresy,\tdplotresz)(\pgfarrowtangentxy,\pgfarrowtangentz)}\show\\
    \pgfmathsetmacro\pgfarrowtangentxyz{sqrt(\pgfarrowtangentxxyy+\pgfarrowtangentz*\pgfarrowtangentz)}
    \pgfmathsetmacro\pgfarrowcospitch{\pgfarrowtangentxy/\pgfarrowtangentxyz}
    \pgfmathsetmacro\pgfarrowsinpitch{\pgfarrowtangentz/\pgfarrowtangentxyz}
}
\pgfkeys{
  /pgf/arrow keys/.cd,
  tangent/.code={%
    \tikz@scan@one@point\pgfarrowtangenttosincos#1
  }
}
\pgfdeclarearrow{
  name = Cone2,
  defaults = {             % inherit from Kite
    length     = +3.6pt +5.4,
    width'     = +0pt +0.5,
    line width = +0pt 1 1,
    tangent    = {(1,0,0)} % lie on x-axis
  },
  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*abs(\pgfarrowsinpitch)}
    \pgfpathellipse{\pgfpointorigin}{\pgfqpoint{\pgfarrowhalfwidthsin pt}{0pt}}{\pgfqpoint{0pt}{\pgfarrowhalfwidth pt}}
    \pgfusepath{fill}
    % test if the cone part visible
    \pgfmathsetmacro\pgfarrowlengthcos{\pgfarrowlength*\pgfarrowcospitch}
    \ifdim\pgfarrowlengthcos pt>\pgfarrowhalfwidthsin pt
      % 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}
    \else
      % it is invisible, check in pointing your eye
      \ifdim\pgfarrowsinpitch pt>0pt
      \pgfpathcircle{\pgfqpoint{\pgfarrowlengthcos pt}{0pt}}{.5\pgfarrowlinewidth}
      \pgfsetcolor{white}
      \pgfusepath{fill}
      \fi
    \fi
    \pgfpathmoveto{\pgfpointorigin}
  }
}
\tdplotsetmaincoords{70}{110}
\begin{tikzpicture}[line width=5,tdplot_main_coords]
    \draw[-{Cone2[tangent={(1,0,0)}]}](0,0,0)--(1,0,0)node[cyan]{X};
    \draw[-{Cone2[tangent={(0,1,0)}]}](0,0,0)--(0,1,0)node[cyan]{Y};
    \draw[-{Cone2[tangent={(0,0,1)}]}](0,0,0)--(0,0,1)node[cyan]{Z};
    \path(-2cm,-2cm)(2cm,2cm);
\end{tikzpicture}

\foreach\theta in{0,10,...,350}{
    \tdplotsetrotatedcoords{\theta}{30}{30}
    \tikz[line width=5,line cap=round,tdplot_rotated_coords]{
        \draw[-{Cone2[tangent={(1,0,0)}]}](0,0,0)--(1,0,0)node[cyan]{X};
        \draw[-{Cone2[tangent={(0,1,0)}]}](0,0,0)--(0,1,0)node[cyan]{Y};
        \draw[-{Cone2[tangent={(0,0,1)}]}](0,0,0)--(0,0,1)node[cyan]{Z};
        \path(-2cm,-2cm)(2cm,2cm);
    }
}










\def\pgfpointxyz#1#2#3{%
  \pgfmathparse{#1}%
  \let\pgftemp@x=\pgfmathresult%
  \pgfmathparse{#2}%
  \let\pgftemp@y=\pgfmathresult%
  \pgfmathparse{#3}%
  \let\pgftemp@z=\pgfmathresult%
  \pgf@x=\pgftemp@x\pgf@xx%
  \advance\pgf@x by \pgftemp@y\pgf@yx%
  \advance\pgf@x by \pgftemp@z\pgf@zx%
  \pgf@y=\pgftemp@x\pgf@xy%
  \advance\pgf@y by \pgftemp@y\pgf@yy%
  \advance\pgf@y by \pgftemp@z\pgf@zy%
  % ↑↑↑old definition↑↑↑ ↓↓↓new code↓↓↓
  \global\let\pgfolderpointx\pgfoldpointx
  \global\let\pgfolderpointy\pgfoldpointy
  \global\let\pgfolderpointz\pgfoldpointz
  \global\let\pgfoldpointx\pgftemp@x
  \global\let\pgfoldpointy\pgftemp@y
  \global\let\pgfoldpointz\pgftemp@z
}


\pgfdeclarearrow{
  name = Cone3,
  defaults = {             % inherit from Kite
    length     = +3.6pt +5.4,
    width'     = +0pt +0.5,
    line width = +0pt 1 1,
    tangent    = {(1,0,0)} % lie on x-axis
  },
  cache = false,           % no need cache
  setup code = {},         % so no need setup
  drawing code = {         % but still need math
    % calculate the tangent
    \pgfkeys{pgf/arrow keys/tangent={(\pgfoldpointx-\pgfolderpointx,\pgfoldpointy-\pgfolderpointy,\pgfoldpointz-\pgfolderpointz)}}
    % draw the base
    \pgfmathsetmacro\pgfarrowhalfwidth{.5\pgfarrowwidth}
    \pgfmathsetmacro\pgfarrowhalfwidthsin{\pgfarrowhalfwidth*abs(\pgfarrowsinpitch)}
    \pgfpathellipse{\pgfpointorigin}{\pgfqpoint{\pgfarrowhalfwidthsin pt}{0pt}}{\pgfqpoint{0pt}{\pgfarrowhalfwidth pt}}
    \pgfusepath{fill}
    % test if the cone part visible
    \pgfmathsetmacro\pgfarrowlengthcos{\pgfarrowlength*\pgfarrowcospitch}
    \ifdim\pgfarrowlengthcos pt>\pgfarrowhalfwidthsin pt
      % 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}
    \else
      % it is invisible, check in pointing your eye
      \ifdim\pgfarrowsinpitch pt>0pt
      \pgfpathcircle{\pgfqpoint{\pgfarrowlengthcos pt}{0pt}}{.5\pgfarrowlinewidth}
      \pgfsetcolor{white}
      \pgfusepath{fill}
      \fi
    \fi
    \pgfpathmoveto{\pgfpointorigin}
  }
}
\tdplotsetmaincoords{70}{110}
\begin{tikzpicture}[line width=5,tdplot_main_coords]
    \draw[-{Cone2[tangent={(1,0,0)}]}](0,0,0)--(1,0,0)node[cyan]{X};
    \draw[-{Cone2[tangent={(0,1,0)}]}](0,0,0)--(0,1,0)node[cyan]{Y};
    \draw[-{Cone2[tangent={(0,0,1)}]}](0,0,0)--(0,0,1)node[cyan]{Z};
    \path(-2cm,-2cm)(2cm,2cm);
\end{tikzpicture}

\foreach\theta in{0,5,...,355}{
    \tdplotsetrotatedcoords{\theta}{2*\theta}{3*\theta}
    \tikz[line width=5,line cap=round,tdplot_rotated_coords]{
        \draw[-Cone3](0,0,0)--(1,0,0)node[cyan]{X};
        \draw[-Cone3](0,0,0)--(0,1,0)node[cyan]{Y};
        \draw[-Cone3](0,0,0)--(0,0,1)node[cyan]{Z};
        \path(-2cm,-2cm)(2cm,2cm);
    }
}




\end{document}
Symbol 1
  • 36,855
  • Well, this is pretty good. Just a few things: (1) in case the top of the arrow falls inside the base, could you add a tiny white dot so it's marked where the virtual end of the arrow would be?; (2) just a comment the reason why I talked about slant is because, depending on the axis it might be needed (here's an example of a cone in this particular perspective); (3) couldn't you hack the starting point of a three coordinate point to save those values for later so the pitch could be autocalculated? – Manuel Sep 14 '15 at 19:24
  • @Manuel For (2) I guess the cone can be slanted on plane in only two cases: either it is slanted in space or the projection is not orthogonal. For both case the calculation is pretty hard. But you still can assign slant directly since it is handled by a coordinate transformation. For (1)(2) I need some work. – Symbol 1 Sep 14 '15 at 23:47
  • Haha, nice! Ugly but nice! :D – percusse Sep 15 '15 at 08:16
9

what about this one?

enter image description here

\documentclass[margin=10pt]{standalone}
\usepackage[svgnames]{xcolor}
\usepackage{tikz}
\begin{document}

\begin{tikzpicture}[scale=3]
\def\opaque{0.035}    % rendering opacity 
\def\prop{1.3}        % variable of rendering opacity
\def\R{2} %           % cone slant height
\def\theta{21}        % 2*\theta is the angle of expanded by the tip of the cone
\def\range{100}       % number of interaction for smooth rending effect
\def\ratio{0.4}       % ellipse b/a for cone lower edge 
\def\aratio{0.35}     % a_rod / a_cone  
\def\height{0.8}      % rod height 
\def\fraction{0.375}  % ellipse b/a for rod edge 
\def\angle{12}        % angle extended by rod edge
\def\total{60}        % number of interaction for smooth rending effect
\def\conecolor{red!\range!black!40!red}   % cone color 

\foreach \i in {0,...,\range}
{
    \fill[\conecolor, opacity={\opaque+\prop*\opaque*(\range-2*\i)/\range}] (0, {\R*cos(\theta)}) -- ({\R*sin(\theta)*cos(270+90*\i/\range)},{\ratio*\R*sin(\theta)*sin(270+90*\i/\range)})  arc({270+90*\i/\range}:360: {\R*sin(\theta)}  and {\ratio*\R*sin(\theta)} ) -- cycle;
    \fill[\conecolor, opacity={\opaque+\prop*\opaque*(\range-\i)/\range}] (0, {\R*cos(\theta)}) -- ({\R*sin(\theta)*cos(270-90*\i/\range)},{\ratio*\R*sin(\theta)*sin(270-90*\i/\range)})  arc({270-90*\i/\range}:180: {\R*sin(\theta)}  and {\ratio*\R*sin(\theta)} ) -- cycle;
}
\definecolor{conered}{RGB}{255,114,114}
\definecolor{conegreen}{RGB}{37,146,37}
\definecolor{coneblue}{RGB}{107,107,236}

\fill[conered] (-0.7167,0) arc (180:360: 0.7167 and 0.2867)  arc (0:180: 0.7167 and 0.2867) -- cycle ;

\pgfmathparse{\R*sin(\theta)*\aratio}

\fill[conered] (-0.251, -{sqrt(1-0.251*0.251/(0.7167*0.7167) )*0.2867}) arc ({360-acos(-0.251/0.7167) )}:{360-acos(0.251/0.7167 )}: 0.7167 and 0.2867 ) -- (0.251,-\height) arc (360:180:0.251 and 0.0941) --cycle ;

\foreach \i  in {0,...,\total}
{
    \fill[\conecolor, opacity={2*\opaque+\prop*\opaque*(\total-1*\i)/\total}] ({\pgfmathresult*\i/\total}, {\pgfmathresult*\ratio*sqrt(1-\i/\total*\i/\total) } )  arc ( {acos(\i/\total)}:0: {\R*sin(\theta)*\aratio} and {\R*sin(\theta)*\aratio*\ratio} ) -- ++(0,-\height) arc (360: {360-acos(\i/\total)}: {\R*sin(\theta)*\aratio} and {\R*sin(\theta)*\aratio*\ratio} ) -- cycle;
    \fill[\conecolor, opacity={2*\opaque+\prop*\opaque*(\total-1*\i)/\total}] ({-\R*sin(\theta)*\aratio*\i/\total}, {\R*sin(\theta)*\aratio*\ratio*sqrt(1-\i/\total*\i/\total) } )  arc ( {acos(-\i/\total)}:180: {\R*sin(\theta)*\aratio} and {\R*sin(\theta)*\aratio*\ratio} ) -- ++(0,-\height) arc (180: {360-acos(-\i/\total)}: {\R*sin(\theta)*\aratio} and {\R*sin(\theta)*\aratio*\ratio} ) -- cycle;
}

\draw[thick, conered, yshift=-0.8cm] (-0.251,0.8) -- (-0.251,0) arc (180:360:0.251 and 0.0941) -- (0.251,0.8);
\end{tikzpicture}

\end{document}