luametafun-envelope.tex /size: 17 Kb    last modification: 2025-02-21 11:03
1% language=us runpath=texruns:manuals/luametafun
2
3\environment luametafun-style
4
5\startcomponent luametafun-envelopes
6
7\startMPdefinitions
8    loadmodule("misc") ;
9\stopMPdefinitions
10
11\startchapter[title={Envelopes}]
12
13\startsection[title=Introduction]
14
15Envelopes are what \METAPOST\ makes for a non circular path. A circular path is
16supported directly by \POSTSCRIPT\ and \PDF. When such a oath is rotated, it is
17still somewhat easy because \METAPOST\ outputs the shape twice, transformed
18differently, but in the end we have one curve, and filling the right space the
19two curves bound which is native behavior of path filling. When the pen is more
20complex, that is not a transformed basic pencircle, \METAPOST\ will calculate a
21so called envelope. This chapter limits the explanation to what we can observe
22and better explanations about pens can be found in the \METAFONT\ book.
23
24\stopsection
25
26\startsection[title=Pens]
27
28The code involves is non trivial and can only work reliable for paths made from
29straight lines which which is why a pen is always reduced to a path with straight
30lines. Internally the term \quote {convex hull} is used. In \LUAMETATEX\ we have
31that operation as primitive.
32
33\startbuffer
34\startMPcode
35pen mypen ; mypen := makepen (fullcircle);
36draw origin withpen mypen scaled 100 withcolor "darkblue" ;
37\stopMPcode
38\stopbuffer
39
40\typebuffer[option=TEX]
41
42By drawing just one point we see the pen:
43
44\startlinecorrection \getbuffer \stoplinecorrection
45
46Indeed the circle has been simplified here.
47
48\startbuffer
49\startMPcode
50def ShowPaths(expr pth) =
51    path p[] ;
52    p[0] := pth scaled 50;
53    p[1] := uncontrolled p[0] ; % show(p[1]);
54    p[2] := convexed     p[0] ; % show(p[2]);
55    draw p[0] shifted (  0,0) withpen pencircle scaled 5 withcolor "darkgreen" ;
56    draw p[1] shifted (100,0) withpen pencircle scaled 5 withcolor "darkred"   ;
57    draw p[2] shifted (160,0) withpen pencircle scaled 5 withcolor "darkblue"  ;
58    draw p[1] shifted (260,0) withpen pencircle scaled 5 withcolor "darkred"  ;
59    draw p[2] shifted (260,0) withpen pencircle scaled 5 withcolor "white" ;
60enddef ;
61
62ShowPaths(fullcircle) ;
63\stopMPcode
64\stopbuffer
65
66\typebuffer[option=TEX]
67
68In this case the straightforward removal of control points gives the same result
69as first calculating the convex hull.
70
71\startlinecorrection \getbuffer \stoplinecorrection
72
73\startbuffer
74\startMPcode
75ShowPaths(fullcircle randomized .1) ;
76\stopMPcode
77\stopbuffer
78
79\typebuffer[option=TEX]
80
81In this example we still seem to get what we expect:
82
83\startlinecorrection \getbuffer \stoplinecorrection
84
85\startbuffer
86\startMPcode
87ShowPaths(fullcircle randomized .4) ;
88\stopMPcode
89\stopbuffer
90
91\typebuffer[option=TEX]
92
93But a bit of exaggeration shows that we don't get the same:
94
95\startlinecorrection \getbuffer \stoplinecorrection
96
97It all has to do with heuristics and nasty border cases when we turn corners. Here is
98what these (not randomized) paths look like, first the \type {uncontrolled}:
99
100\starttyping
101(25,0) .. controls (22.56,5.89) and (20.12,11.79)
102.. (17,68,17,68) .. controls (11.79,20.12) and (5.89,22.56)
103.. (0,25) .. controls (-5.89,22.56) and (-11.79,20.12)
104.. (-17,68,17,68) .. controls (-20.12,11.79) and (-22.56,5.89)
105.. (-25,0) .. controls (-22.56,-5.89) and (-20.12,-11.79)
106.. (-17,68,-17,68) .. controls (-11.79,-20.12) and (-5.89,-22.56)
107.. (0,-25) .. controls (5.89,-22.56) and (11.79,-20.12)
108.. (17,68,-17,68) .. controls (20.12,-11.79) and (22.56,-5.89)
109.. cycle
110\stoptyping
111
112and here is the \type {unconvexed}:
113
114\starttyping
115(-25,0) .. controls (-22.56,-5.89) and (-20.12,-11.79)
116.. (-17,68,-17,68) .. controls (-11.79,-20.12) and (-5.89,-22.56)
117.. (0,-25) .. controls (5.89,-22.56) and (11.79,-20.12)
118.. (17,68,-17,68) .. controls (20.12,-11.79) and (22.56,-5.89)
119.. (25,0) .. controls (22.56,5.89) and (20.12,11.79)
120.. (17,68,17,68) .. controls (11.79,20.12) and (5.89,22.56)
121.. (0,25) .. controls (-5.89,22.56) and (-11.79,20.12)
122.. (-17,68,17,68) .. controls (-20.12,11.79) and (-22.56,5.89)
123.. cycle
124\stoptyping
125
126Now, in order to see what convexing has to do with pens we also introduce a
127\quote {nep} which is a pen that doesn't get its path convexed. We mainly have
128this variant available for experimenting and documentation purposes. Take these
129definitions:
130
131\startbuffer
132\startMPdefinitions
133path PthP ; PthP := (fullcircle scaled 100) randomized 80 ;
134pen  PenP ; PenP := makepen      PthP ;
135nep  NepP ; NepP := makenep      PthP ;
136path ConP ; ConP := convexed     PthP ;
137path UncP ; UncP := uncontrolled PthP ;
138\stopMPdefinitions
139\stopbuffer
140
141\typebuffer[option=TEX] \getbuffer
142
143That are used in:
144
145\startbuffer
146\startMPdefinitions
147def Pth =
148    draw PthP  ;
149enddef ;
150def Pen =
151    draw origin withpen PenP withcolor "darkred" withtransparency (1,.5) ;
152enddef ;
153def Nep =
154    draw origin withpen NepP withcolor "darkblue" withtransparency (1,.5);
155enddef ;
156def Con =
157    fill ConP withpen pencircle scaled 0 withcolor "darkgreen" withtransparency (1,.5) ;
158enddef ;
159def Unc =
160    fill UncP withpen pencircle scaled 0 withcolor "darkyellow" withtransparency (1,.5) ;
161enddef ;
162\stopMPdefinitions
163\stopbuffer
164
165\typebuffer[option=TEX] \getbuffer
166
167The main reason for showing the differences in \in {figure} [fig:trickyconvex] is that
168one should be aware of possible side effects
169
170\startbuffer[all]
171\startcombination [nx=3,ny=4]
172   {\startMPcode draw image (Pen Nep Pth) ; \stopMPcode} {pen          nep}
173   {\startMPcode draw image (Pen Con Pth) ; \stopMPcode} {pen          convexed}
174   {\startMPcode draw image (Pen Unc Pth) ; \stopMPcode} {pen          uncontrolled}
175   {\startMPcode draw image (Nep Pen Pth) ; \stopMPcode} {nep          pen}
176   {\startMPcode draw image (Nep Con Pth) ; \stopMPcode} {nep          convexed}
177   {\startMPcode draw image (Nep Unc Pth) ; \stopMPcode} {nep          uncontrolled}
178   {\startMPcode draw image (Con Pen Pth) ; \stopMPcode} {convexed     pen}
179   {\startMPcode draw image (Con Nep Pth) ; \stopMPcode} {convexed     nep}
180   {\startMPcode draw image (Con Unc Pth) ; \stopMPcode} {convexed     uncontrolled}
181   {\startMPcode draw image (Unc Pen Pth) ; \stopMPcode} {uncontrolled pen}
182   {\startMPcode draw image (Unc Nep Pth) ; \stopMPcode} {uncontrolled nep}
183   {\startMPcode draw image (Unc Con Pth) ; \stopMPcode} {uncontrolled convexed}
184\stopcombination
185\stopbuffer
186
187\startplacefigure[title={Pens are paths with straight lines.},reference=fig:trickyconvex]
188    \getbuffer[all]
189\stopplacefigure
190
191In case you doubt if all this matters, if we use a not to weird path, we're
192fine, as is demonstrated in \in {figure} [fig:okayconvex]; here we used
193
194\starttyping[option=MP]
195PthP := fullcircle yscaled 80 xscaled 140 rotated 45 ;
196\stoptyping
197
198\startbuffer
199\startMPdefinitions
200path PthP ; PthP := fullcircle yscaled 80 xscaled 140 rotated 45 ;
201pen  PenP ; PenP := makepen      PthP ;
202nep  NepP ; NepP := makenep      PthP ;
203path ConP ; ConP := convexed     PthP ;
204path UncP ; UncP := uncontrolled PthP ;
205\stopMPdefinitions
206\stopbuffer
207
208\getbuffer
209
210\startplacefigure[title={When using decent pens the results will be consistent.},reference=fig:okayconvex]
211    \getbuffer[all]
212\stopplacefigure
213
214And when we use such rather normal (non extreme) paths for pens we're ready for
215envelopes.
216
217\page
218
219\startsection[title=Usage]
220
221An envelop is the outline that we get when we run a pen over a path. An envelop
222is (of course) a closed path. Here is a simple example:
223
224\startbuffer
225\startMPcode
226path p ; p := origin -- (100,10) -- cycle ;
227path e ; e := envelope pensquare scaled 10 rotated 45 of p ;
228
229draw e withpen pencircle scaled 2 withcolor "darkred" ;
230draw p withpen pencircle scaled 2 withcolor "darkgray" ;
231
232fill e shifted (120,0) withcolor "darkred" ;
233draw p shifted (120,0) withcolor "lightgray" withpen pencircle scaled 2  ;
234
235fill e shifted (240,0)
236    withshademethod "linear"
237    withshadecolors ("darkred","lightgray") ;
238\stopMPcode
239\stopbuffer
240
241\typebuffer[option=TEX]
242
243This also demonstrates that this way you can apply a shade to a path:
244
245\startlinecorrection
246\getbuffer
247\stoplinecorrection
248
249One problem with envelopes is that you can get unexpected results so let's try to
250explore some details. We start by defining a main path, a pen, a path from the
251pen, and two envelopes.
252
253\startbuffer
254\startMPcode
255path PthP ; PthP := fullcircle xysized(10cm,2cm) ;
256pen  PenP ; PenP := pensquare scaled 2mm rotated 45 ;
257path PthU ; PthU := fullsquare scaled 2mm rotated 45 ;
258path PatP ; PatP := makepath PenP ;
259
260path PthI ; PthI := envelope PenP of reverse PthP ;
261path PthO ; PthO := envelope PenP of PthP ;
262
263fill PthI && PthO && cycle withcolor "lightgray" ;
264
265draw PthI withcolor "darkred" ;
266draw PthO withcolor "darkgreen" ;
267draw PthP dashed evenly ;
268\stopMPcode
269\stopbuffer
270
271\typebuffer[option=TEX]
272
273Watch the difference between the two envelopes: one is the result from traveling
274the pen clockwise and one from running anti-clockwise:
275
276\startlinecorrection
277\getbuffer
278\stoplinecorrection
279
280We can emulate running the pen over the path:
281
282\startbuffer
283\startMPcode
284fill PthI && PthO && cycle withcolor "darkgray" ;
285fill
286    for i within (arcpointlist 50 of PthP) :
287        PatP shifted pathpoint &&
288    endfor cycle
289    withcolor "middlegray" ;
290\stopMPcode
291\stopbuffer
292
293\typebuffer[option=TEX]
294
295Instead of drawing 50 paths, we draw an efficient single one made from 50
296segments and we get this:
297
298\startlinecorrection
299\getbuffer
300\stoplinecorrection
301
302If you look closely at the first rendering you will notice an artifact in the inner
303envelope.
304
305\startlinecorrection
306\startMPcode
307draw PthI withpen pencircle scaled .4mm withcolor "darkred" ;
308\stopMPcode
309\stoplinecorrection
310
311We can get rid of this with a helper macro:
312
313\startbuffer
314\startMPcode
315draw reducedenvelope(PthI) withpen pencircle scaled .4mm withcolor "darkred" ;
316\stopMPcode
317\stopbuffer
318
319\typebuffer[option=TEX]
320
321Of course you get no guarantees but here it works:
322
323\startlinecorrection
324\getbuffer
325\stoplinecorrection
326
327One reason why the helper is not in the core is that it doesn't catch all cases:
328
329\startbuffer
330\startMPcode
331path p ; p := fullcircle scaled 4cm ;
332pen  e ; e := pensquare scaled 3mm ;
333draw envelope e of p ;
334draw envelope e of reverse p ;
335p := p rotated eps shifted (5cm,0) ;
336draw envelope e of p ;
337draw envelope e of reverse p ;
338p := p shifted (5cm,0) ;
339draw          p  enveloped e ;
340draw (reverse p) enveloped e ;
341\stopMPcode
342\stopbuffer
343
344\typebuffer[option=TEX]
345
346Watch how a tiny rotations rid us of the weird rectangle, and the helper makes three
347extra inflected points go away but we're still stuck with an imperfection.
348
349\startlinecorrection
350\getbuffer
351\stoplinecorrection
352
353When we only fill the envelope we don't suffer from this'because the artifacts
354stay within the bounds. Sometimes rotating the pen by \type {eps} also helps.
355
356\startbuffer
357\startMPcode
358path p ; p := fullcircle scaled 4cm ;
359pen  e ; e := pensquare scaled 3mm ;
360fill
361    (envelope e of p) && (envelope e of reverse p) && cycle
362    withcolor "darkblue" ;
363draw % just show the artifacts:
364    (envelope e of p) && (envelope e of reverse p) && cycle
365    withcolor "white" ;
366\stopMPcode
367\stopbuffer
368
369\typebuffer[option=TEX]
370
371\startlinecorrection
372\getbuffer
373\stoplinecorrection
374
375\stopsection
376
377\startsection[title=Details]
378
379For those who are interested in seeing what goes on behind the scenes, this
380section shows some examples that we made when writing an article about envelopes. We start with a  couple of definitions
381
382\startbuffer
383\startMPdefinitions
384loadmodule("misc") ;
385
386path mypaths[] ;
387path mypens[] ;
388
389mypens[ 1] := fullcircle     scaled 15mm ;
390mypens[ 2] := fulldiamond    scaled 15mm ;
391mypens[ 3] := fulltriangle   scaled 15mm ;
392mypens[ 4] := fullsquare     scaled 15mm ; % randomized 4mm ;
393mypens[ 5] := starring(-1/3) scaled 15mm ;
394mypens[ 6] := starring(-1/2) scaled 15mm ;
395mypens[ 7] := starring(-eps) scaled 15mm ;
396mypens[ 8] := starring(1)    scaled 15mm ;
397mypens[ 9] := starring(1/2)  scaled 15mm ;
398mypens[10] := starring(eps)  scaled 15mm ;
399
400mypaths[1] := fullcircle                               scaled 10cm ;
401mypaths[2] := ((0,0) -- (1/2,1/2) -- (2/2,0))          scaled 10cm ;
402mypaths[3] := ((0,0) -- (1/2,1/2) -- (2/2,0) -- cycle) scaled 10cm ;
403\stopMPdefinitions
404\stopbuffer
405
406\typebuffer[option=TEX] \getbuffer
407
408We are not going to use all these shapes and pens here but you might want to try
409out some yourself. We \in {Figure} [fig:envelope:1] we apply a so called \type
410{pensquare} to the paths. In \in {Figure} [fig:envelope:2] we use a star but
411\METAPOST\ will turn this one into a rectangle. In \in {Figure} [fig:envelope:3]
412we also use star but here the points are used.
413
414\startbuffer
415\startMPcode
416draw showenvelope(mypaths[1], mypens[4]) ;
417draw showenvelope(mypaths[2], mypens[4]) shifted (10cm, 1cm) ;
418draw showenvelope(mypaths[3], mypens[4]) shifted (10cm,-6cm) ;
419\stopMPcode
420\stopbuffer
421
422\typebuffer[option=TEX]
423
424\startplacefigure[title={How pen 4 creates an envelope.},reference=fig:envelope:1]
425    \scale[width=1tw]{\getbuffer}
426\stopplacefigure
427
428\startbuffer
429\startMPcode
430draw showenvelope(mypaths[1], mypens[6]) ;
431draw showenvelope(mypaths[2], mypens[6]) shifted (10cm, 1cm) ;
432draw showenvelope(mypaths[3], mypens[6]) shifted (10cm,-6cm) ;
433\stopMPcode
434\stopMPcode
435\stopbuffer
436
437\typebuffer[option=TEX]
438
439\startplacefigure[title={How pen 6 creates an envelope.},reference=fig:envelope:2]
440    \scale[width=1tw]{\getbuffer}
441\stopplacefigure
442
443\startbuffer
444\startMPcode
445draw showenvelope(mypaths[1], mypens[9]) ;
446draw showenvelope(mypaths[2], mypens[9]) shifted (10cm, 1cm) ;
447draw showenvelope(mypaths[3], mypens[9]) shifted (10cm,-6cm) ;
448\stopMPcode
449\stopbuffer
450
451\typebuffer[option=TEX]
452
453\startplacefigure[title={How pen 9 creates an envelope.},reference=fig:envelope:3]
454    \scale[width=1tw]{\getbuffer}
455\stopplacefigure
456
457\stopsection
458
459\startsection[title=Reducing]
460
461If you watch the third shape in the previous examples, the last figure differs in
462that it has a symmetrical inner envelope. We can actually use this knowledge to
463define a pensquare that is better suited for envelopes. We take this example:
464
465\startbuffer
466\startMPdefinitions
467def ExamplePaths =
468    path PthA ; PthA := fullcircle scaled 5cm ;
469    path PthB ; PthB := triangle   scaled 5cm ;
470
471    draw envelope pensquare scaled 10mm of reverse PthA
472        withpen pencircle scaled 2mm
473        withcolor "darkblue"
474    ;
475    draw envelope pensquare scaled 10mm of reverse PthB
476        withpen pencircle scaled 2mm
477        withcolor "darkblue"
478    ;
479
480    draw (reverse PthA) enveloped (pensquare scaled 10mm)
481        withpen pencircle scaled 2mm
482        withcolor "darkred"
483    ;
484    draw (reverse PthB) enveloped (pensquare scaled 10mm)
485        withpen pencircle scaled 2mm
486        withcolor "darkred"
487    ;
488 enddef ;
489\stopMPdefinitions
490\stopbuffer
491
492\typebuffer[option=TEX] \getbuffer
493
494We define two renderings, one with the normal pensquare definition:
495
496\startbuffer[a]
497\startMPcode
498pensquare := makepen(unitsquare shifted -(.5,.5)) ; ExamplePaths ;
499\stopMPcode
500\stopbuffer
501
502\typebuffer[a][option=TEX]
503
504and one with an alternative definition where we have middle points on the
505edges that stick out one eps:
506
507\startbuffer[b]
508\startMPcode
509pensquare := makepen((starring(eps) scaled 1/2)) ; ExamplePaths ;
510\stopMPcode
511\stopbuffer
512
513\typebuffer[b][option=TEX]
514
515This gives \in {figure} [fig:envelope:4]. The blue extensions are what we get
516without clean up but at least the alternative has symmetrical ears.
517
518\startplacefigure[title={An alternative pensquare.},reference=fig:envelope:4]
519    \startcombination[nx=2,ny=1]
520        {\scale[width=.45tw]{\getbuffer[a]}} {default pensquare}
521        {\scale[width=.45tw]{\getbuffer[b]}} {alternative pensquare}
522    \stopcombination
523\stopplacefigure
524
525When you have a somewhat weird envelope the \type {reducedenvelope} macro might
526be able to improve it. The \typ {<pth> enveloped <pen>} primary macro has this
527built in.
528
529\stopsection
530
531\stopchapter
532
533\stopcomponent
534
535% \startMPdefinitions
536% interim tracereducedpath := false ;
537% interim scrutinizing := 3 ;
538%
539% path Paths[], Envelopes[] ; pen Pens[];
540%
541% Paths[1] := reverse fullcircle scaled 200 rotated 30 xyscaled(2,1) ;
542% Paths[2] := reverse fullcircle scaled 200 ;
543% Paths[3] := fullcircle scaled 200 ;
544%
545% Paths[4] := reverse (
546%         (100,0){up}
547%      .. xyrelative (100,100)
548%      .. {left}(0,100)
549%      .. xyrelative (-100,100)
550%      .. {down}(-100,0)
551%      .. xyrelative (-100,-100)
552%      .. {right}(0,-100)
553%      .. xyrelative (100,-100)
554%      .. {up}cycle );
555%
556% Paths[5] := reverse fullsquare rotated 80 scaled 200 ;
557% Paths[6] := fullsquare  scaled 200 rotated 20 ;
558%
559% Paths[7] := for i = 0 step 50 until 200 : (i,uniformdeviate(100)) .. endfor nocycle ;
560% Paths[8] := for i = 0 step 50 until 2000 : (i,uniformdeviate(100)) .. endfor nocycle ;
561%
562% Pens[1] := makepen starring(eps) scaled 1/2 ;
563% Pens[2] := Pens[1] scaled 50 ;
564% Pens[3] := pensquare scaled 50 rotated eps ;
565% Pens[4] := makepen punked fullcircle scaled 50 ;
566% Pens[5] := makepen fulltriangle scaled 50 ;
567%
568% loadmodule "misc" ;
569%
570% % lessdigits := false;
571%
572% def MyTest(expr p, q) =
573%     % show(Pens[q]);
574%     % show(Paths[p]);
575%     draw showreducedenvelope(Paths[p],Pens[q],decimal p,decimal q)
576% enddef ;
577%
578% def MyTest(expr p, q) =
579%     draw Paths[p] enveloped Pens[q] withpen pencircle scaled 2 dashed evenly ;
580% enddef ;
581%
582% % def MyTest(expr p, q) =
583% %     save pp ; path pp ;
584% %     pp := Paths[p] enveloped Pens[q] ;
585% %     fill pp withcolor .7[darkred,white] ;
586% %     draw pp withpen pencircle scaled 2 withcolor darkred ;
587% %     draw pp dashed evenly withcolor white ;
588% %     draw Paths[p] withpen pencircle scaled 2 withcolor darkred ;
589% % enddef ;
590%
591% \stopMPdefinitions
592%
593% \starttext
594%
595% % \startMPpage[offset=1TS] MyTest(5,3) ; \stopMPpage
596% % \startMPpage[offset=1TS] MyTest(8,4) ; \stopMPpage
597% \dorecurse{8}{
598%     \startMPpage[offset=1TS] MyTest(#1,2) ; \stopMPpage
599%     \startMPpage[offset=1TS] MyTest(#1,3) ; \stopMPpage
600%     \startMPpage[offset=1TS] MyTest(#1,4) ; \stopMPpage
601%     \startMPpage[offset=1TS] MyTest(#1,5) ; \stopMPpage
602% }
603%
604% \stoptext
605