15

I want to draw some random simple smooth closed curves. Each smooth curve must have the same perimeter but different area. See the following figure to illustrate what I meant clearly. Each smooth curve is assumed to have the same perimeter.

enter image description here

How to do this with PSTricks (preferred) or TikZ or Metapost or Asymptote?

The following template is provided to save your time.

\documentclass{pstricks,border=12pt,12pt}

\begin{document}
\psLoop{10}{%
\begin{pspicture}(-5,-5)(5,5)
\end{pspicture}}
\end{document}
  • Closed curves? like circles, eclipses, or strings with different numbers of bends? – Ryan Dec 8 '14 at 3:24
  • @Ryan: Any closed curve without self-intersection. :-) – kiss my armpit Dec 8 '14 at 3:56
  • I'm curious -- what's the application, here? I can think of a few in topology (for teaching purposes), but I'm curious about what you have in mind :) – Sean Allred Dec 8 '14 at 15:56
  • See updated answer – David Carlisle Dec 8 '14 at 21:37
  • 1
    @SeanAllred: I just want to make an illustration for teaching calculus of variation by which we solve a famous problem (about searching for a simple closed smooth curve with the largest area). :-) – kiss my armpit Dec 9 '14 at 0:06
5

Here is a solution, which uses pst-intersect's ability to save a generic path. So, first a random path is generated (how is pretty much arbitrary), then it is loaded, its path length is calculated and then it is redrawn with scaled coordinates:

\documentclass[margin=5pt, pstricks]{standalone}
\usepackage{pst-intersect}
\makeatletter
\def\SaveRandomPath{%
  \pssavepath[linestyle=none, arrows=-,ArrowInside=-]{A}{%
    \moveto(! /@S Rand 1.5 mul def @S Rand mul 1 add 0 PtoC 2 copy /@Y ED /@X ED)
    \psparametricplot[plotpoints=35]{10}{350}{@S Rand mul 1 add t Rand 0.5 sub 5 mul add PtoC}%
    \lineto(!@X @Y)
  }%
}%
\def\TraceAndScaleCurve{\pst@object{TraceAndScaleCurve}}%
\def\TraceAndScaleCurve@i#1{%
  \begin@OpenObj
    \addto@pscode{%
      \pst@intersectdict
      \PIT@name{A} -1 -1
    {\psk@plotpoints exch
      \txFunc@BezierCurve
      \ifshowpoints \txFunc@BezierShowPoints \else pop \fi
    } 4 copy
    TraceCurveOrPath PathLength #1 div 
    dup 1 exch div dup scale 5 1 roll 
    newpath TraceCurveOrPath dup scale
  }%
  \end@OpenObj
}%
\makeatother
\begin{document}
\begin{pspicture}(-5,-5)(5,5)
\multido{\i=-4+2}{5}{%
  \multido{\ii=-4+2}{5}{%
    \SaveRandomPath
    \rput(\i,\ii){\TraceAndScaleCurve{150}}}}
\end{pspicture}
\end{document}

I haven't counted the number of keystrokes, but I thought I make it clearer rather than short and cryptic ;)

enter image description here

To make it smoother, one can tweak the parameters and use e.g. plotstyle=cspline, which gives results like

enter image description here

  • Could you make it look as smooth as the metapost version? – kiss my armpit Dec 10 '14 at 15:34
  • Yes, use e.g. plotstyle=cspline. This leaves you a possible sharp edge only where the curve is closed. – Christoph Dec 10 '14 at 18:58
  • Should be \begin@ClosedObj ... \end@ClosedObj – user2478 Dec 10 '14 at 19:48
  • I guess your starting point is the same as the ending point so there is always one cusp. The starting point should be different from the ending point to remove such a cusp. Am I correct? – kiss my armpit Dec 10 '14 at 21:09
  • No, the cspline plotstyle simply cannot handle periodic boundaries: the curvatures of the curve start and curve end should depend on each other, but they don't. – Christoph Dec 10 '14 at 22:21
16

Here's a Metapost effort.

enter image description here

The shapes above are all scaled to the same desired length, and show increasing levels of randomness from black to red. Here's the code:

prologues := 3;
outputtemplate := "%j%c.eps";

beginfig(1);
desired_length = 500;
N = 30;
r = 50;

path shape;
for s=0 step 1 until 11:
  shape := (r,0)
          for i=1 upto N-1: .. (r+s*normaldeviate,0) rotated (i/N*360) endfor
          .. cycle;
  shape := shape scaled (desired_length/arclength shape);
  draw shape shifted (4*((s*r) mod 200), 4r*floor((s*r)/200)) withcolor (s/12)[black,red];
endfor

endfig;
end.
  • Love it because of the fewest number of keystrokes. Thanks. – kiss my armpit Dec 9 '14 at 20:40
10

If I got the sums right then this calculates the length of each curve, given the length it then redraws the path, scaling everything so the length equals your target length.

enter image description here

/getpathlength
{flattenpath
{exch dup /startx exch def
 exch dup /starty exch def
 /currentx startx  def
 /currenty starty  def
 /currentlength 0 def
 moveto}
{exch dup /newx exch def
 exch dup /newy exch def
 /currentlength
 currentlength
 newx currentx sub dup mul
 newy currenty sub dup mul
 add
 sqrt
 add
 def
 /currentx newx def
 /currenty newy def
 lineto}
{(curve) == curveto}
{/currentlength
 currentlength
 startx currentx sub dup mul
 starty currenty sub dup mul
 add
 sqrt
 add
 def
 closepath
}
pathforall}
def


/redopath
{
{newpath starty sub pathscale mul starty add exch
         startx sub pathscale mul startx add exch moveto}
{        starty sub pathscale mul starty add exch
         startx sub pathscale mul startx add exch  lineto}
{(curveto) ==}
{closepath}
pathforall
}
def

/scalepathto
{
getpathlength
/pathscale exch 
currentlength div def
redopath
stroke}
def

newpath
100 200 moveto
10 10 rlineto
2 20 rlineto
30 -50  60 100 -100 0 rcurveto
closepath
400 scalepathto






newpath
200 400 moveto
20 20 rlineto
4 40 rlineto
60 -100  120 200 -200 0 rcurveto
closepath
400 scalepathto

newpath
300 500 moveto
20 10 rlineto
20 20 rlineto
4 40 rlineto
20 20  40 100 60 10 rcurveto
20 -20  40 -30 10 -60 rcurveto
closepath
400 scalepathto

newpath
400 400 20 0 360 arc
400 scalepathto


newpath
400 100 40 0 360 arc
400 scalepathto




showpage


quit
  • Are all the curves above smooth? :-) – kiss my armpit Dec 8 '14 at 23:42
  • @Whoiscrazyfirst You above all should know to define smooth if you mean it so ;-) – Sean Allred Dec 9 '14 at 0:07
  • 1
    @Whoiscrazyfirst well after flatternpath they're piecewise linear so it depends how you define smooth:-) but the point is that you can put 400 scalepathto after any closed path, it doesn't have to be smooth or non-intersecting, so it therefore applies to smooth non-intersecting paths if you choose to apply it to those. – David Carlisle Dec 9 '14 at 0:31
5

Please find a not perfect solution with Asymptote. Since the points are randomly generated it is not easy to have a non-intersecting path. So I remember some hull computation with some parameters. You can find it http://git.piprime.fr/?p=asymptote/pi-packages.git;a=blob;f=hull_pi.asy;hb=HEAD and some examples are on http://www.piprime.fr/developpeur/asymptote/unofficial-packages-asy/hull_pi_asy/. I have to admit that I do not understand very well all the parameters but you can obtain some non convex path. Each path is scaled to obtain a fixed perimeter (the natural idea described by D. Carlisle). The path is also transformed by a roundedpath routine (to smooth a little bit).

In the first version I was stupid. Here a shorter code, with the same result.

import hull_pi;
//    import stats;
import roundedpath;
size(10cm);

pair[] cloud;
int nbpt=55;
int depthMax=5;

// Generate random points.
for (int i=0; i < nbpt; ++i)
  cloud.push((10*unitrand(),10*unitrand()));

pair[] hull=hull(cloud,depthMin=0,depthMax=depthMax,angleMin=50,angleMax=200,3);

path s=roundedpath(polygon(hull),.1);
path snormalized=scale(40/arclength(s))*s;
draw(snormalized,blue+1bp);


pair[] cloud;
int nbpt=110;
int depthMax=10;

// Generate random points.
for (int i=0; i < nbpt; ++i)
  cloud.push((10*unitrand(),20*unitrand()));

pair[] hull=hull(cloud,depthMin=0,depthMax=depthMax,angleMin=60,angleMax=270,1);
path s=roundedpath(polygon(hull),.05);
path snormalized=scale(40/arclength(s))*s;
draw(shift(10,0)*snormalized,black+1bp);

pair[] cloud;
int nbpt=10;
int depthMax=10;

// Generate random points.
for (int i=0; i < nbpt; ++i)
  cloud.push((20*unitrand(),10*unitrand()));

pair[] hull=hull(cloud,depthMin=0,depthMax=depthMax,angleMin=60,angleMax=200,2);
path s=roundedpath(polygon(hull),.9);
path snormalized=scale(40/arclength(s))*s;
draw(shift(0,-10)*snormalized,red+1bp);

And the result.

enter image description here

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.