% language=us runpath=texruns:manuals/luametafun \environment luametafun-style \startcomponent luametafun-envelopes \startMPdefinitions loadmodule("misc") ; \stopMPdefinitions \startchapter[title={Envelopes}] \startsection[title=Introduction] Envelopes are what \METAPOST\ makes for a non circular path. A circular path is supported directly by \POSTSCRIPT\ and \PDF. When such a oath is rotated, it is still somewhat easy because \METAPOST\ outputs the shape twice, transformed differently, but in the end we have one curve, and filling the right space the two curves bound which is native behavior of path filling. When the pen is more complex, that is not a transformed basic pencircle, \METAPOST\ will calculate a so called envelope. This chapter limits the explanation to what we can observe and better explanations about pens can be found in the \METAFONT\ book. \stopsection \startsection[title=Pens] The code involves is non trivial and can only work reliable for paths made from straight lines which which is why a pen is always reduced to a path with straight lines. Internally the term \quote {convex hull} is used. In \LUAMETATEX\ we have that operation as primitive. \startbuffer \startMPcode pen mypen ; mypen := makepen (fullcircle); draw origin withpen mypen scaled 100 withcolor "darkblue" ; \stopMPcode \stopbuffer \typebuffer[option=TEX] By drawing just one point we see the pen: \startlinecorrection \getbuffer \stoplinecorrection Indeed the circle has been simplified here. \startbuffer \startMPcode def ShowPaths(expr pth) = path p[] ; p[0] := pth scaled 50; p[1] := uncontrolled p[0] ; % show(p[1]); p[2] := convexed p[0] ; % show(p[2]); draw p[0] shifted ( 0,0) withpen pencircle scaled 5 withcolor "darkgreen" ; draw p[1] shifted (100,0) withpen pencircle scaled 5 withcolor "darkred" ; draw p[2] shifted (160,0) withpen pencircle scaled 5 withcolor "darkblue" ; draw p[1] shifted (260,0) withpen pencircle scaled 5 withcolor "darkred" ; draw p[2] shifted (260,0) withpen pencircle scaled 5 withcolor "white" ; enddef ; ShowPaths(fullcircle) ; \stopMPcode \stopbuffer \typebuffer[option=TEX] In this case the straightforward removal of control points gives the same result as first calculating the convex hull. \startlinecorrection \getbuffer \stoplinecorrection \startbuffer \startMPcode ShowPaths(fullcircle randomized .1) ; \stopMPcode \stopbuffer \typebuffer[option=TEX] In this example we still seem to get what we expect: \startlinecorrection \getbuffer \stoplinecorrection \startbuffer \startMPcode ShowPaths(fullcircle randomized .4) ; \stopMPcode \stopbuffer \typebuffer[option=TEX] But a bit of exaggeration shows that we don't get the same: \startlinecorrection \getbuffer \stoplinecorrection It all has to do with heuristics and nasty border cases when we turn corners. Here is what these (not randomized) paths look like, first the \type {uncontrolled}: \starttyping (25,0) .. controls (22.56,5.89) and (20.12,11.79) .. (17,68,17,68) .. controls (11.79,20.12) and (5.89,22.56) .. (0,25) .. controls (-5.89,22.56) and (-11.79,20.12) .. (-17,68,17,68) .. controls (-20.12,11.79) and (-22.56,5.89) .. (-25,0) .. controls (-22.56,-5.89) and (-20.12,-11.79) .. (-17,68,-17,68) .. controls (-11.79,-20.12) and (-5.89,-22.56) .. (0,-25) .. controls (5.89,-22.56) and (11.79,-20.12) .. (17,68,-17,68) .. controls (20.12,-11.79) and (22.56,-5.89) .. cycle \stoptyping and here is the \type {unconvexed}: \starttyping (-25,0) .. controls (-22.56,-5.89) and (-20.12,-11.79) .. (-17,68,-17,68) .. controls (-11.79,-20.12) and (-5.89,-22.56) .. (0,-25) .. controls (5.89,-22.56) and (11.79,-20.12) .. (17,68,-17,68) .. controls (20.12,-11.79) and (22.56,-5.89) .. (25,0) .. controls (22.56,5.89) and (20.12,11.79) .. (17,68,17,68) .. controls (11.79,20.12) and (5.89,22.56) .. (0,25) .. controls (-5.89,22.56) and (-11.79,20.12) .. (-17,68,17,68) .. controls (-20.12,11.79) and (-22.56,5.89) .. cycle \stoptyping Now, in order to see what convexing has to do with pens we also introduce a \quote {nep} which is a pen that doesn't get its path convexed. We mainly have this variant available for experimenting and documentation purposes. Take these definitions: \startbuffer \startMPdefinitions path PthP ; PthP := (fullcircle scaled 100) randomized 80 ; pen PenP ; PenP := makepen PthP ; nep NepP ; NepP := makenep PthP ; path ConP ; ConP := convexed PthP ; path UncP ; UncP := uncontrolled PthP ; \stopMPdefinitions \stopbuffer \typebuffer[option=TEX] \getbuffer That are used in: \startbuffer \startMPdefinitions def Pth = draw PthP ; enddef ; def Pen = draw origin withpen PenP withcolor "darkred" withtransparency (1,.5) ; enddef ; def Nep = draw origin withpen NepP withcolor "darkblue" withtransparency (1,.5); enddef ; def Con = fill ConP withpen pencircle scaled 0 withcolor "darkgreen" withtransparency (1,.5) ; enddef ; def Unc = fill UncP withpen pencircle scaled 0 withcolor "darkyellow" withtransparency (1,.5) ; enddef ; \stopMPdefinitions \stopbuffer \typebuffer[option=TEX] \getbuffer The main reason for showing the differences in \in {figure} [fig:trickyconvex] is that one should be aware of possible side effects \startbuffer[all] \startcombination [nx=3,ny=4] {\startMPcode draw image (Pen Nep Pth) ; \stopMPcode} {pen nep} {\startMPcode draw image (Pen Con Pth) ; \stopMPcode} {pen convexed} {\startMPcode draw image (Pen Unc Pth) ; \stopMPcode} {pen uncontrolled} {\startMPcode draw image (Nep Pen Pth) ; \stopMPcode} {nep pen} {\startMPcode draw image (Nep Con Pth) ; \stopMPcode} {nep convexed} {\startMPcode draw image (Nep Unc Pth) ; \stopMPcode} {nep uncontrolled} {\startMPcode draw image (Con Pen Pth) ; \stopMPcode} {convexed pen} {\startMPcode draw image (Con Nep Pth) ; \stopMPcode} {convexed nep} {\startMPcode draw image (Con Unc Pth) ; \stopMPcode} {convexed uncontrolled} {\startMPcode draw image (Unc Pen Pth) ; \stopMPcode} {uncontrolled pen} {\startMPcode draw image (Unc Nep Pth) ; \stopMPcode} {uncontrolled nep} {\startMPcode draw image (Unc Con Pth) ; \stopMPcode} {uncontrolled convexed} \stopcombination \stopbuffer \startplacefigure[title={Pens are paths with straight lines.},reference=fig:trickyconvex] \getbuffer[all] \stopplacefigure In case you doubt if all this matters, if we use a not to weird path, we're fine, as is demonstrated in \in {figure} [fig:okayconvex]; here we used \starttyping[option=MP] PthP := fullcircle yscaled 80 xscaled 140 rotated 45 ; \stoptyping \startbuffer \startMPdefinitions path PthP ; PthP := fullcircle yscaled 80 xscaled 140 rotated 45 ; pen PenP ; PenP := makepen PthP ; nep NepP ; NepP := makenep PthP ; path ConP ; ConP := convexed PthP ; path UncP ; UncP := uncontrolled PthP ; \stopMPdefinitions \stopbuffer \getbuffer \startplacefigure[title={When using decent pens the results will be consistent.},reference=fig:okayconvex] \getbuffer[all] \stopplacefigure And when we use such rather normal (non extreme) paths for pens we're ready for envelopes. \page \startsection[title=Usage] An envelop is the outline that we get when we run a pen over a path. An envelop is (of course) a closed path. Here is a simple example: \startbuffer \startMPcode path p ; p := origin -- (100,10) -- cycle ; path e ; e := envelope pensquare scaled 10 rotated 45 of p ; draw e withpen pencircle scaled 2 withcolor "darkred" ; draw p withpen pencircle scaled 2 withcolor "darkgray" ; fill e shifted (120,0) withcolor "darkred" ; draw p shifted (120,0) withcolor "lightgray" withpen pencircle scaled 2 ; fill e shifted (240,0) withshademethod "linear" withshadecolors ("darkred","lightgray") ; \stopMPcode \stopbuffer \typebuffer[option=TEX] This also demonstrates that this way you can apply a shade to a path: \startlinecorrection \getbuffer \stoplinecorrection One problem with envelopes is that you can get unexpected results so let's try to explore some details. We start by defining a main path, a pen, a path from the pen, and two envelopes. \startbuffer \startMPcode path PthP ; PthP := fullcircle xysized(10cm,2cm) ; pen PenP ; PenP := pensquare scaled 2mm rotated 45 ; path PthU ; PthU := fullsquare scaled 2mm rotated 45 ; path PatP ; PatP := makepath PenP ; path PthI ; PthI := envelope PenP of reverse PthP ; path PthO ; PthO := envelope PenP of PthP ; fill PthI && PthO && cycle withcolor "lightgray" ; draw PthI withcolor "darkred" ; draw PthO withcolor "darkgreen" ; draw PthP dashed evenly ; \stopMPcode \stopbuffer \typebuffer[option=TEX] Watch the difference between the two envelopes: one is the result from traveling the pen clockwise and one from running anti-clockwise: \startlinecorrection \getbuffer \stoplinecorrection We can emulate running the pen over the path: \startbuffer \startMPcode fill PthI && PthO && cycle withcolor "darkgray" ; fill for i within (arcpointlist 50 of PthP) : PatP shifted pathpoint && endfor cycle withcolor "middlegray" ; \stopMPcode \stopbuffer \typebuffer[option=TEX] Instead of drawing 50 paths, we draw an efficient single one made from 50 segments and we get this: \startlinecorrection \getbuffer \stoplinecorrection If you look closely at the first rendering you will notice an artifact in the inner envelope. \startlinecorrection \startMPcode draw PthI withpen pencircle scaled .4mm withcolor "darkred" ; \stopMPcode \stoplinecorrection We can get rid of this with a helper macro: \startbuffer \startMPcode draw reducedenvelope(PthI) withpen pencircle scaled .4mm withcolor "darkred" ; \stopMPcode \stopbuffer \typebuffer[option=TEX] Of course you get no guarantees but here it works: \startlinecorrection \getbuffer \stoplinecorrection One reason why the helper is not in the core is that it doesn't catch all cases: \startbuffer \startMPcode path p ; p := fullcircle scaled 4cm ; pen e ; e := pensquare scaled 3mm ; draw envelope e of p ; draw envelope e of reverse p ; p := p rotated eps shifted (5cm,0) ; draw envelope e of p ; draw envelope e of reverse p ; p := p shifted (5cm,0) ; draw p enveloped e ; draw (reverse p) enveloped e ; \stopMPcode \stopbuffer \typebuffer[option=TEX] Watch how a tiny rotations rid us of the weird rectangle, and the helper makes three extra inflected points go away but we're still stuck with an imperfection. \startlinecorrection \getbuffer \stoplinecorrection When we only fill the envelope we don't suffer from this'because the artifacts stay within the bounds. Sometimes rotating the pen by \type {eps} also helps. \startbuffer \startMPcode path p ; p := fullcircle scaled 4cm ; pen e ; e := pensquare scaled 3mm ; fill (envelope e of p) && (envelope e of reverse p) && cycle withcolor "darkblue" ; draw % just show the artifacts: (envelope e of p) && (envelope e of reverse p) && cycle withcolor "white" ; \stopMPcode \stopbuffer \typebuffer[option=TEX] \startlinecorrection \getbuffer \stoplinecorrection \stopsection \startsection[title=Details] For those who are interested in seeing what goes on behind the scenes, this section shows some examples that we made when writing an article about envelopes. We start with a couple of definitions \startbuffer \startMPdefinitions loadmodule("misc") ; path mypaths[] ; path mypens[] ; mypens[ 1] := fullcircle scaled 15mm ; mypens[ 2] := fulldiamond scaled 15mm ; mypens[ 3] := fulltriangle scaled 15mm ; mypens[ 4] := fullsquare scaled 15mm ; % randomized 4mm ; mypens[ 5] := starring(-1/3) scaled 15mm ; mypens[ 6] := starring(-1/2) scaled 15mm ; mypens[ 7] := starring(-eps) scaled 15mm ; mypens[ 8] := starring(1) scaled 15mm ; mypens[ 9] := starring(1/2) scaled 15mm ; mypens[10] := starring(eps) scaled 15mm ; mypaths[1] := fullcircle scaled 10cm ; mypaths[2] := ((0,0) -- (1/2,1/2) -- (2/2,0)) scaled 10cm ; mypaths[3] := ((0,0) -- (1/2,1/2) -- (2/2,0) -- cycle) scaled 10cm ; \stopMPdefinitions \stopbuffer \typebuffer[option=TEX] \getbuffer We are not going to use all these shapes and pens here but you might want to try out some yourself. We \in {Figure} [fig:envelope:1] we apply a so called \type {pensquare} to the paths. In \in {Figure} [fig:envelope:2] we use a star but \METAPOST\ will turn this one into a rectangle. In \in {Figure} [fig:envelope:3] we also use star but here the points are used. \startbuffer \startMPcode draw showenvelope(mypaths[1], mypens[4]) ; draw showenvelope(mypaths[2], mypens[4]) shifted (10cm, 1cm) ; draw showenvelope(mypaths[3], mypens[4]) shifted (10cm,-6cm) ; \stopMPcode \stopbuffer \typebuffer[option=TEX] \startplacefigure[title={How pen 4 creates an envelope.},reference=fig:envelope:1] \scale[width=1tw]{\getbuffer} \stopplacefigure \startbuffer \startMPcode draw showenvelope(mypaths[1], mypens[6]) ; draw showenvelope(mypaths[2], mypens[6]) shifted (10cm, 1cm) ; draw showenvelope(mypaths[3], mypens[6]) shifted (10cm,-6cm) ; \stopMPcode \stopMPcode \stopbuffer \typebuffer[option=TEX] \startplacefigure[title={How pen 6 creates an envelope.},reference=fig:envelope:2] \scale[width=1tw]{\getbuffer} \stopplacefigure \startbuffer \startMPcode draw showenvelope(mypaths[1], mypens[9]) ; draw showenvelope(mypaths[2], mypens[9]) shifted (10cm, 1cm) ; draw showenvelope(mypaths[3], mypens[9]) shifted (10cm,-6cm) ; \stopMPcode \stopbuffer \typebuffer[option=TEX] \startplacefigure[title={How pen 9 creates an envelope.},reference=fig:envelope:3] \scale[width=1tw]{\getbuffer} \stopplacefigure \stopsection \startsection[title=Reducing] If you watch the third shape in the previous examples, the last figure differs in that it has a symmetrical inner envelope. We can actually use this knowledge to define a pensquare that is better suited for envelopes. We take this example: \startbuffer \startMPdefinitions def ExamplePaths = path PthA ; PthA := fullcircle scaled 5cm ; path PthB ; PthB := triangle scaled 5cm ; draw envelope pensquare scaled 10mm of reverse PthA withpen pencircle scaled 2mm withcolor "darkblue" ; draw envelope pensquare scaled 10mm of reverse PthB withpen pencircle scaled 2mm withcolor "darkblue" ; draw (reverse PthA) enveloped (pensquare scaled 10mm) withpen pencircle scaled 2mm withcolor "darkred" ; draw (reverse PthB) enveloped (pensquare scaled 10mm) withpen pencircle scaled 2mm withcolor "darkred" ; enddef ; \stopMPdefinitions \stopbuffer \typebuffer[option=TEX] \getbuffer We define two renderings, one with the normal pensquare definition: \startbuffer[a] \startMPcode pensquare := makepen(unitsquare shifted -(.5,.5)) ; ExamplePaths ; \stopMPcode \stopbuffer \typebuffer[a][option=TEX] and one with an alternative definition where we have middle points on the edges that stick out one eps: \startbuffer[b] \startMPcode pensquare := makepen((starring(eps) scaled 1/2)) ; ExamplePaths ; \stopMPcode \stopbuffer \typebuffer[b][option=TEX] This gives \in {figure} [fig:envelope:4]. The blue extensions are what we get without clean up but at least the alternative has symmetrical ears. \startplacefigure[title={An alternative pensquare.},reference=fig:envelope:4] \startcombination[nx=2,ny=1] {\scale[width=.45tw]{\getbuffer[a]}} {default pensquare} {\scale[width=.45tw]{\getbuffer[b]}} {alternative pensquare} \stopcombination \stopplacefigure When you have a somewhat weird envelope the \type {reducedenvelope} macro might be able to improve it. The \typ { enveloped } primary macro has this built in. \stopsection \stopchapter \stopcomponent % \startMPdefinitions % interim tracereducedpath := false ; % interim scrutinizing := 3 ; % % path Paths[], Envelopes[] ; pen Pens[]; % % Paths[1] := reverse fullcircle scaled 200 rotated 30 xyscaled(2,1) ; % Paths[2] := reverse fullcircle scaled 200 ; % Paths[3] := fullcircle scaled 200 ; % % Paths[4] := reverse ( % (100,0){up} % .. xyrelative (100,100) % .. {left}(0,100) % .. xyrelative (-100,100) % .. {down}(-100,0) % .. xyrelative (-100,-100) % .. {right}(0,-100) % .. xyrelative (100,-100) % .. {up}cycle ); % % Paths[5] := reverse fullsquare rotated 80 scaled 200 ; % Paths[6] := fullsquare scaled 200 rotated 20 ; % % Paths[7] := for i = 0 step 50 until 200 : (i,uniformdeviate(100)) .. endfor nocycle ; % Paths[8] := for i = 0 step 50 until 2000 : (i,uniformdeviate(100)) .. endfor nocycle ; % % Pens[1] := makepen starring(eps) scaled 1/2 ; % Pens[2] := Pens[1] scaled 50 ; % Pens[3] := pensquare scaled 50 rotated eps ; % Pens[4] := makepen punked fullcircle scaled 50 ; % Pens[5] := makepen fulltriangle scaled 50 ; % % loadmodule "misc" ; % % % lessdigits := false; % % def MyTest(expr p, q) = % % show(Pens[q]); % % show(Paths[p]); % draw showreducedenvelope(Paths[p],Pens[q],decimal p,decimal q) % enddef ; % % def MyTest(expr p, q) = % draw Paths[p] enveloped Pens[q] withpen pencircle scaled 2 dashed evenly ; % enddef ; % % % def MyTest(expr p, q) = % % save pp ; path pp ; % % pp := Paths[p] enveloped Pens[q] ; % % fill pp withcolor .7[darkred,white] ; % % draw pp withpen pencircle scaled 2 withcolor darkred ; % % draw pp dashed evenly withcolor white ; % % draw Paths[p] withpen pencircle scaled 2 withcolor darkred ; % % enddef ; % % \stopMPdefinitions % % \starttext % % % \startMPpage[offset=1TS] MyTest(5,3) ; \stopMPpage % % \startMPpage[offset=1TS] MyTest(8,4) ; \stopMPpage % \dorecurse{8}{ % \startMPpage[offset=1TS] MyTest(#1,2) ; \stopMPpage % \startMPpage[offset=1TS] MyTest(#1,3) ; \stopMPpage % \startMPpage[offset=1TS] MyTest(#1,4) ; \stopMPpage % \startMPpage[offset=1TS] MyTest(#1,5) ; \stopMPpage % } % % \stoptext