76

Okay so I think everyone knows how to draw a sphere in Tikz

\tikz{
\fill [black] (-1,-1) rectangle (1,1);
\shade [ball color=white] (0,0) circle [radius=1cm];
}

But how can I add to it this effect that can be found in Penrose's drawings? (those dots around)

enter image description here

And in general how can one create that effect in Tikz for any shape? (Here are a couple of other images for your viewing pleasure)

enter image description here

enter image description here

enter image description here

enter image description here

jub0bs
  • 58,916
someone
  • 761
  • I used to create a similar effect on a B&W graphics device by adding gray levels for pixels in a scan until it hit one, output a black pixel and subtract one from the running total. I seem to recall having to add a little noise to make the dots less regular. – John Kormylo Sep 10 '15 at 14:26
  • 1
    This would be fairly easy in lualatex, but in just latex+tikz this would be beyond my ability. – JPi Sep 10 '15 at 20:06
  • 5
    @JPi: It's perfectly acceptable (and encouraged) to post an answer that uses a package or engine other than the one that is mentioned in the question. I for one would love to see an approach that uses lualatex! – Jake Sep 10 '15 at 20:37
  • @Jake: ok, done. – JPi Sep 11 '15 at 01:32

4 Answers4

80

This takes ages. Also some care is needed with the direction of the path and best effects are achieved by selectively applying the stippling to parts of the path. So, a real pain, basically. But (as with many things) it looks quite good from a distance.

\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{decorations}
\pgfkeys{/pgf/decoration/.cd,
  stipple density/.store in=\pgfstippledensity,
  stipple density=.1,
  stipple scaling function/.store in=\pgfstipplescalingfunction,
  stipple scaling function=sin(\pgfstipplex*180)*0.875+0.125,
  stipple radius/.store in=\pgfstippleradius,
  stipple radius=0.25pt
}
\pgfdeclaredecoration{stipple}{draw}{
\state{draw}[width=\pgfdecorationsegmentlength]{%
  \pgfmathparse{\pgfdecoratedcompleteddistance/\pgfdecoratedpathlength}%
  \let\pgfstipplex=\pgfmathresult%
  \pgfmathparse{int(\pgfstippledensity*100)}%
  \let\pgfstipplen=\pgfmathresult%
  \pgfmathloop%
  \ifnum\pgfmathcounter<\pgfmathresult\relax%
    \pgfpathcircle{%
      \pgfpoint{(rnd)*\pgfdecorationsegmentlength}%
        {(\pgfstipplescalingfunction)*(rnd^4)*\pgfdecorationsegmentamplitude+\pgfstippleradius}}% 
    {\pgfstippleradius}%
  \repeatpgfmathloop%
}
}

\tikzset{stipple/.style={
  decoration={stipple, segment length=2pt, #1},
  decorate,
  fill
}}
\begin{document}
\begin{tikzpicture}
\draw [postaction={stipple={amplitude=0.125cm}}] 
  (0,0) [rotate=45]  circle [radius=1];
\path [postaction={stipple={amplitude=0.25cm, stipple density=.35}},
  postaction={stipple={amplitude=0.35cm, stipple density=.15}}]
  (135:1) arc (135:315:1);
\end{tikzpicture}

\end{document}

enter image description here

And this takes even longer:

\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{decorations}
\pgfkeys{/pgf/decoration/.cd,
  stipple density/.store in=\pgfstippledensity,
  stipple density=(sin(\pgfstipplex*180)*0.25+0.1),
  stipple amplitude/.store in=\pgfstippleamplitude,
  stipple amplitude=(rnd^3)*\pgfstippley*(sin(\pgfstipplex*180)^2+0.05),
  stipple radius/.store in=\pgfstippleradius,
  stipple radius=0.25pt
}
\pgfdeclaredecoration{stipple}{draw}{
\state{draw}[width=\pgfdecorationsegmentlength]{%
  \pgfmathparse{\pgfdecoratedcompleteddistance/\pgfdecoratedpathlength}%
  \let\pgfstipplex=\pgfmathresult%
  \let\pgfstippley=\pgfdecorationsegmentamplitude%
  \pgfmathparse{int(abs((\pgfstippledensity)*100))}%
  \let\pgfstipplen=\pgfmathresult%
  \pgfmathloop%
  \ifnum\pgfmathcounter<\pgfmathresult\relax%
    \pgfpathcircle{%
      \pgfpoint{(rnd-0.5)*\pgfdecorationsegmentlength}%
        {(\pgfstippleamplitude)+\pgfstippleradius}}% 
    {\pgfstippleradius}%
  \repeatpgfmathloop%
}
}

\tikzset{stipple/.style={
  decoration={stipple, segment length=2pt, #1},
  decorate,
  fill
}}
\begin{document}
\begin{tikzpicture}[x=2em,y=2em]
\draw [postaction={stipple={amplitude=0.5cm}},
      postaction={stipple={amplitude=0.25cm}}] 
  (2,1) ..controls ++(135:1) and ++(90:1) .. 
  (0,0) .. controls ++(270:1) and ++(180:1.5) .. 
  (2,-2) .. controls ++(0:1.5) and ++(270:2) .. 
  (5,1) .. controls ++(90:2) and ++(75:1) .. 
  (2,1) .. controls ++(255:1/4) and ++(0:1/2) .. (3/2,0);
\draw [postaction={stipple={amplitude=0.125cm, stipple density=0.05}},
  postaction={stipple={amplitude=0.5cm, reverse path}},
  postaction={stipple={amplitude=0.25cm, reverse path}}] 
  (3,0) .. controls ++(135:1) and ++(90:1) .. (4,1);
\end{tikzpicture}
\end{document}

enter image description here

Mark Wibrow
  • 70,437
  • 2
    Wow, that looks amazing! – Jake Sep 11 '15 at 08:40
  • 5
    That looks f*** awesome. I didn't expect something so close. – MyUserIsThis Sep 11 '15 at 08:43
  • 4
    Spectacular! I'm already using it! – Gonzalo Medina Sep 11 '15 at 17:11
  • 1
    Why did you put a % after \pgfmathresult and \pgfdecorationsegmentamplitude? Are those macros "special" in some way? – Pier Paolo Sep 11 '15 at 18:34
  • 2
    @PierPaolo no, they aren't special, actually I usually put % at the end of most lines involving pgf basic layer stuff (even when it is technically not necessary). I was just "lazy" in this case. – Mark Wibrow Sep 11 '15 at 19:44
  • 1
    @MarkWibrow That's awesome. Any possibility of automating this with lua? So it doesn't take ages? – Manuel Sep 11 '15 at 20:08
  • Great work! it was fun to figure out how to do it from first principles, though. – Davislor Sep 11 '15 at 20:09
  • @Manuel : no problem – JPi Sep 12 '15 at 00:53
  • 1
    @Manuel in principle it could be automated with lua. But as the man factor affecting the speed is the decoration engine (specifically, calculating/estimation the path length and moving along the path by a particular distance) the majority of the low-level decoration stuff would have to be rewritten. – Mark Wibrow Sep 12 '15 at 06:27
  • First takes 4.7 sec with pdfTeX, second one takes 17.4 sec and requires LuaTeX (on my machine of course: Intel(R) Core(TM) i3-3225 CPU @ 3.30GHz). – Henri Menke May 30 '16 at 14:56
  • @MarkWibrow: That's amazing. I tried to compile your code with pdflatex but I get this error `Runaway argument? \pgfsyssoftpath@movetotoken {40.00006pt}{20.00003pt}\pgfsyssoftpath@movetotoken \ETC. ! TeX capacity exceeded, sorry [main memory size=5000000]. \pgf@last@processed@path ...etosupportatoken \ETC.

    l.41 ...rols ++(255:1/4) and ++(0:1/2) .. (3/2,0);`. Could you please help? Thanks.

    – Hoang-Ngan Nguyen Jul 03 '16 at 05:29
  • @Hoang-NganNguyen Decorations can be a bit fragile, particularly when decorating a path that is "too complicated" (e.g., lots of sub-paths), or with sub-paths that are very short. Difficult to know without seeing the code. You could try asking a new question using a minimal example that exhibits the problem. – Mark Wibrow Jul 04 '16 at 07:32
  • @MarkWibrow: Thank you. Probably, your path is "too complicated" so that pdflatex use all allowed memory. I compile the code in lualatex and it runs fine. – Hoang-Ngan Nguyen Jul 04 '16 at 15:46
38

Looks like I’ll have to step up my game here. Mark Wibrow added a great answer, but this one has a few points in its favor, including grayscaling the dots so the ones on the inside are lighter, getting exactly predictable and replicable results on each run, and having control over all the parameters. (For example, you can change how much the thickness of the stippling varies by changing the exponent of \thisrowno{1} in both places.) It also runs pretty quick while working in engines other than LuaLaTeX.

\PassOptionsToPackage{svgnames}{xcolor}
\documentclass{standalone}
\usepackage{pgfplots}

\pgfplotsset{width=\textwidth,compat=1.12}

\begin{document}

\begin{tikzpicture}
\begin{axis}[trig format plots=rad, width=5cm,axis equal,
             xmin = -1, xmax = 1,
             axis x line = none, axis y line = none]
  \addplot [variable=\t,domain=0:2*pi,samples=30,smooth]({cos t},{sin t});
%% θ(u) = 2πku, r(t) = t^c
  \addplot+ [scatter,scatter src=0.6*(1-\thisrowno{0}^0.125)+0.2,
             only marks,mark=*,mark size=0.001cm,colormap/blackwhite]
            table[header=false,
                  x expr=\thisrowno{0}^0.125*cos(7*pi/3+sign(\thisrowno{2}-0.5)*pi*\thisrowno{1}^0.5),
                  y expr=\thisrowno{0}^0.125*sin(7*pi/3+sign(\thisrowno{2}-0.5)*pi*\thisrowno{1}^0.5),
                 ]
                 {randtuple.dat}; % A file of three columns of random numbers from [0,1).
\end{axis}
\end{tikzpicture}

\end{document}

stippling of varying thickness small stippling of varying thickness large

For completeness, here’s the program that generated the random data, although it would be possible to generate it within pgfmath using rand:

#include <cmath>
#include <ctime>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <random>

using std::cout;

int main(void)
{
  static const ssize_t ncols = 1000;
  static const unsigned sigfigs = 17;
  static const unsigned width = 22;
  static const double interval = std::exp2(-64.0L);

  std::mt19937_64 rng( static_cast<std::mt19937_64::result_type>(
                         time(NULL)*CLOCKS_PER_SEC+clock() ) );

  cout.precision(sigfigs);

  for ( ssize_t i = 0; i < ncols; ++i ) {
    const double x = rng() * interval;
    const double y = rng() * interval;
    const double z = rng() * interval;

    cout << std::setw(width) << x << " "
         << std::setw(width) << y << " "
         << std::setw(width) << z << "\n";
  }

  return EXIT_SUCCESS;
}

We might also take columns of points (t,u) ∊ [0,1]×[0,1] drawn from a uniform random distribution, and map them to r(t) = t^c, θ(u) = 2πu.

And here’s what that looks like:

enter image description here

\PassOptionsToPackage{svgnames}{xcolor}
\documentclass{standalone}
\usepackage{pgfplots}

\pgfplotsset{width=\textwidth,compat=1.12}

\begin{document}

\begin{tikzpicture}
\begin{axis}[trig format plots=rad, width=5cm,axis equal,
             xmin = -1, xmax = 1,
             axis x line = none, axis y line = none]
  \addplot [variable=\t,domain=0:2*pi,samples=30,smooth]({cos t},{sin t});
%% θ(u) = 2πku, r(t) = t^c
  \addplot+ [scatter,scatter src=0.8*(1-\thisrowno{0}^0.125),
             only marks,mark=*,mark size=0.001cm,colormap/blackwhite]
            table[header=false,
                  x expr=\thisrowno{0}^0.125*cos(10*pi*\thisrowno{1}),
                  y expr=\thisrowno{0}^0.125*sin(10*pi*\thisrowno{1}),
                 ]
                 {randpairs.dat}; % A file of two columns of random numbers from [0,1).
\end{axis}
\end{tikzpicture}

\end{document}

And the old version

Maybe get a noisy distribution of points on a line, paramaterize those as a spiral that coils more tightly on the outside then the inside, e.g. for t ∊ [0,1], k>1, 0<c<1, θ(t) = 2πkt, r(t) = t^c, and plot them?

\PassOptionsToPackage{svgnames}{xcolor}
\documentclass{standalone}
\usepackage{pgfplots}

\pgfplotsset{width=\textwidth,compat=1.12}

\begin{document}

\begin{tikzpicture}
\begin{axis}[trig format plots=rad, width=5cm,axis equal,
             xmin = -1, xmax = 1,
             axis x line = none, axis y line = none]
  \addplot[variable=\t,domain=0:2*pi,samples=30,smooth]({cos t},{sin t});
%% θ(t) = 2πk·√t, r(t) = t^c
%% x(t) = r(t) cos θ(t) = t^c cos 2πk√t
%% y(t) = r(t) sin θ(t) = t^c sin 2πk√t
%% Where t ∊ [0,1], k>1, 0<c<1
%%
%% To eliminate the first half-revolution, solve for 2πk√t = π.  A prime
%% number in the sample size is less likely to produce unattractive patterns.
  \addplot+[variable=\t,domain=0.01:1,samples=193,
            only marks,mark=*,mark size=0.01cm,
            mark options={draw=DimGray,fill=DimGray}]
           ({t^0.125*cos(10*pi*t^0.5)},{t^0.125*sin(10*pi*t^0.5});
\end{axis}
\end{tikzpicture}

\end{document}

enter image description here

For simplicity, there’s no noise in the distribution of the points, but it still looks fairly nice to me.

Davislor
  • 44,045
  • Once I start doing grayscale, though, I guess I might as well just run a shading algorithm on each coordinate. But that might look too realistic here! – Davislor Sep 11 '15 at 20:39
  • +1 for using std::random, perhaps this (determinisitic) sequence can achieve more uniform covering by sacrificing a bit of randomness https://people.sc.fsu.edu/~jburkardt/cpp_src/sobol/sobol.html – alfC Sep 12 '15 at 19:12
  • Your C++ code is not so nice. I recommend using std::uniform_real_distribution. Also there is not reason for those constants to be static. Here is my approach: https://hastebin.com/raw/usojakivew – Henri Menke Apr 09 '18 at 21:26
  • @HenriMenke If I were writing that code today, I would do it differently. For example, those static const variables would be constexpr. All variables local to main() are now static by default, but that was not the case when I learned C and C++. Many older compilers allowed you to call main(), and by default would have allocated those variables on the stack without staticand perhaps not folded the constants. – Davislor Apr 09 '18 at 21:46
  • @HenriMenke Also, is there a reason you’re removing all the contractions? – Davislor Apr 09 '18 at 21:49
  • @Davislor Because they are not correct. You probably inferred them from the “it's” contraction for “it is” but “here” and “there” are not possessives. Even though common in speech, they are wrong when written. https://en.wikipedia.org/wiki/English_auxiliaries_and_contractions#Contracted_auxiliaries At least, that is what I infer from the Wikipedia article and that is how I learned it in school, but I'm not a native speaker so I might be wrong, in which case you should feel free to roll back. – Henri Menke Apr 09 '18 at 22:44
  • 1
    @Davislor Please add the syntax highlighting again after rolling back. That, I believe, is correct ;-) – Henri Menke Apr 09 '18 at 22:54
  • @HenriMenke Great idea! Done. – Davislor Apr 09 '18 at 23:02
26

Ok, here goes, compile with lualatex.

\documentclass{article}

\usepackage{tikz}
\usepackage{luacode}



\begin{document}


\begin{tikzpicture}
\draw (0,0) circle(5cm);
\begin{luacode*}
math.randomseed(os.time())
for i=1,1000 do
r=math.random()*3.145926535*2
s=math.random()+3.9
tex.print("\\draw[fill] (" .. s*math.cos(r) .. "," .. s*math.sin(r) ..") circle(0.2mm);")
end
 \end{luacode*}
\end{tikzpicture}

\end{document}

enter image description here

And a somewhat more sophisticated example:

\documentclass{article}

\usepackage{tikz}
\usepackage{luacode}



\begin{document}


\begin{tikzpicture}
\draw (0,0) circle(5cm);
\begin{luacode*}
math.randomseed(os.time())
for i=1,1000 do
r=math.sqrt(math.random())*3.145926535*2
s=math.pow(math.random(),0.2)+3.99
tex.print("\\draw[fill] (" .. s*math.cos(r) .. "," .. s*math.sin(r) ..") circle(0.2mm);")
end
 \end{luacode*}
\end{tikzpicture}

\end{document}

enter image description here

Better yet:

\documentclass{article}

\usepackage{tikz}
\usepackage{luacode}



\begin{document}


\begin{tikzpicture}
\draw (0,0) circle(5cm);
\begin{luacode*}
math.randomseed(os.time())
for i=1,1000 do
r=math.sqrt(math.random())*3.145926535*2
s=math.pow(math.random(),0.1)*4.99
tex.print("\\draw[fill] (" .. s*math.cos(r) .. "," .. s*math.sin(r) ..") circle(0.2mm);")
end
 \end{luacode*}
\end{tikzpicture}

\end{document}

enter image description here

Best:

\documentclass{article}

\usepackage{tikz}
\usepackage{luacode}



\begin{document}


\begin{tikzpicture}
\draw (0,0) circle(5cm);
\begin{luacode*}
math.randomseed(os.time())
for i=1,1000 do
if math.random() > 0.5 then b=1 else b= -1 end
r=(b*math.sqrt(math.random())+1)*math.pi
s=math.pow(math.random(),0.1)*4.99
tex.print("\\draw[fill] (" .. s*math.cos(r) .. "," .. s*math.sin(r) ..") circle(0.3mm);")
end
 \end{luacode*}
\end{tikzpicture}

\end{document}

enter image description here

JPi
  • 13,595
  • 1
    This was fun.... – JPi Sep 11 '15 at 01:39
  • Lua’s random number generator seems to favor higher numbers over lower ones. – Davislor Sep 11 '15 at 05:36
  • I'm drawing random numbers from a power distribution instead of a uniform. – JPi Sep 11 '15 at 08:34
  • And have indicated a range. – JPi Sep 11 '15 at 08:35
  • It’s introducing a visible discontinuity around θ=0. If we want the dots to be clustered around one side, maybe raised-cosine distribution? – Davislor Sep 11 '15 at 08:40
  • It's not a discontinuity, albeit that random number generators by necessity only have finite precision. The probability of drawing a number near zero is small for a power distribution. I could location shift the power distribution a bit, but I'm happy with the way it currently looks. – JPi Sep 11 '15 at 08:43
  • If you look at the right side of the sphere, there’s a really sharp boundary between the densely-shaded area right below θ = 0 and the lightly-shaded area right above. If you want the dots thicker on the bottom, which I didn’t duplicate, the distribution you’re using should be periodic. – Davislor Sep 11 '15 at 08:58
  • I haver not understood the code at all, but is 3.145926535 suppose to be an approximation of pi? -one digit is missing and the rounding is wrong pi \app 3.1415926536. – hpekristiansen Sep 11 '15 at 08:58
  • True, easy to do. Will do when I get a chance. – JPi Sep 11 '15 at 08:59
  • Right 89793.... ;) – JPi Sep 11 '15 at 09:02
  • @Hans: all it does is to draw a random number for the arc and one for the radius. The power and square root transformations are there to make the distribution uneven. – JPi Sep 11 '15 at 09:09
  • @Lorehead : done – JPi Sep 11 '15 at 12:38
19

I guess this is not exactly what you want. But it looks like pictures from old books as well.

I just applied ordered dithering to the sphere here.

Theoretically, given any functional shading, it is always possible to apply a dithering by a post script. (unless the stack overflows.)

The resolution is hard-coded, but still it is possible to change the resolution. (But it is meaningless if the resolution is too high.)

\documentclass[tikz,border=9]{standalone}

\pgfdeclarefunctionalshading{ordered dithering sphere}{\pgfpoint{0bp}{0bp}}{\pgfpoint{50bp}{50bp}}{}{
    2 copy
    %
    50 div 128 mul floor 63.5 sub 64 div exch
    50 div 128 mul floor 63.5 sub 64 div exch
    2 copy
    dup mul exch dup mul add sqrt 3 1 roll
    %
    2 copy 
    dup mul exch
    dup mul add
    1.0 sub
    0.3 dup mul
    -0.5 dup mul add
    1.0 sub
    mul abs sqrt
    exch 0.3 mul add
    exch -0.5 mul add
    dup abs add 2.0 div 
    0.6 mul 0.4 add
    %
    exch .98 ge {pop 1} if
    3 1 roll
    %
    50 div exch 50 div 1 % y x 1
    %
    3 1 roll 8 mul dup floor sub
    2 1 roll 8 mul dup floor sub
    3 2 roll
    %
    3 1 roll 2 mul dup floor dup 3 1 roll sub 3 2 roll 2 mul dup floor dup 3 1 roll sub
    4 3 roll 3 2 roll 2 copy -4 mul mul exch 3 mul add exch 2 mul add 4 mul 4 3 roll add
    %
    3 1 roll 2 mul dup floor dup 3 1 roll sub 3 2 roll 2 mul dup floor dup 3 1 roll sub
    4 3 roll 3 2 roll 2 copy -4 mul mul exch 3 mul add exch 2 mul add 16 mul 4 3 roll add
    %
    3 1 roll 2 mul dup floor dup 3 1 roll sub 3 2 roll 2 mul dup floor dup 3 1 roll sub
    4 3 roll 3 2 roll 2 copy -4 mul mul exch 3 mul add exch 2 mul add 64 mul 4 3 roll add
    %
    3 1 roll 2 mul dup floor dup 3 1 roll sub 3 2 roll 2 mul dup floor dup 3 1 roll sub
    4 3 roll 3 2 roll 2 copy -4 mul mul exch 3 mul add exch 2 mul add 256 mul 4 3 roll add
    %
    1025 div
    3 index
    le
    {1}{0}ifelse
    dup dup
}

\begin{document}
    \tikz\shade[shading=ordered dithering sphere](0,0)circle[radius=5cm];
\end{document}
Symbol 1
  • 36,855