# How to draw random simple closed smooth curves but with the same perimeter?

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.

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
• @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

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

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

• 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

Here's a Metapost effort.

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

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.

/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
sqrt
def
/currentx newx def
/currenty newy def
lineto}
{(curve) == curveto}
{/currentlength
currentlength
startx currentx sub dup mul
starty currenty sub dup mul
sqrt
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
• @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

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.