diff options
Diffstat (limited to 'Master/texmf-dist/metapost')
-rw-r--r-- | Master/texmf-dist/metapost/fiziko/fiziko.mp | 2163 |
1 files changed, 2163 insertions, 0 deletions
diff --git a/Master/texmf-dist/metapost/fiziko/fiziko.mp b/Master/texmf-dist/metapost/fiziko/fiziko.mp new file mode 100644 index 00000000000..cf134b0fed0 --- /dev/null +++ b/Master/texmf-dist/metapost/fiziko/fiziko.mp @@ -0,0 +1,2163 @@ +% fiziko 0.1.3 +% MetaPost library for physics textbook illustrations +% Copyright 2019 Sergey Slyusarev +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see <http://www.gnu.org/licenses/>. + +% https://github.com/jemmybutton/fiziko + +% +% Here we define some things of general interest +% + +pi := 3.1415926; +radian := 180/pi; + +vardef sin primary x = (sind(x*radian)) enddef; + +vardef cos primary x = (cosd(x*radian)) enddef; + +vardef log (expr n, b) = + save rv; + numeric rv; + if n > 0: + rv := (mlog(n)/mlog(b)); + else: + rv := 0; + fi; + rv +enddef; + +vardef arcsind primary x = angle((1+-+x,x)) enddef; + +vardef arccosd primary x = angle((x,1+-+x)) enddef; + +vardef arcsin primary x = ((arcsind(x))/radian) enddef; + +vardef arccos primary x = ((arccosd(x))/radian) enddef; + +vardef angleRad primary x = angle(x)/radian enddef; + +vardef dirRad primary x = dir(x*radian) enddef; + +% used here and there. + +vardef sign (expr x)= + if x > 0: 1 fi + if x < 0: -1 fi + if x = 0: 1 fi +enddef; + +% This is inverted `clip` + +primarydef i maskedWith p = +begingroup + save q, invertedmask, resultimage; + pair q[]; + path invertedmask; + picture resultimage; + resultimage := i; + q1 := ulcorner(i) shifted (-1, 1); + q3 := lrcorner(i) shifted (1, -1); + q2 := (xpart(q3), ypart(q1)); + q4 := (xpart(q1), ypart(q3)); + bp := ypart((ulcorner(p)--llcorner(p)) firstIntersectionTimes p); + invertedmask := (subpath (bp, length(p) + bp) of p) -- q1 -- q2 -- q3 -- q4 -- q1 -- cycle; + clip resultimage to invertedmask; + resultimage +endgroup +enddef; + +% +% Since metapost is somewhat unpredictable in determining where paths intersect, here's macro +% that returns first intersection times with first path (ray) priority. +% Actually, it is so in most cases, but sometimes second path can take precedence, +% so the macro just checks whether reversing 'q' changes something +% + +primarydef p firstIntersectionTimes q = +begingroup + save t; + pair t[]; + t1 := p intersectiontimes q; + t2 := p intersectiontimes reverse(q); + if xpart(t1) < xpart(t2): + t3 := t1; + else: + t3 := (xpart(t2), length(q) - ypart(t2)); + fi; + if xpart(t1) < 0: t3 := t2; fi; + t3 +endgroup +enddef; + +% This checks if point a is inside of closed path p + +primarydef a isInside p = +begingroup + save ang, v, i, rv, pp; + boolean rv; + pair pp[]; + ang := 0; + for i := 0 step 1/4 until (length(p)): + pp1 := (point i of p) - a; + pp2 := (point i + 1/4 of p) - a; + if (pp1 <> (0, 0)) and (pp2 <> (0, 0)): + v := angle(pp1) - angle(pp2); + if v > 180: v := v - 360; fi; if v < -180: v := v + 360; fi; + ang := ang + v; + fi; + endfor; + if abs(ang) > 355: + rv := true; + else: + rv := false; + fi; + rv +endgroup +enddef; + +% +% sometimes it's useful to put some arrows along the path. this macro puts them +% in the middles of the segments that have length no less than midArrowLimit; +% + +midArrowLimit := 1cm; + +def drawmidarrow (expr p) text t = +begingroup + save i, j, q; + path q; + j := 0; + for i := 1 upto length(p): + if arclength(subpath(i-1, i) of p) >= midArrowLimit: + q := subpath(j, i - 1/2) of p; + j := i - 1/2; + draw q t; + filldraw arrowhead q t; + fi; + endfor; + draw subpath(j, length(p)) of p t; +endgroup +enddef; + +% This macro marks angles, unsurprisingly + +def markAngle (expr a, o, b) (text t) = +begingroup + save p, an, d; + numeric an[], d[]; + pair p; + an1 := angle(a-o); + an2 := angle(b-o) - an1; + if (an2 < 0): an2 := an2 + 360; fi; + an3 := an1 + 1/2an2; + p := center(t); + d1 := abs(ulcorner(t)-lrcorner(t)); + if (an2 < 90) and (an2 > 0): + d2 := max(1/3cm, (d1/(abs(sind(an2))*1/3cm))*1/3cm); + else: + d2 := 1/3cm; + fi; + draw subpath (0, 8an2/360) of fullcircle scaled 2d2 rotated an1 shifted o withpen thinpen; + draw (t) shifted -p shifted o shifted (dir(an3)*(d2 + d1)); +endgroup +enddef; + +% +% Here we define some auxilary global variables +% + +% Offset path algorithm can subdivide original path in order to be more precise +offsetPathSteps := 4; + +% The following macro sets all the values related to minimal stroke width at once. +% It can be used to easily redefine all of them. +def defineMinStrokeWidth (expr msw) = + % We don't want to display strokes that are too thin to print. Default value + % is subject to change when needed. + minStrokeWidth := msw; + maxShadingStrokeWidth := 2minStrokeWidth; + + % At some point it's useless to display even dashes + minDashStrokeWidth := 1/3minStrokeWidth; + + % this value corresponds to particular dashing algorithm and is subject to change whenever this algorithm changes + minDashStrokeLength := 3minStrokeWidth; + + dashStrokeWidthStep := 1/5minDashStrokeWidth; + + % all the shading algorithms need to know how close lines should be packed + shadingDensity := 3maxShadingStrokeWidth; + + % here are some pens + pen thinpen, thickpen, fatpen; + + thinpen := pencircle scaled minStrokeWidth; + thickpen := pencircle scaled 3minStrokeWidth; + fatpen := pencircle scaled 6minStrokeWidth; +enddef; + +defineMinStrokeWidth(1/5pt); + +% here we set global light direction + +def defineLightDirection (expr ldx, ldy) = + pair lightDirection, lightDirectionVector; + lightDirection := (ldx, ldy); + lightDirectionVector := (sin(xpart(lightDirection)), sin(ypart(lightDirection))); +enddef; + +defineLightDirection(-1/8pi, 1/8pi); + +boolean shadowsEnabled; +shadowsEnabled := false; + +% +% To simplify further calculations we need subdivided original path +% + +vardef pathSubdivideBase (expr p, subdivideStep, i) = + save returnPath, sp; + path returnPath, sp; + returnPath := point i of p; + if i<length(p): + sp := subpath(i, i + subdivideStep) of p; + returnPath := returnPath .. controls (postcontrol 0 of sp) and (precontrol 1 of sp) .. pathSubdivideBase (p, subdivideStep, i + subdivideStep); + fi; + if (i = 0) and (cycle p): + (subpath(0, length(returnPath)-1) of returnPath) .. controls (postcontrol length(returnPath)-1 of returnPath) and (precontrol length(returnPath) of returnPath) .. cycle + else: + returnPath + fi +enddef; + +vardef offsetPathSubdivide (expr p) = + pathSubdivideBase(p, 1/offsetPathSteps, 0) +enddef; + +vardef pathSubdivide (expr p, n) = + pathSubdivideBase(p, 1/n, 0) +enddef; + +% +% This macro creates a template offset path to a straight line, so we can correct angles +% It might appear as if we need to calculate derivative of the function somehow, instead of mocking it +% but this function might be anything, function of coordinates of distance to some point etc., +% so consider this a lazy way to do the right thing. +% +% either offsetPathTime or offsetPathLength are intended to be used as arguments. offsetPathTime is for time and offsetPathLength is for distance +% + +vardef offsetPathTemplate (expr p, i) (text offsetFunction) = + save returnPath, offsetPathTime, offsetPathLength, instantDirection, nextDirection; + numeric offsetPathTime, offsetPathLength, currentAngle; + pair instantDirection, nextDirection; + path returnPath; + if (i <= length(p)): + offsetPathTime := i; + else: + offsetPathTime := length(p); + fi; + if (arclength(p) > 0): + offsetPathLength := arclength(subpath (0, i) of p)/arclength(p); + else: + offsetPathLength := 0; + fi; + returnPath := (arclength(subpath (0, i) of p), offsetFunction); + if (i < length(p)): + % this thing is glitchy, but should be more accurate + %if (arclength(subpath (0, i) of p) < arclength(subpath (0, i + 1/4) of p)): + % offsetPathTime := i + 1/4; + % offsetPathLength := arclength(subpath (0, i + 1/4) of p)/arclength(p); + % instantDirection := unitvector((arclength(subpath (0, i + 1/4) of p), offsetFunction) - point 0 of returnPath); + % offsetPathTime := i + 1; + % offsetPathLength := arclength(subpath (0, i + 1) of p)/arclength(p); + % nextDirection := (arclength(subpath (0, i + 1) of p), offsetFunction); + % offsetPathTime := i + 3/4; + % offsetPathLength := arclength(subpath (0, i + 3/4) of p)/arclength(p); + % nextDirection := unitvector(nextDirection - (arclength(subpath (0, i + 3/4) of p), offsetFunction)); + % returnPath := returnPath{instantDirection} .. {nextDirection}offsetPathTemplate(p, i + 1)(offsetFunction); + % returnPath := returnPath -- offsetPathTemplate(p, i + 1)(offsetFunction); + %else: + returnPath := returnPath -- offsetPathTemplate(p, i + 1)(offsetFunction); + %fi; + fi; + returnPath +enddef; + +% +% This macro creates offset path p based on previously built template q, instead of function itself +% It is loosely based on something called Tiller-Hanson heuristic as described here: +% http://math.stackexchange.com/questions/465782/control-points-of-offset-bezier-curve +% + +vardef offsetPathGenerate (expr p, q, i) = + save returnPath, c, d, pl, ps; + path returnPath, pl[]; + pair c[], d[]; + c1 := precontrol i of p; + c2 := point i of p; + c3 := postcontrol i of p; + if abs(c1-c2) = 0: + c1 := c2 shifted (c2-c3); + fi; + if abs(c3-c2) = 0: + c3 := c2 shifted (c2-c1); + fi; + if (abs(c1-c2) > 0) and (abs(c2-c3) > 0): + d1 := unitvector(c1-c2) rotated -90; + d2 := unitvector(c2-c3) rotated -90; + pl1 := (unitvector(c2-c1)--unitvector(c1-c2)) + scaled arclength(subpath (i - 1/2, i + 1/2) of p) + shifted (point i of p shifted (d1 scaled ypart(point i of q))); + pl2 := (unitvector(c2-c3)--unitvector(c3-c2)) + scaled arclength(subpath (i - 1/2, i + 1/2) of p) + shifted (point i of p shifted (d2 scaled ypart(point i of q))); + if (abs(angle(d1) - angle(d2)) > 2) and (xpart(pl1 intersectiontimes pl2) > 0): + c4 := pl1 intersectionpoint pl2; + else: + c4 := c2 shifted (d1 scaled ypart(point i of q)); + fi; + returnPath := c4; + else: + returnPath := c2 shifted (unitvector( (point i-1 of p) - (point i+1 of p) rotated -90) scaled ypart (point i of q)); + fi; + if i < length(p): + path ps; + ps := subpath (i, i + 1) of p; + c1 := point 0 of ps; + c2 := postcontrol 0 of ps; + c3 := precontrol 1 of ps; + c4 := point 1 of ps; + c5 := point 0 of returnPath; + if (abs(c3-c4)>0) + and (abs(c1-c2)>0) + and (abs(c1-c4)>0) + and (abs(direction i of q) > 0): + c6 := c4 shifted (unitvector(c4 - c3) rotated 90 scaled ypart(point i + 1 of q)); + c7 := (c2 - c1) scaled (abs(c5-c6)/abs(c1-c4)) rotated angle(direction i of q) shifted c5; + c8 := (c3 - c4) scaled (abs(c5-c6)/abs(c1-c4)) rotated angle(direction i + 1 of q) shifted c6; + returnPath := returnPath .. controls c7 and c8 .. offsetPathGenerate (p, q, i + 1); + else: + returnPath := returnPath -- offsetPathGenerate (p, q, i + 1); + fi; + fi; + returnPath +enddef; + +% +% Frontend for offsetPathGenerate and offsetPathTemplate +% + +vardef offsetPath (expr p)(text offsetFunction) = + offsetPathGenerate (p, offsetPathTemplate(p, 0)(offsetFunction), 0) +enddef; + +% +% Brush macro. It draws line with brush of variable width. +% For parts thicker than minStrokeWidth it uses offsetPath functions' +% results, for thiner parts it draws dashed lines of fixed width +% + +def brushGenerate (expr p, q, i) = +begingroup + save w, bp, bt, t; + numeric w[], t[]; + path bp[], bt; + bt := q; + w0 := (ypart(urcorner(bt))); + w1 := (ypart(lrcorner(bt))); + t := cutPathTime(bt, minStrokeWidth); + if ((w0 > minStrokeWidth) + and (w1 < minStrokeWidth) + and (t > 0) + and (t < length(p)) + and (arclength(p) > minDashStrokeLength) + and (i < 10)): + brushGenerate (subpath (0, t) of p, subpath (0, t) of q, i + 1); + brushGenerate (subpath (t, length(p)) of p, subpath (t, length(q)) of q, i + 1); + elseif (arclength(p) > 0): + if (w0 > 99/100minStrokeWidth) + and (w1 > 99/100minStrokeWidth): + bp1 := offsetPathGenerate (p, q yscaled 1/2, 0); + bp2 := offsetPathGenerate (p, q yscaled -1/2, 0); + fill bp1 -- reverse(bp2) -- cycle; + elseif (w0 < 101/100minStrokeWidth) and (w1 < 101/100minStrokeWidth): + thinBrushGenerate (p, q, 0) + fi; + fi; +endgroup +enddef; + +% +% macro for thin lines which are actually dashed +% + +def thinBrushGenerate (expr p, q, i) = +begingroup + save w, bp, bt, t, h, linecap; + numeric w[], t[]; + path bp[], bt; + bt := q; + w0 := (ypart(urcorner(bt))); + w1 := (ypart(lrcorner(bt))); + w2 := floor((1/2(w0 + w1))/dashStrokeWidthStep)*dashStrokeWidthStep; + t := cutPathTime(bt, w2); + bp1 := subpath (0, t) of p; + bp2 := subpath (t, length(p)) of p; + if (((w0 - w1) > dashStrokeWidthStep) and (i < 15)) + and ((arclength(bp1) > minDashStrokeLength) + or (arclength(bp2) > minDashStrokeLength)): + thinBrushGenerate (bp1, subpath (0, t) of q, i + 1); + thinBrushGenerate (bp2, subpath (t, length(q)) of q, i + 1); + else: + linecap := butt; + if (w2 > minStrokeWidth): + w2 := minStrokeWidth; + fi; + if (w2 >= minDashStrokeWidth) and (arclength(p) > 0): + draw p withpen thinpen dashed thinBrushPattern(w2, arclength(p)); + fi; + fi; +endgroup +enddef; + +% +% this macro returns path as a shaded edge +% + +vardef shadedEdge (expr p) = + image( + brushGenerate (p, + offsetPathTemplate (p, 0) ( + 1/2minStrokeWidth + 2*minStrokeWidth + * angleToLightness( + sphereAngleToAbsoulteAngle( + (angleRad(direction offsetPathTime of p), 1/2) + ), 0, point offsetPathTime of p + ) + ), 0); + ) +enddef; + +% +% Whenever we have brush thinner than minStrokeWidth we call this dash pattern macro +% + +vardef thinBrushPattern (expr w, l) = + save d; + numeric d[]; + d0 := w; + if d0 > minStrokeWidth: d0 := minStrokeWidth; fi; + % d1 is a result of some arbitrary function of line width + % we do not use simple linear function because minimal dash length + % also shouldn't be less than minStrokeWidth. + % After we get d1 other measurements are calculated, + % so filled area per unit length remains adequate and dashes are aligned + % with segments + d1 := (1/2minDashStrokeLength) + (((d0/minStrokeWidth)**2)*1/2minDashStrokeLength); + d1 := d1 + 1/2uniformdeviate(d1); + d2 := (minStrokeWidth - d0)*(d1/d0); + d3 := round(l/(d2 + d1)); + if (d3 < 1): d3 := 1; fi; + d4 := (l/d3)/(d2 + d1); + d1 := d1*d4; + d2 := d2*d4; + if (uniformdeviate(2) > 1): + dashpattern (on d1 off 2d2) + else: + dashpattern (off 2d2 on d1) + fi +enddef; + +% +% macro that actually draws line of variable width +% + +vardef brush (expr p) (text offsetFunction) = + image( + brushGenerate (p, offsetPathTemplate(p, 0)(offsetFunction), 0); + ) +enddef; + +% +% This macro generates tube between paths p and q, of variable width d +% Tube is subdivided into segments in such a way that within every segment +% we need 2**n lines to generate even fill +% + +def tubeGenerate (expr p, q, d, i) = +begingroup + save w, bw, k, t, tubeWidth, sp, currentPath, currentTubePath, currentDepth; + numeric w[], bw[], t, currentDepth; + path tubeWidth, sp, currentPath, currentTubePath; + tubeWidth := d yscaled 2; + w0 := (ypart(urcorner(tubeWidth))) - 1/1000; + w1 := (ypart(lrcorner(tubeWidth))) + 1/1000; + w2 := ceiling(log(w0/shadingDensity, 2)); + w3 := ceiling(log(w1/shadingDensity, 2)); + if ((w2 > w3) and (i<20)): + t := cutPathTime(tubeWidth, shadingDensity*(2**(w2-1))); + tubeGenerate (subpath (0, t) of p, subpath (0, t) of q, subpath (0, t) of d, i + 1); + tubeGenerate (subpath (t, length(p)) of p, subpath (t, length(q)) of q, subpath (t, length(d)) of d, i + 1); + else: + if (arclength(p) > 0) and (arclength(q) > 0): + bw1 := 2**w2; + currentTubePath := interpath (1/2, q, p); + for k := 0 upto bw1: + currentPath := interpath (k/bw1, q, p); + angleOnTube := arccos(((k/bw1)*2) - 1); + currentDepth := -abs((1-sin(angleOnTube))*w0); + if shadowsEnabled: + currentPath := shadowCut(currentPath, currentDepth); + fi; + brushGenerate (currentPath, + offsetPathTemplate(currentPath, 0)( + maxShadingStrokeWidth + if odd (k): * (abs(ypart(point offsetPathTime of tubeWidth)/bw1) - 1/2shadingDensity) fi + %* orderFade(xpart(unitvector(direction offsetPathTime of tubeWidth yscaled 1/2cos(angleOnTube))), k) % why was it even here? + * angleToLightness( + tubeAngleToAbsoulteAngle(( + angleOnTube, + angleRad(direction offsetPathTime of currentTubePath), + angleRad(direction offsetPathTime of tubeWidth yscaled 1/2) + )), currentDepth, point offsetPathTime of currentPath) + ), 0); + endfor; + fi; + fi; +endgroup +enddef; + +% +% This macro is analogous to tubeGenerate, but draws transverse strokes +% result is somewhat suboptimal for now, but in simple cases it works ok +% + +def tubeGenerateAlt (expr p, q, d) = +begingroup + save spth, lpth, currentPath, pos, t, pthdir, corr, o, l, i, j, k, tubeAngle, pathAngle, scorr, dt; + numeric l[]; + path spth, lpth, currentPath; + pos := 0; + j := 0; + forever: + dt := (xpart(point pos of d) + 1/2shadingDensity); + scorr := cosd(angle(direction xpart(d intersectiontimes ((dt, ypart(lrcorner(d))) -- (dt, ypart(urcorner(d))))) of d)); + t1 := arctime ((arclength(subpath(0, pos) of p)) + shadingDensity/scorr) of p; + t2 := arctime ((arclength(subpath(0, pos) of q)) + shadingDensity/scorr) of q; + if (arclength(subpath(pos, t1) of p) < arclength(subpath(pos, t1) of q)): + pthdir := -1; + t3 := t1; + else: + pthdir := 1; + t3 := t2; + fi; + corr := round(arclength(subpath(pos, t3) of if pthdir = 1: p else: q fi)/(shadingDensity/scorr)); + if (corr < 1): corr := 1; fi; + corr := (arclength(subpath(pos, t3) of if pthdir = 1: p else: q fi) - (corr*(shadingDensity/scorr)))/corr; + t3 := arctime (arclength(subpath(0, t3) of if pthdir = 1: q else: p fi) - 1/3corr) of if pthdir = 1: q else: p fi; + spth := subpath(pos, t3) of if pthdir = 1: q else: p fi; + lpth := subpath(pos, t3) of if pthdir = 1: p else: q fi; + tubeAngle := angleRad(direction 1/2[pos, t3] of d); + pathAngle := angleRad(direction 1/2 of interpath (1/2, spth, lpth)); + pos := t3; + l1 := round(arclength(lpth)/(shadingDensity/scorr)); + if (l1 < 1): l1 := 1; fi; + l2 := arclength(lpth)/(l1*(shadingDensity/scorr)); + for i := 0 upto l1 - 1: + j := j + 1; + k := i*(arclength(lpth)/l1); + currentPath := point (arctime k of lpth) of spth -- point (arctime k of lpth) of lpth; + currentPath := offsetPathSubdivide(currentPath); + brushGenerate ( + currentPath, + offsetPathTemplate(currentPath, 0)( + maxShadingStrokeWidth + * orderFade(offsetPathLength[1/l1, l2], j) + * angleToLightness( + tubeAngleToAbsoulteAngle(( + arccos(pthdir*((offsetPathLength*2)-1)), + pathAngle, + tubeAngle) + ), -2(1/2arclength(currentPath))+sqrt(1 - (2offsetPathLength - 1)**2)*(1/2arclength(currentPath)), point offsetPathTime of currentPath) + ) + , 0); + endfor; + exitif pos >= length(p); + endfor; +endgroup +enddef; + +% +% This macro converts some measurements of point on tube to absolute angle. +% Since there are three such measurements, macro gets them as as a single +% argument of "color" type, in case it will eventually appear as a result +% of some other macro. +% + +vardef tubeAngleToAbsoulteAngle (expr p) = + save a; + numeric a[]; + a1 := bluepart(p) + 1/2pi; + a2 := arccos(cos(redpart(p))*sin(a1)); + a3 := greenpart(p) + 1/2pi; + a4 := arccos((cos(a1) * cos(a3) - cos(a2) * sin(a3))*(99/100)); + a5 := arccos((cos(a1) * sin(a3) + cos(a2) * cos(a3))*(99/100)); + (a5, a4) +enddef; + +% +% frontends to simplify tube drawing. tubeOutline variable changes on every call +% of any tube frontend function and can be used afterwards. +% + +path tubeOutline; +boolean drawTubeEnds; +drawTubeEnds := true; + +vardef tube.l (expr p)(text offsetFunction)= + save q; + path q[]; + q0 := offsetPathSubdivide(p); + q1 := offsetPathTemplate(q0, 0)(offsetFunction); + q2 := offsetPathGenerate (q0, q1, 0); + q3 := offsetPathGenerate (q0, q1 yscaled -1, 0); + tubeOutline := q3--reverse(q2)--cycle; + image( + tubeGenerate (q2, q3, q1, 0); + if (cycle p) or (not drawTubeEnds): + draw q2 withpen thinpen; + draw q3 withpen thinpen; + else: + draw q2--reverse(q3)--cycle withpen thinpen; + fi; + ) +enddef; + +vardef tube.t (expr p)(text offsetFunction)= + save q; + path q[]; + q0 := offsetPathSubdivide(p); + q1 := offsetPathTemplate(q0, 0)(offsetFunction); + q2 := offsetPathGenerate (q0, q1, 0); + q3 := offsetPathGenerate (q0, q1 yscaled -1, 0); + tubeOutline := q3--reverse(q2)--cycle; + image( + tubeGenerateAlt (q2, q3, q1); + if (cycle p) or (not drawTubeEnds): + draw q2 withpen thinpen; + draw q3 withpen thinpen; + else: + draw q2--reverse(q3)--cycle withpen thinpen; + fi; + ) +enddef; + +vardef tube.e (expr p)(text offsetFunction)= + save q; + path q[]; + q0 := offsetPathSubdivide(p); + q1 := offsetPathTemplate(q0, 0)(offsetFunction); + q2 := offsetPathGenerate (q0, q1, 0); + q3 := offsetPathGenerate (q0, q1 yscaled -1, 0); + tubeOutline := q3--reverse(q2)--cycle; + if not drawTubeEnds: + image( + draw q2 withpen thinpen; + draw q3 withpen thinpen; + ) + else: + tubeOutline := q3--reverse(q2)--cycle; + tubeOutline + fi +enddef; + +% +% Sphere can be used as a cap for a tube, so it has same 2**n lines. +% + +vardef sphere.c (expr d) = + save currentCircle, origCircle, currentRadius, currentDepth, order, circleThickness; + path currentCircle, origCircle; + numeric currentRadius, currentDepth, order; + origCircle := fullcircle; + order := 2**ceiling(log((1/2d)/shadingDensity, 2)); + image( + draw fullcircle scaled d withpen thinpen; + for i := 1 upto order: + currentRadius := i/order; + currentCircle := origCircle scaled (currentRadius*d) rotated uniformdeviate (1/4pi); + if odd(i): + circleThickness := maxShadingStrokeWidth * ((abs(d - (shadingDensity*order)))/order); + else: + circleThickness := maxShadingStrokeWidth; + fi; + currentDepth:= -(1-sqrt(1-currentRadius**2))*(1/2d); + if shadowsEnabled: + currentCircle := shadowCut(currentCircle, currentDepth); + fi; + brushGenerate (currentCircle, + offsetPathTemplate (currentCircle, 0) ( + circleThickness + * angleToLightness( + sphereAngleToAbsoulteAngle( + (angleRad(direction offsetPathTime of currentCircle), currentRadius) + ), currentDepth, point offsetPathTime of currentCircle + ) + ), 0); + endfor; + ) +enddef; + +% +% Alternative sphere macro. It's all about latitudinal strokes. +% The idea is: when we have a sphere with evenly distributed parallel strokes +% we know how their density rises towards edge in a projection, +% so all we need to do is to fade lines correspondingly +% + +vardef sphere.l (expr d, lat) = + save p, a, x, y, sphlat, latrad, n, c, currentPath, nline, tlat; + path p[], currentPath, currentArc; + sphlat := 0; + nline := 0; + latrad := (2pi*lat/360); + n := ceiling((pi*1/2d)/shadingDensity); + if (cosd(lat) <> 0): tlat := (sind(lat)/cosd(lat)); fi; + image( + draw fullcircle scaled d withpen thinpen; + p0 := fullcircle rotated 90; + for nline := 1 upto n-1: + sphlat := nline*(pi/n); + if (sphlat + latrad < pi) and (sphlat + latrad > 0): + if (cosd(lat) <> 0): + if (sin(sphlat) <> 0): + x := tlat*(cos(sphlat)/sin(sphlat)); + else: + x := 0; + fi; + else: + if ((sphlat > 1/2pi) and (lat > 0)) or ((sphlat < 1/2pi) and (lat < 0)): + x := -2; + else: + x := 2; + fi; + fi; + if (abs(x) <= 1): + y := arcsin(x); + p1 := subpath(6 + 8y/2pi, 2 - 8y/2pi) of p0; + else: + p1 := p0; + fi; + if (x > -1) and (arclength(p1) > 0): + currentPath := (p1 scaled (d*sin(sphlat)) yscaled sind(lat)) shifted (0, 1/2d*cos(sphlat)*cosd(lat)); + currentPath := offsetPathSubdivide(currentPath); + brushGenerate(currentPath, + offsetPathTemplate(currentPath, 0)( + maxShadingStrokeWidth * orderFade( + sqrt(1 - + abs( + ypart(point offsetPathTime of currentPath)/(1/2d), + 1 - abs( + 1 - abs( + xpart(point offsetPathTime of currentPath) + /(1/2d) + ) + )**abs(sind(lat)) + )**2) + , nline) + * angleToLightness( + sphereAngleToAbsoulteAngle(( + ( + if (abs(point offsetPathTime of currentPath) > 0): + angleRad(point offsetPathTime of currentPath) + else: + 0 + fi + + 1/2pi), 2abs(point offsetPathTime of currentPath)/(d+1)) + ), 0, point offsetPathTime of currentPath) + ), 0); + fi; + fi; + endfor; + ) +enddef; + +vardef orderFade (expr v, n)= + save o; + if (v > 1/256): + o := 2**ceiling(log(1/v, 2)); + if ((n mod 1/2o) = 0): + if ((n mod o) = 0): + 1 + else: + (v*o) - 1 + fi + else: + 0 + fi + else: + 0 + fi +enddef; + +% +% This one converts point location on sphere to absolute angle +% + +vardef sphereAngleToAbsoulteAngle (expr p) = + save a; + numeric a[]; + a1 := xpart(p) - 1/2pi; + a2 := arcsin(ypart(p)); + a3 := arccos(sin(a2)*cos(a1)); + a4 := pi - arccos(sin(a2)*sin(a1)); + (a3, a4) +enddef; + +% +% Once we get two angles at some point of some surface, we can compute light intensity there. +% + +vardef angleToLightness (expr p, d, q) = + save returnValue, shiftedShadowPath; + path shiftedShadowPath; + if shadowsEnabled: + for i := 0 step 1 until numberOfShadows: + shiftedShadowPath := shadowPath[i] shifted (lightDirectionVector scaled (d-shadowDepth[i])); + if q isInside shiftedShadowPath: + returnValue := 1; + fi; + endfor; + fi; + if not known returnValue: + returnValue := (cos(xpart(p) + xpart(lightDirection))++cos(ypart(p) - ypart(lightDirection))); + returnValue := angleToLightnessPP(returnValue); + fi; + if returnValue > 1: + 1 + else: + returnValue + fi +enddef; + +vardef angleToLightnessPP (expr v) = + v**3 +enddef; + +% Shadows are global + +path shadowPath[]; +numeric shadowDepth[]; + +% Shadows either require high path resolution, or some points +% on a path in just the right place for shadows. +% This macro adds such points. + +vardef shadowCut (expr pathToCut, currentDepth)= + save shiftedShadowPath, pathShadowIntersection, pathShadowCut, currentPath; + path shiftedShadowPath, currentPath; + pair pathShadowIntersection; + numeric pathShadowCut; + currentPath := pathToCut; + for j := 0 step 1 until numberOfShadows: + shiftedShadowPath := shadowPath[j] shifted (lightDirectionVector scaled (currentDepth - shadowDepth[j])); + forever: + pathShadowIntersection := shiftedShadowPath firstIntersectionTimes currentPath; + pathShadowCut := ypart(pathShadowIntersection); + if (pathShadowCut > 1/10) and (pathShadowCut < length(currentPath) - 1/10): + currentPath := (subpath (0, pathShadowCut - 1/20) of currentPath) .. (subpath (pathShadowCut + 1/20, length(currentPath)) of currentPath); + fi; + shiftedShadowPath := subpath (xpart(pathShadowIntersection) + 1/5, length(shiftedShadowPath)) of shiftedShadowPath; + exitif (pathShadowCut = -1); + endfor; + endfor; + currentPath +enddef; + +% +% Several macros rely on cutting offset path template at given height. +% Taking cutting points closer to the middle gives better results, and that's +% just what this macro tries to do. +% + +vardef cutPathTime (expr p, h) = + save cutTime, d; + numeric cutTime[], d[]; + d1 := xpart(urcorner(p)); + d2 := xpart(ulcorner(p)); + if (d2 < d1): + d3 := 1/2(d1 + d2); + cutTime1 := ypart(((d3, h) -- (d1, h)) firstIntersectionTimes p); + cutTime2 := ypart(((d3, h) -- (d2, h)) firstIntersectionTimes p); + d4 := xpart (point cutTime1 of p); + d5 := xpart (point cutTime2 of p); + if abs(d4-d3) < abs(d5-d3): + cutTime3 := cutTime1 + else: + cutTime3 := cutTime2 + fi; + else: + cutTime3 := -1; + fi; + cutTime3 +enddef; + +% +% This macro calculates ray angle after refraction. It takes raw angles (one of ray — p and one of surface — q) +% and refraction indices ratio. Whether ray comes from opticaly denser material is determined by direction of q +% relative to that of p +% + +vardef refractionAngle (expr p, q, n) = + save a; + numeric a[]; + a0 := p - q; + if (sin(a0) < 0): + a1 := cos(a0 + pi) * n; + a2 := pi; + else: + a1 := cos(a0) / n; + a2 := 0; + fi; + if abs(a1) <= 1: + a3 := arccos(a1) + q + a2; + else: + a3 := -1000; + fi; + a3 +enddef; + +% +% Same thing for reflection angle, just in case +% + +vardef reflectionAngle (expr p, q) = + (2pi - p + 2q) +enddef; + +% +% This macro returns path of ray 'sa' (which can actually be any path, but only ray from next to last to last point +% will count) refracted with coef. n through some shape p; if ray can't be refracted and, therefore, totally reflected, +% it will contunue as reflected from that point. i is total number of refractions to compute; +% + +vardef refractionPathR (expr sa, p, n, i, mn) = + save ray, resultRay, d, s, a, iT; + path ray, resultRay; + pair s, iT; + numeric d[], a; + s := point (length(sa) - 1) of sa; + a := angleRad((point (length(sa)) of sa)-(point (length(sa) - 1) of sa)); + ray := (s shifted (-dirRad(a) scaled 2)) -- s -- (s shifted (dirRad(a) scaled (abs(llcorner(p)-(urcorner(p))) + abs(s-(center(p)))))); + if (i > 0): ray := subpath (1 + 1/1000, 2) of ray; fi; + iT := ray firstIntersectionTimes p; + d1 := xpart(iT); + d2 := ypart(iT); + d3 := a; + d4 := angleRad(direction d2 of p); + if (n > 0): + d5 := refractionAngle(d3, d4, n); + if (d5 < -100) and (d2 >= 0): + d5 := reflectionAngle(d3, d4); + fi; + else: + d5 := reflectionAngle(d3, d4); + fi; + if (d1 >= 0) and (i < mn) and (d5 > -100): + resultRay := (subpath (0, length(sa) - 1) of sa) -- refractionPathR(point d2 of p -- (point d2 of p shifted dirRad(d5)), p, n, i + 1, mn); + else: + if (d5 > -100) or (d1 < 0): + resultRay := subpath (0, 1/2) of ray; + else: + resultRay := subpath (0, d1) of ray; + fi; + fi; + resultRay +enddef; + +vardef refractionPath (expr sa, p, n) = + refractionPathR(sa, p, n, 0, 10) +enddef; + +% +% These macros are for isolines. cLine draws continuous line and is called by isoLines. +% For now they are only used to draw wood texture, but can be used elsewhere +% + +% +% isoLines goes through i by j matrix of nodes (xy), looking for square, that has some of it's +% angles below zero and some - above, when found, it calls cLine, that tries to build segment of +% isoline, that happen to go through abovementioned square. Thickness of line is +% controlled by values in v array. +% All squares with lines already drawn through are ignored. +% + +vardef isoLines (suffix xy)(expr cs, l, s) = + save xxyy, i, j, c, v, sqB, iL, lvl; + numeric xxyy[][], c[], v[], sqB, sqbM; + lvl := l; + path iL; + image( + for i := 0 step 1 until xpart(cs) - 1: + for j := 0 step 1 until ypart(cs) - 1: + if (unknown xxyy[i][j]): + c1 := xy[i][j]+lvl; + c2 := xy[i][j+1]+lvl; + c3 := xy[i+1][j]+lvl; + c4 := xy[i+1][j+1]+lvl; + sqB := 0; + sqBm := 0; + if (abs(sign(c1)+sign(c2)+sign(c3)+sign(c4)) < 4): + iL := cLine (xy)((i, j), (0, 0), 0, cs) scaled s; + brushGenerate (reverse(iL), + offsetPathTemplate(iL, 0)( + 1/16minStrokeWidth + /(1/64 + 2( + if (offsetPathTime < length(iL) - 1): + (offsetPathTime - floor(offsetPathTime)) + [v[floor(sqB + offsetPathTime)], + v[ceiling(sqB + offsetPathTime)]] + else: + 1 + fi + )) + ) + , 0); + fi; + fi; + endfor; + endfor; + draw (0,0); + ) +enddef; + +% cLine tries to generate continouos segment of an isoline + +vardef cLine (suffix xy)(expr ij, dr, st, cs) = + save p, d, dd, n, i, j, k, outputPath, sqS, cp, dp, nd; + pair p[], d[], dd[], cp[], dp[]; + path outputPath; + i := xpart(ij); + j := ypart(ij); + sqS := 0; + if (i >= 0) and (i <= xpart(cs)-1) and (j >= 0) and (j <= ypart(cs)-1): + n := 0; + c1 := xy[i][j] + lvl; d1:= (i, j); + c2 := xy[i][j+1] + lvl; d2:= (i, j+1); + c3 := xy[i+1][j] + lvl; d3:= (i+1, j); + c4 := xy[i+1][j+1] + lvl; d4:= (i+1, j+1); + cp1 := (1, 2); dp1 := (-1, 0); + cp2 := (1, 3); dp2 := (0, -1); + cp3 := (2, 4); dp3 := (0, 1); + cp4 := (3, 4); dp4 := (1, 0); + for k := 1 upto 4: + c5 := c[xpart(cp[k])]; d5 := d[xpart(cp[k])]; + c6 := c[ypart(cp[k])]; d6 := d[ypart(cp[k])]; + if (sign(c5)) <> (sign(c6)): + n := n + 1; + p[n] := (abs(-c5/(c6-c5)))[d5, d6]; + dd[n] := dp[k]; + fi; + endfor; + sqS := max(c1, c2, c3, c4) - min(c1, c2, c3, c4); + if (unknown xxyy[i][j]): + xxyy[i][j] := 1; + if (dr = (0, 0)): + outputPath := cLine (xy)(ij shifted dd2, dd2, st + 1,cs) -- p1 -- p2 -- cLine (xy)(ij shifted dd1, dd1, st - 1,cs); + else: + nd := 0; + if (unknown(xxyy[i + xpart(dd1)][j + ypart(dd1)])): + nd := 1; + elseif (unknown(xxyy[i + xpart(dd2)][j + ypart(dd2)])): + nd := 2; + fi; + if nd > 0: + outputPath := cLine (xy)(ij shifted dd[nd], dd[nd], st + sign(st),cs); + p3 := p[nd]; + if (st > 0): + outputPath := outputPath -- p3; + else: + outputPath := p3 -- outputPath; + fi; + else: + outputPath := 1/2[p1, p2]; + fi; + fi; + else: + outputPath := 1/2[p1, p2]; + fi; + else: + outputPath := (i, j); + xxyy[i][j] := 1; + fi; + if (st < sqB): sqB := st; fi; + if (st > sqBm): sqBm := st; fi; + v[st] := sqS; + outputPath +enddef; + +% +% +% +% In following section are gathered all the things for some commonly used +% real life objects +% +% +% + +% +% Though there's a decent library to deal with geography for mp already +% (http://melusine.eu.org/lab/bmp/a_mp-geo), here's some basic globe-drawing +% routine for simple cases, note that latitude starts from the pole, +% not from the equator (for convenience sake) +% Below some landmasses are defined +% + +path landmass[]; +landmass1 := (206, 122.33)--(211.07, 116)--(213.3, 109.94)--(218.57, 106.03)--(218.38, 97.36)--(220.28, 91.28)--(229.75, 78.07)--(221.41, 78.29)--(220.78, 76.52)--(218.07, 74.48)--(213.8, 66.08)--(213.38, 62.04)--(222.31, 77.1)--(233.88, 72.27)--(237.79, 68.59)--(234.88, 64.69)--(229.83, 65.57)--(228.98, 64.73)--(227.37, 59.82)--(250.57, 68.12)--(254.63, 80.83)--(257.07, 80.93)--(257.38, 80.52)--(258.64, 75.5)--(266.4, 68.48)--(269.56, 67.49)--(271.88, 70.43)--(272.67, 74.49)--(275.36, 72.94)--(276.87, 78.6)--(276.68, 79.04)--(276.11, 79.28)--(276.3, 80.22)--(276.75, 79.96)--(276.56, 82.38)--(277.05, 82.04)--(280.5, 86.44)--(277.25, 85.56)--(276.55, 88.03)--(279.47, 92.77)--(283.29, 92.25)--(282.68, 90.91)--(283.74, 90.4)--(282.53, 89.58)--(283.03, 88.6)--(278.44, 80.08)--(279.15, 76.64)--(281.08, 78.25)--(282.29, 80.21)--(285.35, 79.72)--(288, 77.83)--(284.21, 71.22)--(287.94, 68.57)--(288, 68.6)--(288.74, 69.82)--(300.09, 61.89)--(300.86, 59.94)--(299.36, 59.63)--(297.64, 55.13)--(301.24, 52.55)--(296.1, 51.5)--(300.45, 49.51)--(299.83, 50.75)--(299.84, 50.82)--(299.44, 51.42)--(303.59, 50.57)--(302.72, 51.9)--(302.96, 52.12)--(304.97, 52.87)--(304.12, 55.13)--(307.89, 53.38)--(306.37, 50.11)--(308.65, 47.92)--(315.01, 45.12)--(319.69, 40.31)--(320.43, 44.25)--(321.66, 44.31)--(323.19, 41.66)--(320.37, 35.59)--(318.47, 37.21)--(315.99, 36.32)--(313.68, 35.16)--(320.43, 31.11)--(332.73, 30.38)--(338.5, 28.24)--(340.91, 28.61)--(334.92, 32.27)--(335, 39.2)--(340.58, 35.32)--(341.69, 32.15)--(340.43, 31.93)--(344.49, 29.68)--(352.49, 28.33)--(355.9, 25.35)--(358.67, 24.01)--(366.1, 25.61)--(368.78, 23.99)--(319.11, 17.34)--(309.82, 19)--(308.23, 18.4)--(307.69, 16.74)--(297.49, 16.63)--(290.61, 13.26)--(285.38, 13.37)--(284.06, 12.79)--(258.59, 16.61)--(260.79, 18.13)--(254.13, 18.01)--(253.53, 17.04)--(252.25, 17.02)--(252.44, 18.56)--(253.69, 19.64)--(251.71, 20.89)--(249.66, 16.97)--(245.54, 19.39)--(236.64, 19.73)--(239.08, 21)--(237.57, 21.46)--(232.4, 21.62)--(232.29, 21.34)--(225.16, 22.27)--(221.46, 21.23)--(218.52, 25.09)--(216.81, 24.69)--(214.76, 24.93)--(214.95, 25.52)--(213.66, 25.25)--(211.67, 23.33)--(215.44, 23.49)--(217.75, 21.65)--(200.52, 19.57)--(194.37, 21.1)--(186.19, 26.3)--(183.33, 30.4)--(187.61, 31.42)--(191.44, 33.88)--(194.61, 33.54)--(197.17, 30.74)--(196.08, 28.46)--(196.04, 27.66)--(203.54, 24.58)--(203.45, 24.88)--(200.38, 27.18)--(200.91, 27.54)--(200.05, 29.69)--(199.62, 29.91)--(201.03, 30.25)--(207.36, 29.93)--(205.2, 31.05)--(199.88, 30.97)--(199.94, 31.44)--(200.26, 32.2)--(202.19, 31.76)--(202.85, 32.2)--(199.62, 32.83)--(199.15, 34.61)--(189.46, 35.87)--(189.93, 35.46)--(191.12, 35.08)--(190.83, 34.56)--(188.1, 33.45)--(186.87, 34.75)--(187.11, 36.02)--(176.39, 40.19)--(176.65, 41.24)--(173.41, 42.02)--(176.82, 43.77)--(169.68, 46.56)--(169.15, 53.05)--(171.1, 53.62)--(173.12, 53.39)--(178.7, 51.26)--(183.17, 46.73)--(186.38, 46.75)--(192.72, 49.52)--(191.46, 52.64)--(193.74, 52.83)--(196.74, 50.32)--(190.71, 44.65)--(191.74, 44.4)--(198.11, 50.06)--(198.89, 52.03)--(200.95, 53.75)--(202.49, 51.99)--(201.15, 49.3)--(204.15, 49.28)--(206.54, 53.44)--(214.39, 53.25)--(211.18, 58.75)--(198.36, 57.7)--(197.88, 59.47)--(188.5, 55.87)--(189.63, 53.18)--(189.49, 52.79)--(173.31, 54.75)--(168.56, 58.21)--(161.34, 69.75)--(160.58, 75.18)--(161.25, 77.58)--(162.08, 79.09)--(163.71, 80.23)--(165.04, 82.46)--(168.88, 84.86)--(182.72, 83.77)--(184.88, 85.79)--(187.22, 85.99)--(186.79, 90.34)--(190.56, 95.97)--(190.23, 105.47)--(193.05, 115.74)--(196.18, 121.46)--(196.92, 124.65)--(206, 122.33)--(206, 122.33); +landmass2 := (111.44, 45.06)--(113.41, 44.75)--(111.77, 46)--(111.77, 46.07)--(118.69, 43.98)--(118.13, 42.88)--(116.49, 43.6)--(114.48, 42.7)--(114.1, 43.65)--(114.04, 41.9)--(113.28, 42.04)--(108.57, 42.18)--(114.57, 39.81)--(120.91, 38.84)--(119.04, 41.38)--(119.53, 41.59)--(122.9, 42.64)--(121.94, 42.77)--(121.82, 43.31)--(124.3, 42.48)--(125.29, 42.37)--(125.59, 41.84)--(125.41, 41.3)--(124.75, 41.38)--(122.58, 40.38)--(122.97, 39.95)--(121.71, 40.06)--(123.02, 38.38)--(121.65, 38.53)--(120.78, 35.69)--(116.8, 33.48)--(114.09, 29.59)--(110.59, 31.42)--(108.79, 28.81)--(104.18, 27.54)--(99.54, 29.42)--(100.85, 30.15)--(100.61, 31.83)--(100.51, 34.6)--(98.7, 35.56)--(99.11, 36.37)--(99.33, 38.41)--(96.3, 36.78)--(85.98, 32.83)--(83.79, 29.73)--(86.02, 28)--(87.63, 26.53)--(92.02, 23.6)--(94.53, 23.9)--(94.57, 23.59)--(95.6, 23.75)--(96.08, 21.63)--(96.05, 20.47)--(99.61, 19.73)--(103.5, 21.33)--(105.9, 22.76)--(103.75, 23.87)--(104.54, 24.37)--(101.78, 25.83)--(112.52, 27.74)--(113.4, 27.33)--(113.39, 27.23)--(110.6, 24.25)--(110.62, 24.18)--(111.27, 23.6)--(116.34, 23.81)--(117.17, 23.3)--(110.5, 21.34)--(111.78, 20.87)--(110.82, 20.4)--(111.3, 20.21)--(109.74, 19.84)--(110.11, 19.43)--(102.09, 17.29)--(97.38, 16.64)--(92.88, 17.67)--(92.21, 18.01)--(91.83, 17.28)--(89.16, 18.82)--(92.76, 20.05)--(93.22, 20.96)--(92.06, 21.98)--(91.69, 22.39)--(88.11, 21.59)--(87.79, 20.91)--(86.11, 20.22)--(86.94, 19.77)--(84.28, 18.1)--(84.81, 17.32)--(85.92, 16.37)--(82.62, 16.82)--(82.26, 18.46)--(82.22, 20.63)--(80.29, 21.42)--(73.81, 21.72)--(73.19, 21.56)--(73.02, 21.15)--(75.97, 21.21)--(75.92, 20.34)--(77.6, 20.64)--(73.05, 17.2)--(70.79, 18.11)--(67.81, 17.27)--(64.31, 17.23)--(54.27, 15.93)--(52.31, 18.03)--(60.06, 17.29)--(60.43, 18.73)--(59.79, 19.06)--(65.44, 20.05)--(71.86, 20.7)--(72.16, 20.95)--(69.43, 21.73)--(70.4, 21.96)--(70.06, 22.49)--(69.48, 22.4)--(63.3, 21.97)--(48.4, 19.77)--(41.64, 20.99)--(22.72, 18.59)--(11.06, 21.67)--(16.58, 23.75)--(14.57, 23.75)--(10.29, 24.54)--(17.36, 25.3)--(17.42, 26.18)--(12.61, 29.54)--(15.96, 29.89)--(15.52, 31.43)--(20.94, 31.31)--(13.31, 35.43)--(16.05, 35.66)--(19.1, 35.11)--(18.12, 34.75)--(27.83, 28.87)--(28.89, 30.18)--(33.87, 29.92)--(33.36, 30.35)--(38.31, 30.44)--(42.27, 33.16)--(42.87, 32.94)--(47.84, 35.37)--(50.85, 39.13)--(53.79, 42.3)--(54.39, 50.26)--(64.16, 61.59)--(63.13, 61.47)--(62.78, 61.95)--(64.93, 63.33)--(66.24, 65.62)--(67.78, 65.49)--(65.67, 61.75)--(65.09, 58.99)--(66.42, 61.44)--(68.81, 63.42)--(72.94, 69.36)--(81.5, 74.4)--(83.61, 73.92)--(85.99, 75.53)--(90.7, 76.75)--(93.55, 80.26)--(95.61, 82.43)--(96.7, 82.3)--(98.47, 82.54)--(101.05, 86.23)--(98.22, 93.14)--(100.55, 101.04)--(102.97, 105.27)--(107.45, 108.16)--(104.04, 132.27)--(104.08, 133.7)--(103.49, 134.62)--(102.5, 136.82)--(104.04, 136.98)--(103.44, 141.15)--(104.17, 143.63)--(108.08, 144.9)--(110.27, 145.35)--(111.35, 145.04)--(113.34, 144.63)--(109.31, 140.79)--(112.69, 137.21)--(111.47, 135.36)--(113.17, 133.67)--(113.34, 130.92)--(116.56, 129.1)--(119.97, 124.37)--(128.41, 119.87)--(133.19, 114.17)--(136.37, 113.08)--(138.56, 109.76)--(141.63, 100.78)--(139.94, 93.61)--(133.56, 91.17)--(129.89, 90.92)--(128.12, 89.29)--(123.75, 83.93)--(119.97, 83.01)--(117.24, 81.38)--(117.85, 80.79)--(116.9, 80.06)--(117.65, 79.02)--(114.24, 79.23)--(110.12, 78.93)--(108.35, 77.69)--(102.27, 80.15)--(102.6, 80.45)--(101.42, 81.66)--(96.3, 80.94)--(95.6, 75.16)--(91.88, 73.8)--(89.69, 73.89)--(91.19, 69.61)--(91.08, 68.3)--(87.73, 69.96)--(81.32, 69.32)--(81.63, 61.96)--(88.41, 60.66)--(88.78, 61.23)--(92.41, 60.31)--(95.93, 65.23)--(96.7, 65.47)--(97.12, 65.14)--(97.12, 59.81)--(100.34, 56.62)--(103.22, 54.85)--(104.21, 50.99)--(106.28, 48.84)--(108.51, 48.83)--(108.23, 48.05)--(111.68, 45.41); +landmass3 := (309.58, 101.89)--(307.85, 104.82)--(304.23, 104.36)--(301.98, 106.89)--(301.81, 107.2)--(301.39, 106.32)--(297.9, 109.91)--(292.61, 112.27)--(292.42, 116.24)--(291.76, 116.34)--(293.22, 123.3)--(295.54, 125.57)--(300.79, 123.84)--(313.31, 124.74)--(314.35, 125.13)--(314.72, 125.92)--(316.53, 125.74)--(318.45, 127.71)--(324.06, 128.78)--(326.88, 127.94)--(328.82, 125.64)--(331.64, 120.19)--(331.26, 115.24)--(328.34, 111.83)--(327.46, 109.78)--(327.32, 109.75)--(323.91, 106.24)--(320.57, 100.41)--(318.09, 106.52)--(315.29, 105.19)--(314.65, 104.27)--(314.4, 103.64)--(314.38, 101.88)--(314.02, 100.8)--(310.93, 100.72)--(310, 101.21)--(309.58, 101.89); +landmass4 := (360, 173.94)--(347.31, 173.02)--(337.83, 170.31)--(340.03, 168.66)--(345.63, 168.61)--(346.01, 167.92)--(341.09, 166.89)--(341.61, 165.5)--(343.88, 164.64)--(343.24, 163.51)--(349.37, 161.91)--(322.58, 157.73)--(323.11, 157.14)--(263.4, 156.39)--(263.26, 156.59)--(263.82, 157.03)--(245.18, 163.09)--(245.85, 157.68)--(234.93, 156.8)--(226.52, 156.65)--(194.69, 160.02)--(194.23, 160.12)--(182.27, 160.22)--(175.88, 161.21)--(175.09, 160.92)--(174.87, 161.24)--(171.99, 160.65)--(165.8, 161.33)--(163.91, 162.6)--(161.68, 163.73)--(141.54, 169.39)--(118.58, 172.13)--(116.78, 171.69)--(102.46, 169.67)--(94.78, 168.49)--(97.74, 167.88)--(101.28, 166.75)--(117.86, 164.33)--(118.52, 163.02)--(117.24, 161.46)--(117.41, 160.81)--(114.93, 158.64)--(113.38, 158.53)--(113.67, 158.17)--(112.94, 157.45)--(115.93, 156.83)--(115.81, 156.34)--(116.18, 155.88)--(116.84, 155.67)--(119.7, 154.63)--(119.77, 154.11)--(121.16, 153.99)--(121.76, 153.53)--(116.74, 154)--(113.84, 154.79)--(110.65, 157.28)--(111.18, 157.79)--(111.19, 159.12)--(104.73, 163.13)--(104.3, 163)--(90.27, 162.69)--(86.46, 162.59)--(74.77, 162.74)--(78.14, 164.63)--(64.48, 164.84)--(65.31, 164.46)--(50.04, 164.22)--(50.29, 164.61)--(32.44, 165.76)--(29.52, 166.6)--(27.21, 166.74)--(27.17, 166.97)--(22.41, 166.97)--(23.06, 168.33)--(21.43, 168.63)--(29.78, 169.97)--(27.58, 170.36)--(29.1, 170.81)--(23.15, 171.94)--(24.93, 172.46)--(17.69, 173.06)--(13.37, 172.69)--(3.71, 172.63)--(13.68, 173.98)--(0, 174.21); +landmass5 := (124.62, 19.08)--(123.98, 19.56)--(126.36, 20.09)--(127.24, 20.59)--(124.98, 23.19)--(130.77, 29.28)--(135.8, 28.99)--(137.84, 25.04)--(141.86, 24.34)--(146.13, 22.18)--(157, 19.51)--(155.91, 17.98)--(155.55, 16.8)--(159.25, 15.48)--(158.44, 14.93)--(159.9, 14.05)--(157.97, 12.46)--(159.52, 10.77)--(159.19, 10.3)--(167.06, 8.42)--(159.95, 8.2)--(156.76, 8.73)--(153.02, 7.87)--(131.44, 7.07)--(130.82, 7.37)--(127.12, 7.44)--(116.94, 8.32)--(111.03, 9.65)--(105.32, 11.75)--(107.03, 12.86)--(108.98, 13.43)--(112.23, 14.12)--(119.83, 14.46)--(121.54, 15.67)--(120.54, 15.91)--(122.91, 16.69)--(121.82, 17.23)--(124.62, 19.08); +landmass6 := (307.49, 56.47)--(307.06, 57.11)--(308.22, 57.5)--(310.39, 56.79)--(310.75, 57.43)--(312.59, 56.96)--(313.38, 56.17)--(316.84, 55.24)--(317.14, 55.51)--(319.46, 54.34)--(320.21, 51.88)--(320.32, 48.77)--(319.72, 48.29)--(319.35, 47.51)--(322.01, 46.93)--(323.56, 45.58)--(319.79, 44.82)--(319.05, 44.6)--(318.2, 46.06)--(317.67, 48.01)--(318.7, 48.63)--(315.73, 52.34)--(311.83, 54.71)--(307.49, 56.47); +landmass7 := (172.44, 33.8)--(171.76, 34.44)--(174.73, 35.22)--(173.52, 37.33)--(172.83, 38.17)--(172.56, 39.98)--(179.32, 38.52)--(178.63, 37.06)--(175.74, 33.85)--(176.19, 30.59)--(172.04, 32.2)--(171.52, 33.36)--(172.44, 33.8); +landmass8 := (222.04, 111.1)--(224.53, 115.35)--(228.6, 106.45)--(226.81, 103.35)--(222.04, 111.1); + +% +% This macro draws contures for a landmass on globe centered on lon,lat +% + +vardef drawLandMass (expr p, lon, lat) = + save i, j, k, l, horizon, currentPoint, horizonTimes, outHorizon, inHorizon, visibleContours, pathNumber, horizonArc, arcTimes, resultPath; + path resultPath, visibleContours[], horizon, horizonArc; + pair currentPoint, horizonTimes[]; + numeric pathNumber, outHorizon, arcTimes[]; + horizon := fullcircle scaled 2; + pathNumber := 0; + outHorizon := -1; + inHorizon := -1; + + % In the following loop visible segments of landmass and points of + % horizon-crossing are calculated + % visibleContours are just what they are called and horizonTimes are + % times on horizon circle where visible segment should cross it + for i := 0 upto length(p): + currentPoint := pointOnGlobe (point i of p, lon, lat); + if (horizonOnGlobe (point i of p, lon, lat) < 0): + if (unknown visibleContours[pathNumber]): + visibleContours[pathNumber] := currentPoint; + if (i > 0): + outHorizon := xpart(horizon intersectiontimes ((0,0) -- (findHorizonPoint (subpath(i-1, i) of p, lon, lat, 0) scaled 5))); + if (outHorizon < inHorizon): outHorizon := outHorizon + 8; fi; + horizonTimes[pathNumber - 1] := (inHorizon, outHorizon); + fi; + else: + visibleContours[pathNumber] := visibleContours[pathNumber] -- currentPoint; + fi; + else: + if (known visibleContours[pathNumber]): + pathNumber := pathNumber + 1; + inHorizon := xpart(horizon intersectiontimes ((0,0) -- (findHorizonPoint (subpath(i, i-1) of p, lon, lat, 0) scaled 5))); + fi; + fi; + endfor; + if (unknown visibleContours0): + resultPath := (1,0); + else: + if (unknown visibleContours[pathNumber]): pathNumber := pathNumber - 1; fi; + if (unknown horizonTimes[-1]): + if (pathNumber > 0): + visibleContours0 := visibleContours[pathNumber] -- visibleContours0; + fi; + pathNumber := pathNumber - 1; + else: + if (ypart(horizonTimes[-1]) < inHorizon): + horizonTimes[pathNumber] := (inHorizon, ypart(horizonTimes[-1]) + 8); + else: + horizonTimes[pathNumber] := (inHorizon, ypart(horizonTimes[-1])); + fi; + fi; + % In these loops horizon arcs directions should be handled + % The idea is that when we have path with no self-intersections arcs should + % not cross one another, these conflicts are resolved here + % It's important to note, that horizon-time detecting algorithm is + % not absolutely precise for now, so at times in will work incorrect. + for i := 0 upto pathNumber - 1: + for j := i + 1 upto pathNumber: + l := 0; + for k := -8, 0, 8: + if (xpart(horizonTimes[j]) > xpart(horizonTimes[i]) + k) and (xpart(horizonTimes[j]) < ypart(horizonTimes[i]) + k): l := l + 1; fi; + if (xpart(horizonTimes[i]) > xpart(horizonTimes[j]) + k) and (xpart(horizonTimes[i]) < ypart(horizonTimes[j]) + k): l := l + 1; fi; + endfor; + if (l > 1): horizonTimes[j] := (xpart(horizonTimes[j]) + 8, ypart(horizonTimes[j])); fi; + endfor; + endfor; + + % In the following loop previously calculated segments of a landmass and + % arcs of the horizon are sewed together in order + resultPath := visibleContours0 + for i := 1 upto pathNumber + 1: + if (known horizonTimes[i-1]): -- (subpath horizonTimes[i-1] of horizon) fi + if (i <= pathNumber): -- visibleContours[i] fi + endfor + fi; + resultPath -- cycle +enddef; + +% This thing just converts coordinates on globe rotated by lon, lat to screen coordinates + +vardef pointOnGlobe (expr p, lon, lat) = + (cosd(lon + xpart(p)) * sind(ypart(p)), + cosd(ypart(p)) * cosd(lat) + + sind(lon + xpart(p)) * sind(ypart(p)) * sind(lat)) +enddef; + +% This one is needed to check if point on globe is in view + +vardef horizonOnGlobe (expr p, lon, lat) = + (sind(lon + xpart(p)) * cosd(lat) * sind(ypart(p))) - (cosd(ypart(p)) * sind(lat)) +enddef; + +% This macro calculates horizon crossing point with given precision (recursion depth). +% Likely, this could be done analytically, though. + +vardef findHorizonPoint (expr p, lon, lat, i) = + save selecthalf, returnpoint; + pair selecthalf, returnpoint; + if (horizonOnGlobe (point 1/2 of p, lon, lat) < 0): + selecthalf := (0, 1/2); + else: + selecthalf := (1/2, 1); + fi; + if (i < 5): + returnpoint := findHorizonPoint (subpath selecthalf of p, lon, lat, i + 1) + else: + returnpoint := pointOnGlobe (point 1/2 of p, lon, lat); + fi; + returnpoint +enddef; + +vardef globe (expr s, lon, lat) = + save i, p, lm; + picture p[]; + path lm; + begingroup + save angleToLightnessPP; + vardef angleToLightnessPP (expr v) = + 1/2(v**3) + enddef; + p1 := image(draw sphere.l(2s, lat)); + vardef angleToLightnessPP (expr v) = + if (abs(cos(sphlat)) > 7/8 + uniformdeviate (1/20)): + 1/4(v**2) + else: + 1/3(v**2) + 2/3 + fi + enddef; + p2 := image(draw sphere.l(2s, lat)); + endgroup; + image( + draw fullcircle scaled 2s withpen thinpen; + for i := 1 upto 8: + lm := drawLandMass(landmass[i], lon + 90, lat) scaled s; + p3 := p2; + clip p3 to lm; + draw p3; + thinBrushGenerate (lm, + offsetPathTemplate (lm, 0) ( + 2/3minStrokeWidth + 1/3minStrokeWidth + * angleToLightness( + sphereAngleToAbsoulteAngle( + (angleRad(point offsetPathTime of lm) + 1/4pi, abs(point offsetPathTime of lm)/2s) + ), 0, point offsetPathTime of lm + ) + ), 0); + p1 := p1 maskedWith lm; + endfor; + draw p1; + ) +enddef; + +% +% This macro draws an eye pointed in the direction a (in degrees) +% Eye is opened at random angle and pupil is scaled randomly by design +% Scaling below some level, dependent on minStrokeWidth, simplifies image +% + +eyescale := 1/2cm; + +vardef eye (expr a) = + save s, eyelids, pupil, eyeball, eyelash, loopstep, p, o; + path eyelids[], pupil[], eyelash; + pair p[]; + picture eyeball; + numeric s, loopstep; + o := 10 + (15/(1 + 2**(3/2normaldeviate))); + s := eyescale; + p1 := (-3/4s, 0); + pupil1 := ((subpath (-2, 2) of fullcircle xscaled 3/5) .. (subpath (3, 5) of fullcircle xscaled 2/5) .. cycle) scaled 3/5s; + pupil2 := fullcircle scaled (1/3s + uniformdeviate(1/5s)) xscaled 1/3; + p2 := ((p1 -- ((1/2s, 0) rotatedabout (p1, o - 5))) intersectionpoint (subpath (0, 4) of pupil1)); + eyelids1 := (p1 shifted (0, -1/16s)){dir(1/3o)} .. {dir(0)}p2; + eyelids2 := p1 {dir(-1/3o)} .. {dir(0)}((1/6s, 0) rotatedabout (p1, -2/3o - 5)); + eyelids2 := subpath(xpart(eyelids2 intersectiontimes eyelids1), length(eyelids2)) of eyelids2; + eyelids3 := (p1){dir(2/3o)} .. tension 3/2 .. {dir(o-1/3o)}p2 rotatedabout (p1, 1/3o + 7); + eyelids3 := subpath (1/8, length(eyelids3)) of eyelids3; + eyelids4 := (p1 shifted (0, -1/16s)) .. {dir(1/4o - 2/3o)}((1/6s, -1/6s) rotatedabout (p1, -2/3o - 5)); + eyelids4 := subpath (1/2, length(eyelids4)) of eyelids4; + loopstep := length(eyelids1)/20; + if (arclength(subpath(0, loopstep) of eyelids1) < 5minStrokeWidth): + loopstep := arctime (5minStrokeWidth) of eyelids1; + fi; + eyeball := image( + if (5loopstep <= 1): + draw pupil1 withpen thinpen; + fill pupil2; + for i := 0 step 5loopstep until length pupil1: + draw brush ((point i of pupil1) -- (point i + 6 of pupil2 scaled 5/6 yscaled 1/2))(cos(offsetPathLength*1/2pi)*2minStrokeWidth); + endfor; + else: + fill pupil1; + fi; + ); + clip eyeball to (eyelids1{(1, 0)} .. (s, 0) .. {-1, 0}reverse(eyelids2) -- cycle); + eyeball := eyeball maskedWith ((fullcircle scaled (1/4s) xscaled 1/2 rotated 2 shifted (1/12s, 0) rotatedabout (p1, 1/3o + 2))); + image( + draw brush (eyelids1 .. tension 2.5 .. reverse (eyelids3))((1-offsetPathLength)*2minStrokeWidth); + draw brush (eyelids2)((offsetPathLength)*2minStrokeWidth); + draw brush (eyelids4)(sin(offsetPathLength*pi)*minStrokeWidth); + draw eyeball; + for i := length(eyelids1) step -loopstep until 0: + eyelash := (point i of eyelids1) {dir(angle(direction i of eyelids1) - 60 + 50*(i/length(eyelids1)))} + .. (point i of eyelids1) shifted (1/16s + (i/length(eyelids1))*1/4s, (i/length(eyelids1))*1/5s); + if (arclength(eyelash) > 2/3minDashStrokeLength): + draw brush (eyelash shifted ((-1/32s, uniformdeviate(1/12s)) scaled ((length(eyelids1)-i)/length(eyelids1))) )(minStrokeWidth + (1-offsetPathLength)*minStrokeWidth); + fi; + endfor; + for i := length(eyelids2) step -3/2loopstep until 0: + eyelash := (point i of eyelids2) {dir(angle(direction i of eyelids2) + 20 - 40*(i/length(eyelids2)))} + .. (point i of eyelids2) shifted (1/16s + (i/length(eyelids2))**2*1/7s, 1/16s - (i/length(eyelids2))*1/5s); + if (arclength(eyelash) > minDashStrokeLength): + draw brush (eyelash shifted (-1/32s, -uniformdeviate(1/24s)))(minStrokeWidth + (1-offsetPathLength)*minStrokeWidth); + fi; + endfor; + ) if cosd(a) < 0: yscaled -1 rotated (a) else: rotated a fi +enddef; + +% +% This macro draws solid surface +% + +vardef solidSurface (expr p) = + save q, s, d, stripes; + path q, s; + picture stripes; + q := offsetPath(p) (-1/4cm); + s := p -- reverse(q) -- cycle; + image( + draw solid (s, 45, 0); + draw p withpen thinpen; + ) +enddef; + +vardef solid (expr p, a, t) = + save stripes, stripeskind, d, i, j, c; + picture stripes, stripeskind; + pair c; + stripes := image( + d1 := abs(ulcorner(p rotated (90 - a)) - urcorner(p rotated (90 - a))); + d2 := abs(ulcorner(p rotated (90 - a)) - llcorner(p rotated (90 - a))); + stripeskind := dashpattern (on 1mm); + c := 1/2[ulcorner(p rotated (90 - a)), lrcorner(p rotated (90 - a))] rotated (a - 90); + for i:= 0 step (3/2shadingDensity)/d1 until 1: + if (t = 1): + j := round(i*d1/(3/2shadingDensity)); + if (j mod 4) = 0: + stripeskind := dashpattern (on 8shadingDensity off 4shadingDensity); + fi; + if ((j mod 4) = 1) or ((j mod 4) = 3): + stripeskind := dashpattern (off 1shadingDensity on 6shadingDensity off 5shadingDensity); + fi; + if (j mod 4) = 2: + stripeskind := dashpattern (on 0 off 12shadingDensity); + fi; + fi; + draw ((dir(a) scaled 1/2d2) -- (dir(a + 180) scaled 1/2d2)) shifted c shifted i[dir(a + 90) scaled 1/2d1, dir(a -90) scaled 1/2d1] withpen thinpen + dashed stripeskind; + endfor; + ); + clip stripes to p; + stripes +enddef; + +% +% Returns a picture of shaded gradient inside shape p at an angle a +% + +vardef gradientShade (expr p, a) = + save stripes, stripeshd, d, i, j, s; + picture stripes; + path s; + stripes := image( + d1 := abs(ulcorner(p rotated (90 - a)) - urcorner(p rotated (90 - a))); + d2 := abs(ulcorner(p rotated (90 - a)) - llcorner(p rotated (90 - a))); + for i:= 0 step (shadingDensity)/d1 until 1: + s := ((dir(a) scaled 1/2d2) -- (dir(a + 180) scaled 1/2d2)) shifted 1/2[ulcorner(p), lrcorner(p)] shifted i[dir(a + 90) scaled 1/2d1, dir(a -90) scaled 1/2d1]; + stripeshd := 1/4 + 3/4i; + draw brush(s)(minStrokeWidth*stripeshd); + endfor; + ); + clip stripes to p; + stripes +enddef; + +% +% This one draws a spring between points p and q with n steps +% if stretched more than possible, displayed as a straight line +% if compressed too much, displayed as having less steps +% + +springwidth := 1/8cm; + +vardef spring (expr p, q, n) = + save sp, ss, t, x, springstep, springcoef, springsegment; + transform t; + path ss[]; + pair sp; + picture springsegment; + springstep := (arclength(p--q) - 2springwidth)/(n+1); + if (springstep < 6minStrokeWidth): springstep := arclength(p--q)/round(arclength(p--q)/6minStrokeWidth); fi; + if (springstep < (springwidth*pi)): + springcoef := (1-(springstep/(springwidth*pi))); + else: + springcoef := 0; + fi; + image( + for i := 0 step 30 until 360: + sp := ((cosd(i - 90), sind(i - 90)) scaled springwidth xscaled 1/4 yscaled springcoef) shifted (springstep*(i/360) - 1/2springstep, 0); + if (i = 0): + ss1 := sp; + else: + ss1 := ss1 -- sp; + fi; + endfor; + ss2 := subpath (0, 6) of ss1; + ss3 := subpath (6, 12) of ss1; + ss4 := subpath (0, ypart(ss2 shifted (-3minStrokeWidth, 0) intersectiontimes ss3)) of ss3; + x := ypart(ss2 shifted (3minStrokeWidth, 0) intersectiontimes ss3); + if (x > 0): + ss5 := subpath (x, length(ss3)) of ss3; + else: + ss5 := point length(ss3) of ss3; + fi; + if (xpart(llcorner(ss4)) - 3minStrokeWidth < xpart(urcorner(ss2 shifted (-springstep, 0)))): + x := ypart((subpath (3, 6) of ss2 shifted (-springstep + 3minStrokeWidth, 0)) intersectiontimes ss4); + if (x > 0): + ss6 := subpath (0, x) of ss4; + else: + ss6 := point 0 of ss4; + fi; + x := ypart((subpath (0, 3) of ss2 shifted (-springstep + 3minStrokeWidth, 0)) intersectiontimes ss4); + if (x > 0): + ss7 := subpath (x, length(ss4)) of ss4; + else: + ss7 := point length(ss4) of ss4; + fi; + fi; + springsegment := image( + draw brush (ss2)(minStrokeWidth + sin(offsetPathLength*pi)*minStrokeWidth); + if (unknown(ss6)): + if (arclength(ss4) > minStrokeWidth): draw ss4 withpen thinpen; fi; + else: + if (arclength(ss6) > minStrokeWidth): draw ss6 withpen thinpen; fi + if (arclength(ss7) > minStrokeWidth): draw ss7 withpen thinpen; fi + fi; + if (arclength(ss5) > minStrokeWidth): draw ss5 withpen thinpen; fi; + ); + ss8 := (ss2 shifted (-2minStrokeWidth, 0)) rotated angle(q-p) shifted ((springwidth + 1/2springstep)/arclength(p--q))[p, q]; + for i := springwidth + 1/2springstep step springstep until arclength(p--q) - springwidth - 1/2springstep + 1: + t := identity rotated angle(q-p) shifted (i/arclength(p--q))[p, q]; + draw springsegment transformed t; + if (i <= springwidth + 1/2springstep + 2/3springwidth): + ss9 := ss3 transformed t; + ss10 := subpath ( + xpart(ss9 intersectiontimes (subpath (0, 3) of ss8)), + xpart(ss9 intersectiontimes (subpath (3, 6) of ss8)) + ) of ss9; + if (arclength(ss10) > minStrokeWidth): draw ss10 withpen thinpen; fi; + fi; + endfor; + draw brush (((springwidth)/arclength(p--q))[p, q] shifted (dir(angle(p-q) + 90)*springwidth * springcoef){(p-q)} .. {(p-q)}p)(minStrokeWidth); + draw brush (((springwidth)/arclength(p--q))[q, p] shifted (dir(angle(p-q) + 90)*springwidth * springcoef){(q-p)} .. {(q-p)}q)(minStrokeWidth); + ) +enddef; + +% +% This macro draws some kind of weight. Not very nice one at the moment +% + +vardef weight.h (expr h) = + save auricle, q, r; + path q[]; + auricle.d := 2mm; + auricle.t := 2shadingDensity; + r := 2/5(h-auricle.d); + image( + q0 := offsetPathSubdivide((0, -h) -- (0, -auricle.d - 1/6h)); + q1 := offsetPathTemplate(q0, 0)(r-(offsetPathLength*(1/8r))); + q2 := offsetPathGenerate (q0, q1, 0); + q3 := offsetPathGenerate (q0, q1 yscaled -1, 0); + tubeGenerate (q2, q3, q1, 0); + draw reverse(q2) -- q3 withpen thinpen; + q0 := offsetPathSubdivide((0, -auricle.d - 1/6h) -- (0, -auricle.d)); + q1 := offsetPathTemplate(q0, 0)(2/8r + 5/8r*(sqrt(1-offsetPathLength**2))); + q2 := offsetPathGenerate (q0, q1, 0); + q3 := offsetPathGenerate (q0, q1 yscaled -1, 0); + tubeGenerateAlt (q2, q3, q1); + draw q3 -- reverse(q2) withpen thinpen; + q5 := offsetPathSubdivide(point 0 of q3 -- point 0 of q2); + q6 := tube.e((0,0) -- (0, -3/2auricle.t))(1/2auricle.t); + draw image( + q4 := (((0, -1/2) {dir(90)} .. (1/2, 1/2) .. (0, 1) .. {dir(-90)}(-1/2, 1/4)) shifted (0, -1)) scaled 2/3auricle.d; + draw shadedEdge(tube.e(q4) (1/4auricle.t)) shifted (0, -1/2auricle.t); + ) maskedWith (q3 -- reverse(q2) -- (q2 yscaled 0 shifted (0, -h)) -- (reverse(q3) yscaled 0 shifted (0, -h)) -- cycle) + maskedWith q6; + draw shadedEdge(q6); + thinBrushGenerate (q5, + offsetPathTemplate (q5, 0) ( + minDashStrokeWidth + minStrokeWidth + * angleToLightness( + (arccos(1 - offsetPathLength*2), 1/2pi) + , 0, point offsetPathTime of q5 + ) + ), 0); + ) +enddef; + +vardef weight.s (expr h) = + save q,r; + path q[]; + r := 2/5h; + image( + q0 := offsetPathSubdivide((0, 0) -- (0, h-2/3r)); + q1 := offsetPathTemplate(q0, 0)(r); + q2 := offsetPathGenerate (q0, q1, 0); + q3 := offsetPathGenerate (q0, q1 yscaled -1, 0); + tubeGenerate (q2, q3, q1, 0); + draw reverse(q2)--q3 withpen thinpen; + q0 := offsetPathSubdivide((0, h-2/3r) -- (0, h - 1/8r)); + q1 := offsetPathTemplate(q0, 0)(r-sqrt(1- (1- offsetPathLength*2)**2)*1/3r - 1/6r*offsetPathLength); + q2 := offsetPathGenerate (q0, q1, 0); + q3 := offsetPathGenerate (q0, q1 yscaled -1, 0); + tubeGenerateAlt (q2, q3, q1); + draw q2 withpen thinpen; + draw q3 withpen thinpen; + q0 := offsetPathSubdivide((0, h-1/8r) -- (0, h)); + q1 := offsetPathTemplate(q0, 0)((r-1/6r)+sqrt(1- (1- offsetPathLength*2)**2)*1/16r); + q2 := offsetPathGenerate (q0, q1, 0); + q3 := offsetPathGenerate (q0, q1 yscaled -1, 0); + tubeGenerateAlt (q2, q3, q1); + draw q2 --reverse(q3) withpen thinpen; + ) +enddef; + +% +% This macro makes a lens-shaped clockwise path with radii +% r = (left radius, right radius), thickness t and diameter d +% + +vardef lens (expr r, t, d, s) = + save p, q, m, c; + pair c[]; + path p[], q[]; + if (xpart(r) = infinity): + p1 := (0, d) -- (0, -d); + else: + p1 := subpath (2, 6) of fullcircle scaled 2xpart(r); + fi; + if (ypart(r) = infinity): + p2 := (0, d) -- (0, -d); + else: + p2 := subpath (-2, 2) of fullcircle scaled 2ypart(r); + fi; + q1 := (min(xpart(ulcorner(p1)), xpart(ulcorner(p2))) - 1, -1/2d)--(max(xpart(urcorner(p1)), xpart(urcorner(p2))) + 1,-1/2d); + q2 := q1 shifted (0, d); + q3 := q1 shifted (0, 1/2d); + c1 := p1 intersectiontimes q1; + c2 := p1 intersectiontimes q2; + c3 := p2 intersectiontimes q2; + c4 := p2 intersectiontimes q1; + if (xpart(c1) > 0): + p1 := subpath (xpart(c1), xpart(c2)) of p1; + fi; + if (xpart(c3) > 0): + p2 := subpath (xpart(c3), xpart(c4)) of p2; + fi; + p1 := p1 shifted (-xpart(point xpart(p1 intersectiontimes q3) of p1), 0); + p2 := p2 shifted (-xpart(point xpart(p2 intersectiontimes q3) of p2) + t, 0); + reverse(p1--p2--cycle) scaled s +enddef; + +% +% This one returns a picture of a pulley with diameter d and it's support rotated +% at angle a. pulleyOutline path changes every time +% + +path pulleyOutline; +numeric pulleySupportSize; +pulleySupportSize := 2/3; + +vardef pulley (expr d, a) = + save pw, p, r; + picture pw; + path p[]; + r1 := 3/5d; + r2 := 1/6d; + image( + p0 := fullcircle scaled d; + p1 := (subpath (3, 9) of fullcircle) scaled r1; + p0 := subpath ( + xpart(p0 intersectiontimes ((point 0 of p1) -- (point 0 of p1 shifted (0, d)))), + 8 + xpart(p0 intersectiontimes ((point 0 of reverse(p1)) -- (point 0 of reverse(p1) shifted (0, d))))) + of p0; + p0 := ((xpart(point 0 of reverse(p0)), pulleySupportSize*d) -- (xpart(point 0 of p0), pulleySupportSize*d) -- p0 -- cycle) rotated a; + pulleyOutline := p0; + p1 := (p1 -- (xpart(point 0 of reverse(p1)), pulleySupportSize*d) -- (xpart(point 0 of p1), pulleySupportSize*d) -- cycle) rotated a; + pw := pulleyWheel(d) maskedWith p1; + draw pw; + draw p1 withpen thinpen; + draw shadedEdge(reverse(fullcircle) scaled r2); + ) +enddef; + +vardef pulleyWheel (expr d) = + save pw, r, i; + picture pw; + r1 := 7/9d; + r2 := 8/9d; + r3 := 3/5d; + r4 := 1/8d; + if (r2-r1) > shadingDensity: + pw := image( + for i := r3 step 2shadingDensity until r2: + if (i <= r1) or (i >= r2): + thinBrushGenerate (fullcircle scaled i, + offsetPathTemplate (fullcircle scaled i, 0) ( + 2/3minStrokeWidth + minStrokeWidth + * angleToLightness( + sphereAngleToAbsoulteAngle( + (angleRad(direction offsetPathTime of fullcircle), i/4d) + ), 0, point offsetPathTime of fullcircle scaled i + ) + ), 0); + else: + thinBrushGenerate (fullcircle scaled i, + offsetPathTemplate (fullcircle scaled i, 0) ( + 1/4minStrokeWidth + minStrokeWidth + * angleToLightness( + sphereAngleToAbsoulteAngle( + (angleRad(direction offsetPathTime of fullcircle) + pi, 1/2) + ), 0, point offsetPathTime of fullcircle scaled i + ) + ), 0); + fi; + endfor; + draw brush (fullcircle scaled r1)(minStrokeWidth); + draw brush (fullcircle scaled r2)(minStrokeWidth); + draw fullcircle scaled d withpen thinpen; + draw fullcircle scaled r3 withpen thinpen; + draw shadedEdge(reverse(fullcircle) scaled r4); + ); + else: + pw := image( + draw shadedEdge (reverse(fullcircle) scaled r1); + draw fullcircle scaled d withpen thinpen; + draw fullcircle scaled r4 withpen thickpen; + ) + fi; + pw +enddef; + +% +% This macro draws a wheel +% + +vardef wheel (expr d, a) = + save pc, p, r, i; + picture pc[]; + path p[]; + r1 := 2/8d; + r2 := d-6minStrokeWidth; + r3 := 7/8d-2shadingDensity; + if r1 > 3shadingDensity: + pc1 := image( + for i := r1 step 2shadingDensity until r3: + thinBrushGenerate (fullcircle scaled i, + offsetPathTemplate (fullcircle scaled i, 0) ( + 2/3minStrokeWidth + minStrokeWidth + * angleToLightness( + sphereAngleToAbsoulteAngle( + (angleRad(direction offsetPathTime of fullcircle) + pi, i/4d) + ), 0, point offsetPathTime of fullcircle scaled i + ) + ), 0); + endfor; + draw sphere.c(r1); + draw shadedEdge (reverse(fullcircle) scaled r3); + ); + else: + pc1 := image( + draw shadedEdge (fullcircle scaled d); + draw shadedEdge (fullcircle scaled r1); + draw shadedEdge (reverse(fullcircle) scaled r2); + ); + fi; + pc2 := image(fill fullcircle scaled d;) maskedWith (fullcircle scaled r2); + pc3 := image( + p1 := reverse((1/3d, 1/4d) -- (-1/3d, 1/4d) -- (-1/2d, 1/2d) -- (1/2d, 1/2d) -- cycle) rotated a; + draw p1 withpen thinpen; + draw gradientShade(p1, 180); + ); + pc3 := pc3 maskedWith (fullcircle scaled d); + image( + draw pc1; + draw pc2; + draw pc3; + ) +enddef; + +% +% These macros are for drawing wood texture. A bunch of wood-related global +% variables are also here. +% + +woodBlockRes := 1/3mm; +woodBlockDetail := 1/5; +woodBlockYRdensity := 1/24; +woodKnotAngle := 1/3pi; +woodKnotRadius := 1/8cm; +woodKnotDensity := 2cm; + +% wField returns a value on a scalar field, that is surface of year ring + +vardef wField (suffix wk)(expr i, j, cs, nwk)= + save x, y, csx, csy, ba, a, r, outputValue, k, bd; + csx := xpart(cs); + csy := ypart(cs); + x := i*woodBlockDetail; + y := j*woodBlockDetail; + bd := 1/2(woodKnotRadius/woodBlockRes)*woodBlockDetail; + outputValue := 0; + for k := 0 upto nwk: + r := (abs(((x, y) shifted -wk[k]) yscaled (sin(woodKnotAngle)))); + a := angleRad((x, y) shifted -wk[k]); + if (r >= 2bd) and (1/2r-bd < 8): + outputValue := outputValue + ((sqrt(1/2r-bd)/(3**(1/2r-bd)))*(sin(woodKnotAngle)*(1/2sin(-a)) + 1)*(1/3 + 2/3abs(cos(a))))**2; + elseif (r < 2bd): + outputValue := outputValue - 10sqrt(1-(r/2bd)**4); + fi; + outputValue := outputValue + 1/64cos(2pi*1/30r); + endfor; + outputValue := outputValue - (i*woodBlockYRdensity)/5 + 1/32sin(pi*i/xpart(cs) + 4pi*j/ypart(cs)); + outputValue +enddef; + +% woodBlock generates coordinates of knots, calls wField to +% generate matrix of heights (one for all years, that's simplification, of course) +% and then calls isoLines for each year ring (shifting values in matrix by woodBlockYRdensity) + +vardef woodBlock (expr w, l) = + save wood, wKnot, nwKnot, p, q, i, j, k, cl, tr, wS, lS; + numeric wood[][]; + pair wKnot[]; + path q; + picture p; + if (l > w): + wS := round(w/woodBlockRes); + lS := round(l/woodBlockRes); + else: + wS := round(l/woodBlockRes); + lS := round(w/woodBlockRes); + fi; + image( + p := image( + i := -1; + j := 0; + forever: + wKnot[-1] := (uniformdeviate(wS*woodBlockDetail + woodKnotRadius*woodBlockDetail/woodBlockRes), uniformdeviate(lS*woodBlockDetail + woodKnotRadius*woodBlockDetail/woodBlockRes)) shifted (-1/2woodKnotRadius*woodBlockDetail/woodBlockRes, -1/2woodKnotRadius*woodBlockDetail/woodBlockRes); + cl := 0; + if (i > -1): + for k := 0 step 1 until i: + if (woodBlockRes*abs(wKnot[k]-wKnot[-1])/woodBlockDetail) < woodKnotDensity: + cl := 1; + fi; + endfor; + fi; + if cl > 0: + j := j + 1; + else: + i := i + 1; + wKnot[i] := wKnot[-1]; + for tr := 2woodKnotRadius step -8minStrokeWidth until 2minStrokeWidth: + draw brush( + ((fullcircle scaled tr yscaled (1/sin(woodKnotAngle))) shifted (woodBlockRes*wKnot[i]/woodBlockDetail)) + )( + minStrokeWidth*(1/2+abs(sin(woodKnotAngle))*abs(sin(angleRad(point offsetPathTime of fullcircle)))) + ); + endfor; + fi; + exitif (j >= 10) or (i >= 1/2((w/cm)*(l*cm))/(woodKnotDensity/cm)); + endfor; + nwKnot := i; + for i := 0 step 1 until wS: + k := uniformdeviate(1/8woodBlockYRdensity); + for j := 0 step 1 until lS: + wood[i][j] := wField (wKnot)(i, j, (wS, lS), nwKnot) + k; + endfor; + endfor; + for i := -wS/(80woodBlockYRdensity) - 2 upto wS/(80woodBlockYRdensity) + 2: + draw isoLines(wood)((wS, lS), woodBlockYRdensity*i + uniformdeviate(1/8woodBlockYRdensity), woodBlockRes); + endfor; + ); + q := (0, 0) -- (w, 0) -- (w, l) -- (0, l) -- cycle; + if (w > l): q := q xscaled -1 rotated -90; fi; + clip p to q; + draw p; + draw q withpen thinpen; + ) if (l < w): xscaled -1 rotated -90 fi +enddef; + +% woodenThing fits a woodBlock into thingOutline at a given woodAngle + +vardef woodenThing (expr thingOutline, woodAngle) = + save shiftedThing, thingOrigin, thingWoodBlock; + path shiftedThing; + pair thingOrigin; + picture thingWoodBlock; + shiftedThing := thingOutline rotated -woodAngle; + thingOrigin := llcorner(shiftedThing); + shiftedThing := shiftedThing shifted -thingOrigin; + thingWoodBlock := woodBlock(xpart(urcorner(shiftedThing)), ypart(urcorner(shiftedThing))); + clip thingWoodBlock to shiftedThing; + thingWoodBlock := (thingWoodBlock shifted thingOrigin) rotated woodAngle; + image( + draw thingOutline withpen thinpen; + draw thingWoodBlock; + ) +enddef; + +% +% This part is related to knots +% + +% lists are only used in knots so far. +% The following two macros are taken from byrne.mp + +vardef appendList@#(suffix listName)(expr valueToAdd, whereToAdd, omitDuplicates) = + save v, valueExists; + string v; + boolean valueExists; + valueExists := false; + if str @# = "": + if not string listName: + string listName; + fi; + else: + if not string listName0: + string listName[]; + fi; + fi; + if unknown listName@#: + listName@# := ""; + fi; + if omitDuplicates: + for i=scantokens(listName@#): + if (i = valueToAdd): + valueExists := true; + fi; + endfor; + fi; + if not valueExists: + if string valueToAdd: + v := valueToAdd; + else: + v := decimal(valueToAdd); + fi; + if length(listName@#) = 0: + listName@# := v; + else: + if (whereToAdd = 1): + listName@# := listName@# & ", " & v; + else: + listName@# := v & ", " & listName@#; + fi; + fi; + fi; +enddef; + +vardef sortList (expr listToSort, ascending) = + save nPre, nPost, pivot, isSorted, lastValue, preList, postList, rv; + numeric nPre, nPost, pivot; + boolean isSorted; + string preList, postList, rv; + nPre := 0; + nPost := 0; + isSorted := true; + if ascending: + lastValue := -infinity; + else: + lastValue := infinity; + fi; + for i=scantokens(listToSort): + if (unknown pivot): + pivot := i; + fi; + if ((i < pivot) and ascending) or ((i > pivot) and not ascending): + appendList (preList, i, -1, false); + nPre := nPre + 1; + else: + appendList (postList, i, -1, false); + nPost := nPost + 1; + fi; + if ((lastValue > i) and ascending) or ((lastValue < i) and not ascending): + isSorted := false; + fi; + lastValue := i; + endfor; + if isSorted: + rv := listToSort; + else: + if nPre > 1: + preList := sortList(preList, ascending); + fi; + if nPre > 0: + preList := preList & ", "; + else: + preList := ""; + fi; + if nPost > 1: + postList := sortList(postList, ascending); + fi; + rv := preList & postList; + fi; + rv +enddef; + + +% When looking for intersections, knot is browsed with knotStepSize step. +% Affects nothing interesting. +numeric knotStepSize; +knotStepSize := 1/8; + +vardef knotFromStrands (suffix knotName) = + save slidingSegment, timeToAdd, pathSegments, intTimes, tSegWidth, tSegStyle, numberOfSegments, segmentWidth, totalNumberOfSegments, intersections, intersectionsList, layerContents, layersList, b, e, n, layerContents, segmentPicture; + save shadowsEnabled, allShadowPaths, totalNumberOfShadows, numberOfShadows, shadowPath, tmpShadows; + boolean shadowsEnabled; + path shadowPath[], allShadowPaths[]; + numeric timeToAdd, numberOfShadows, totalNumberOfShadows; + totalNumberOfShadows := -1; + tmpShadows := -1; + path inspectedPath, slidingSegment, pathSegments[]; + pair intTimes[]; + numeric intersections[], numberOfSegments[], segmentWidth[], totalNumberOfSegments, tSegWidth, b, e, n; + picture layerPicture[]; + string layerContents[], segmentStyle[], tSegStyle; + totalNumberOfSegments := 0; + for sNa := 1 step 1 until knotName.nStrands: + inspectedPath := knotName.strandPath[sNa]; + tSegWidth := knotName.strandWidth[sNa]; + tSegStyle := knotName.strandStyle[sNa]; + for sNb := 1 step 1 until knotName.nStrands: + for i := -knotStepSize step knotStepSize until length(knotName.strandPath[sNb]): + slidingSegment := subpath (i, i + 2knotStepSize) of knotName.strandPath[sNb]; + intTimes0 := (inspectedPath firstIntersectionTimes slidingSegment); + intTimes1 := (reverse(inspectedPath) firstIntersectionTimes slidingSegment); + if (sNb = sNa): + if (xpart(intTimes0) >= i) + and (xpart(intTimes0) <= i + 2knotStepSize): + intTimes0 := (-1, -1); + fi; + if ((length(inspectedPath) - xpart(intTimes1)) >= i) + and ((length(inspectedPath) - xpart(intTimes1)) <= i + 2knotStepSize): + intTimes1 := (-1, -1); + fi; + if (i+knotStepSize >= length(inspectedPath)): + if (xpart(intTimes0) <= knotStepSize) + or (xpart(intTimes0) = length(inspectedPath)): + intTimes0 := (-1, -1); + fi; + if ((length(inspectedPath) - xpart(intTimes1)) <= knotStepSize) + or (xpart(intTimes1) = 0): + intTimes1 := (-1, -1); + fi; + fi; + if (i-knotStepSize <= length(inspectedPath)): + if (xpart(intTimes0) >= (length(inspectedPath) - knotStepSize)) + or (xpart(intTimes0) = 0): + intTimes0 := (-1, -1); + fi; + if ((length(inspectedPath) - xpart(intTimes1)) >= (length(inspectedPath) - knotStepSize)) + or (xpart(intTimes1) = length(inspectedPath)): + intTimes1 := (-1, -1); + fi; + fi; + fi; + timeToAdd := -1; + if ((ypart(intTimes0) > 0) and (ypart(intTimes0) < length(slidingSegment))) + and ((xpart(intTimes0) >= 0) and (xpart(intTimes0) <= length(inspectedPath))) + and ((sNb <> sNa) or ((xpart(intTimes0) < i) or (xpart(intTimes0) > i + 1))): + timeToAdd := xpart(intTimes0); + elseif (sNb = sNa): + if ((ypart(intTimes1) > 0) and (ypart(intTimes1) < length(slidingSegment))) + and ((xpart(intTimes1) >= 0) and (xpart(intTimes1) <= length(inspectedPath))) + and ((length(inspectedPath) - xpart(intTimes1) < i) or (length(inspectedPath) - xpart(intTimes1) > i + 1)): + timeToAdd := length(inspectedPath) - xpart(intTimes1); + fi; + fi; + if (timeToAdd >= 0): + if (timeToAdd = length(inspectedPath)): + timeToAdd := 0; + fi; + appendList[sNa](intersectionsList, round(timeToAdd*10)/10, 1, true); + fi; + endfor; + endfor; + if known intersectionsList[sNa]: + numberOfSegments[sNa] := 0; + for i := scantokens(sortList(intersectionsList[sNa], true)): + numberOfSegments[sNa] := numberOfSegments[sNa] + 1; + intersections[numberOfSegments[sNa]] := i; + endfor; + intersections[0] := 0; + if (not cycle knotName.strandPath[sNa]): + intersections[numberOfSegments[sNa] + 1] := length(knotName.strandPath[sNa]); + fi; + for i := 1 step 1 until numberOfSegments[sNa]: + totalNumberOfSegments := totalNumberOfSegments + 1; + if (i > 1): + b := 1/2[intersections[i-1], intersections[i]]; + else: + if (not cycle knotName.strandPath[sNa]): + b := 0; + else: + b := 1/2[intersections[numberOfSegments[sNa]] - length(knotName.strandPath[sNa]), intersections[i]]; + fi; + fi; + if (i < numberOfSegments[sNa]): + e := 1/2[intersections[i], intersections[i+1]]; + else: + if (not cycle knotName.strandPath[sNa]): + e := length(inspectedPath); + else: + e := 1/2[intersections[i], intersections[1] + length(knotName.strandPath[sNa])]; + fi; + fi; + pathSegments[totalNumberOfSegments] := subpath (b, e) of inspectedPath; + if (length(pathSegments[totalNumberOfSegments])<=2): + pathSegments[totalNumberOfSegments] := pathSubdivide(pathSegments[totalNumberOfSegments], 2); + fi; + segmentWidth[totalNumberOfSegments] := tSegWidth; + segmentStyle[totalNumberOfSegments] := tSegStyle; + endfor; + else: + totalNumberOfSegments := totalNumberOfSegments + 1; + numberOfSegments[sNa] := 1; + pathSegments[totalNumberOfSegments] := inspectedPath; + segmentWidth[totalNumberOfSegments] := tSegWidth; + segmentStyle[totalNumberOfSegments] := tSegStyle; + fi; + n := 0; + for i := scantokens(knotName.intLayers[sNa]): + n := n + 1; + if (n <= numberOfSegments[sNa]): + appendList[i](layerContents, totalNumberOfSegments - numberOfSegments[sNa] + n, 1, true); + appendList(layersList, i, 1, true); + fi; + endfor; + if n > 0: + if n < numberOfSegments[sNa]: + for i := n + 1 step 1 until numberOfSegments[sNa]: + appendList0(layerContents, totalNumberOfSegments - numberOfSegments[sNa] + i, 1, true); + endfor; + appendList(layersList, 0, 1, true); + fi; + else: + for i := 1 step 1 until numberOfSegments[sNa]: + appendList0(layerContents, totalNumberOfSegments - numberOfSegments[sNa] + i, 1, true); + endfor; + appendList(layersList, 0, 1, true); + fi; + endfor; + image( + for i := scantokens(sortList(layersList, false)): + layerPicture[i] := image( + for j := scantokens(layerContents[i]): + numberOfShadows := -1; + shadowsEnabled := false; + for k := 0 step 1 until totalNumberOfShadows: + if xpart((subpath (1/10, length(pathSegments[j]) - 1/10) of pathSegments[j]) + intersectiontimes allShadowPaths[k]) > 0: + shadowsEnabled := true; + numberOfShadows := numberOfShadows + 1; + shadowPath[numberOfShadows] := allShadowPaths[k]; + shadowDepth[numberOfShadows] := 3/2segmentWidth[j]; + fi; + endfor; + save drawTubeEnds; + boolean drawTubeEnds; + drawTubeEnds := false; + draw tube.scantokens(segmentStyle[j])(pathSegments[j])(segmentWidth[j]) if segmentStyle[j] = "e": withpen thickpen fi; + tmpShadows := tmpShadows + 1; + allShadowPaths[tmpShadows] := tubeOutline; + endfor; + ); + for j := 0 step 1 until totalNumberOfShadows: + layerPicture[i] := layerPicture[i] maskedWith allShadowPaths[j]; + endfor; + totalNumberOfShadows := tmpShadows; + endfor; + for i := scantokens(sortList(layersList, true)): + draw layerPicture[i]; + endfor; + ) +enddef; + +vardef addStrandToKnot (suffix knotName) (expr p, w, s, intersectionLayers) = + save n; + if not known knotName.nStrands: + numeric knotName.nStrands; + knotName.nStrands := 0; + fi; + if not path knotName.strandPath0: + path knotName.strandPath[]; + fi; + if not numeric knotName.strandWidth0: + numeric knotName.strandWidth[]; + fi; + if not string knotName.strandStyle0: + string knotName.strandStyle[]; + fi; + if not string knotName.intLayers0: + string knotName.intLayers[]; + fi; + knotName.nStrands := knotName.nStrands + 1; + n := knotName.nStrands; + knotName.strandPath[n] := p; + knotName.strandWidth[n] := w; + knotName.strandStyle[n] := s; + knotName.intLayers[n] := intersectionLayers; +enddef; |