summaryrefslogtreecommitdiff
path: root/graphics/asymptote/base
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /graphics/asymptote/base
Initial commit
Diffstat (limited to 'graphics/asymptote/base')
-rw-r--r--graphics/asymptote/base/CAD.asy367
-rw-r--r--graphics/asymptote/base/animate.asy3
-rw-r--r--graphics/asymptote/base/animation.asy188
-rw-r--r--graphics/asymptote/base/annotate.asy16
-rw-r--r--graphics/asymptote/base/asy-init.el4
-rw-r--r--graphics/asymptote/base/asy-kate.sh146
-rw-r--r--graphics/asymptote/base/asy-mode.el1600
-rw-r--r--graphics/asymptote/base/asy.vim202
-rw-r--r--graphics/asymptote/base/asy_filetype.vim3
-rwxr-xr-xgraphics/asymptote/base/asymptote.py50
-rw-r--r--graphics/asymptote/base/babel.asy4
-rw-r--r--graphics/asymptote/base/bezulate.asy310
-rw-r--r--graphics/asymptote/base/binarytree.asy383
-rw-r--r--graphics/asymptote/base/bsp.asy209
-rw-r--r--graphics/asymptote/base/colormap.asy3890
-rw-r--r--graphics/asymptote/base/contour.asy682
-rw-r--r--graphics/asymptote/base/contour3.asy479
-rw-r--r--graphics/asymptote/base/drawtree.asy101
-rw-r--r--graphics/asymptote/base/embed.asy37
-rw-r--r--graphics/asymptote/base/external.asy37
-rw-r--r--graphics/asymptote/base/feynman.asy622
-rw-r--r--graphics/asymptote/base/flowchart.asy526
-rw-r--r--graphics/asymptote/base/fontsize.asy1
-rw-r--r--graphics/asymptote/base/geometry.asy7192
-rw-r--r--graphics/asymptote/base/graph.asy2243
-rw-r--r--graphics/asymptote/base/graph3.asy2044
-rw-r--r--graphics/asymptote/base/graph_settings.asy18
-rw-r--r--graphics/asymptote/base/graph_splinetype.asy264
-rw-r--r--graphics/asymptote/base/grid3.asy412
-rw-r--r--graphics/asymptote/base/interpolate.asy140
-rw-r--r--graphics/asymptote/base/labelpath.asy28
-rw-r--r--graphics/asymptote/base/labelpath3.asy83
-rw-r--r--graphics/asymptote/base/latin1.asy2
-rw-r--r--graphics/asymptote/base/lmfit.asy881
-rw-r--r--graphics/asymptote/base/markers.asy218
-rw-r--r--graphics/asymptote/base/math.asy451
-rw-r--r--graphics/asymptote/base/metapost.asy19
-rw-r--r--graphics/asymptote/base/nopapersize.ps3
-rw-r--r--graphics/asymptote/base/obj.asy113
-rw-r--r--graphics/asymptote/base/ode.asy457
-rw-r--r--graphics/asymptote/base/palette.asy530
-rw-r--r--graphics/asymptote/base/patterns.asy102
-rw-r--r--graphics/asymptote/base/plain.asy302
-rw-r--r--graphics/asymptote/base/plain_Label.asy691
-rw-r--r--graphics/asymptote/base/plain_arcs.asy44
-rw-r--r--graphics/asymptote/base/plain_arrows.asy631
-rw-r--r--graphics/asymptote/base/plain_bounds.asy788
-rw-r--r--graphics/asymptote/base/plain_boxes.asy139
-rw-r--r--graphics/asymptote/base/plain_constants.asy169
-rw-r--r--graphics/asymptote/base/plain_debugger.asy86
-rw-r--r--graphics/asymptote/base/plain_filldraw.asy248
-rw-r--r--graphics/asymptote/base/plain_margins.asy99
-rw-r--r--graphics/asymptote/base/plain_markers.asy401
-rw-r--r--graphics/asymptote/base/plain_paths.asy397
-rw-r--r--graphics/asymptote/base/plain_pens.asy368
-rw-r--r--graphics/asymptote/base/plain_picture.asy1694
-rw-r--r--graphics/asymptote/base/plain_prethree.asy216
-rw-r--r--graphics/asymptote/base/plain_scaling.asy258
-rw-r--r--graphics/asymptote/base/plain_shipout.asy145
-rw-r--r--graphics/asymptote/base/plain_strings.asy264
-rw-r--r--graphics/asymptote/base/pstoedit.asy18
-rw-r--r--graphics/asymptote/base/rational.asy269
-rw-r--r--graphics/asymptote/base/rationalSimplex.asy362
-rw-r--r--graphics/asymptote/base/reload.js23
-rw-r--r--graphics/asymptote/base/res/notes.txt7
-rw-r--r--graphics/asymptote/base/roundedpath.asy84
-rw-r--r--graphics/asymptote/base/shaders/fragment.glsl237
-rw-r--r--graphics/asymptote/base/shaders/vertex.glsl49
-rw-r--r--graphics/asymptote/base/simplex.asy329
-rw-r--r--graphics/asymptote/base/size10.asy12
-rw-r--r--graphics/asymptote/base/size11.asy12
-rw-r--r--graphics/asymptote/base/slide.asy620
-rw-r--r--graphics/asymptote/base/slopefield.asy87
-rw-r--r--graphics/asymptote/base/smoothcontour3.asy1582
-rw-r--r--graphics/asymptote/base/solids.asy412
-rw-r--r--graphics/asymptote/base/stats.asy292
-rw-r--r--graphics/asymptote/base/syzygy.asy926
-rw-r--r--graphics/asymptote/base/texcolors.asy68
-rw-r--r--graphics/asymptote/base/three.asy3251
-rw-r--r--graphics/asymptote/base/three_arrows.asy723
-rw-r--r--graphics/asymptote/base/three_light.asy133
-rw-r--r--graphics/asymptote/base/three_margins.asy104
-rw-r--r--graphics/asymptote/base/three_surface.asy2455
-rw-r--r--graphics/asymptote/base/three_tube.asy426
-rw-r--r--graphics/asymptote/base/tree.asy86
-rw-r--r--graphics/asymptote/base/trembling.asy199
-rw-r--r--graphics/asymptote/base/tube.asy167
-rw-r--r--graphics/asymptote/base/unicode.asy2
-rw-r--r--graphics/asymptote/base/x11colors.asy145
89 files changed, 45080 insertions, 0 deletions
diff --git a/graphics/asymptote/base/CAD.asy b/graphics/asymptote/base/CAD.asy
new file mode 100644
index 0000000000..1bed03bc20
--- /dev/null
+++ b/graphics/asymptote/base/CAD.asy
@@ -0,0 +1,367 @@
+struct sCAD
+{
+ int nLineGroup = 0; // 0-3
+
+ pen
+ // A
+ pA,
+ pVisibleEdge, // Sichtbare Kanten
+ pVisibleContour, // Sichtbarer Umriss
+ pUsableWindingLength, // Nitzbare Gewindelänge
+ pSystemLine, // Systemlinie (Stahlbau)
+ pDiagramCurve, // Kurve in Diagrammen
+ pSurfaceStructure, // Oberflächenstrukturen
+ // B
+ pB,
+ pLightEdge, // Lichtkante
+ pMeasureLine, // Maßlinie
+ pMeasureHelpLine, // Maßhilfslinie
+ pMeasureLineBound, // Maßlinienbegrenzung
+ pReferenceLine, // Hinweislinie
+ pHatch, // Schraffur
+ pWindingGround, // Gewindegrund
+ pDiagonalCross, // Diagonalkreuz
+ pBendLine, // Biegelinie
+ pProjectionLine, // Projektionslinie
+ pGrid, // Rasterlinien
+ // C
+ pC,
+ pFreehand, // Begrenzung abgebrochener oder unterbrochener
+ // Schnitte, wenn die Begrenzung
+ // keine Mittellinie ist
+ // E
+ pE,
+ pSurfaceTreatmentAllowed, // Bereich zulässiger Oberflächenbehandlung
+ // F
+ pF,
+ pInvisibleEdge, // unsichtbare Kante
+ pInvisibleContour, // unsichtbarer Umriss
+ // G
+ pG,
+ pMiddleLine, // Mittellinie
+ pSymmetryLine, // Symmetrielinie
+ pPartialCircle, // Teilkreis
+ pCircularHole, // Lochkreis
+ pDivisionPlane, // Teilungsebene
+ pTransferLine, // Trajektorien (Übertragunslinien)
+ // J
+ pJ,
+ pCuttingPlane, // Schnittebene
+ pSurfaceTreatmentRequested, // Bereich geforderter Behandlungen
+ // K
+ pK,
+ pContourBeforeDeformation, // Umrisse vor Verformung
+ pAdjacentPartContour, // Umrisse angrenzender Teile
+ pEndShapeRawMaterial, // Fertigformen in Rohteilen
+ pContourEligibleType, // Umrisse wahlweiser Ausführungen
+ pPartInFrontOfCuttingPlane; // Teile vor der Schnittebene
+
+
+
+ static sCAD Create(int nLineGroup = 1)
+ {
+ sCAD cad = new sCAD;
+ if ( nLineGroup < 0 )
+ nLineGroup = 0;
+ if ( nLineGroup > 3 )
+ nLineGroup = 3;
+ cad.nLineGroup = nLineGroup;
+
+ restricted real[] dblFullWidth = {0.35mm, 0.5mm, 0.7mm, 1.0mm};
+ restricted real[] dblHalfWidth = {0.18mm, 0.25mm, 0.35mm, 0.5mm};
+
+ pen pFullWidth = linewidth(dblFullWidth[nLineGroup]);
+ pen pHalfWidth = linewidth(dblHalfWidth[nLineGroup]);
+
+ // Linienarten:
+ // A
+ cad.pA =
+ cad.pVisibleEdge =
+ cad.pVisibleContour =
+ cad.pUsableWindingLength =
+ cad.pSystemLine =
+ cad.pDiagramCurve =
+ cad.pSurfaceStructure =
+ pFullWidth + solid;
+ // B
+ cad.pB =
+ cad.pLightEdge =
+ cad.pMeasureLine =
+ cad.pMeasureHelpLine =
+ cad.pMeasureLineBound =
+ cad.pReferenceLine =
+ cad.pHatch =
+ cad.pWindingGround =
+ cad.pDiagonalCross =
+ cad.pBendLine =
+ cad.pProjectionLine =
+ cad.pGrid =
+ pHalfWidth + solid;
+ // C
+ cad.pC =
+ cad.pFreehand =
+ pHalfWidth + solid;
+ // D
+ // Missing, as I have no idea how to implement this...
+ // E
+ cad.pE =
+ cad.pSurfaceTreatmentAllowed =
+ pFullWidth + linetype(new real[] {10,2.5});
+ // F
+ cad.pF =
+ cad.pInvisibleEdge =
+ cad.pInvisibleContour =
+ pHalfWidth + linetype(new real[] {20,5});
+ // G
+ cad.pG =
+ cad.pMiddleLine =
+ cad.pSymmetryLine =
+ cad.pPartialCircle =
+ cad.pCircularHole =
+ cad.pDivisionPlane =
+ cad.pTransferLine =
+ pHalfWidth + linetype(new real[] {40,5,5,5});
+ // H
+ // see J
+ // I
+ // This letter is not used in DIN 15
+ // J
+ cad.pJ =
+ cad.pCuttingPlane =
+ cad.pSurfaceTreatmentRequested =
+ pFullWidth + linetype(new real[] {20,2.5,2.5,2.5});
+ // K
+ cad.pK =
+ cad.pContourBeforeDeformation =
+ cad.pAdjacentPartContour =
+ cad.pEndShapeRawMaterial =
+ cad.pContourEligibleType =
+ cad.pPartInFrontOfCuttingPlane =
+ pHalfWidth + linetype(new real[] {40,5,5,5,5,5});
+
+ return cad;
+ } // end of Create
+
+
+
+ real GetMeasurementBoundSize(bool bSmallBound = false)
+ {
+ if ( bSmallBound )
+ return 1.5 * linewidth(pVisibleEdge) / 2;
+ else
+ return 5 * linewidth(pVisibleEdge);
+ }
+
+
+
+ path GetMeasurementBound(bool bSmallBound = false)
+ {
+ if ( bSmallBound )
+ return scale(GetMeasurementBoundSize(bSmallBound = bSmallBound)) *
+ unitcircle;
+ else
+ return (0,0) --
+ (-cos(radians(7.5)), -sin(radians(7.5))) *
+ GetMeasurementBoundSize(bSmallBound = bSmallBound) --
+ (-cos(radians(7.5)), sin(radians(7.5))) *
+ GetMeasurementBoundSize(bSmallBound = bSmallBound) --
+ cycle;
+ }
+
+
+
+ void MeasureLine(picture pic = currentpicture,
+ Label L,
+ pair pFrom,
+ pair pTo,
+ real dblLeft = 0,
+ real dblRight = 0,
+ real dblRelPosition = 0.5,
+ bool bSmallBound = false)
+ {
+ if ( dblLeft < 0 )
+ dblLeft = 0;
+ if ( dblRight < 0 )
+ dblRight = 0;
+ if ( (dblLeft > 0) && (dblRight == 0) )
+ dblRight = dblLeft;
+ if ( (dblLeft == 0) && (dblRight > 0) )
+ dblLeft = dblRight;
+ pair pDiff = pTo - pFrom;
+ real dblLength = length(pDiff);
+ pair pBegin = pFrom - dblLeft * unit(pDiff);
+ pair pEnd = pTo + dblRight * unit(pDiff);
+ if ( bSmallBound )
+ {
+ draw(
+ pic = pic,
+ g = pBegin--pEnd,
+ p = pMeasureLine);
+ }
+ else
+ {
+ real dblBoundSize = GetMeasurementBoundSize(bSmallBound = bSmallBound);
+ if ( dblLeft == 0 )
+ draw(
+ pic = pic,
+ g = (pFrom + dblBoundSize/2 * unit(pDiff))
+ -- (pTo - dblBoundSize/2 * unit(pDiff)),
+ p = pMeasureLine);
+ else
+ draw(
+ pic = pic,
+ g = pBegin -- (pFrom - dblBoundSize/2 * unit(pDiff))
+ ^^ pFrom -- pTo
+ ^^ (pTo + dblBoundSize/2 * unit(pDiff)) -- pEnd,
+ p = pMeasureLine);
+ }
+ path gArrow = GetMeasurementBound(bSmallBound = bSmallBound);
+ picture picL;
+ label(picL, L);
+ pair pLabelSize = 1.2 * (max(picL) - min(picL));
+ if ( dblLeft == 0 )
+ {
+ fill(
+ pic = pic,
+ g = shift(pFrom) * rotate(degrees(-pDiff)) * gArrow,
+ p = pVisibleEdge);
+ fill(
+ pic = pic,
+ g = shift(pTo) * rotate(degrees(pDiff)) * gArrow,
+ p = pVisibleEdge);
+ if ( dblRelPosition < 0 )
+ dblRelPosition = 0;
+ if ( dblRelPosition > 1 )
+ dblRelPosition = 1;
+ label(
+ pic = pic,
+ L = rotate(degrees(pDiff)) * L,
+ position =
+ pFrom
+ + dblRelPosition * pDiff
+ + unit(rotate(90)*pDiff) * pLabelSize.y / 2);
+ }
+ else
+ {
+ fill(
+ pic = pic,
+ g = shift(pFrom) * rotate(degrees(pDiff)) * gArrow,
+ p = pVisibleEdge);
+ fill(
+ pic = pic,
+ g = shift(pTo) * rotate(degrees(-pDiff)) * gArrow,
+ p = pVisibleEdge);
+ if ( (dblRelPosition >= 0) && (dblRelPosition <= 1) )
+ label(
+ pic = pic,
+ L = rotate(degrees(pDiff)) * L,
+ position =
+ pFrom
+ + dblRelPosition * pDiff
+ + unit(rotate(90)*pDiff) * pLabelSize.y / 2);
+ else
+ {
+ // draw label outside
+ if ( dblRelPosition < 0 )
+ label(
+ pic = pic,
+ L = rotate(degrees(pDiff)) * L,
+ position =
+ pBegin
+ + pLabelSize.x / 2 * unit(pDiff)
+ + unit(rotate(90)*pDiff) * pLabelSize.y / 2);
+ else
+ // dblRelPosition > 1
+ label(
+ pic = pic,
+ L = rotate(degrees(pDiff)) * L,
+ position =
+ pEnd
+ - pLabelSize.x / 2 * unit(pDiff)
+ + unit(rotate(90)*pDiff) * pLabelSize.y / 2);
+ }
+ }
+ } // end of MeasureLine
+
+
+
+ void MeasureParallel(picture pic = currentpicture,
+ Label L,
+ pair pFrom,
+ pair pTo,
+ real dblDistance,
+ // Variables from MeasureLine
+ real dblLeft = 0,
+ real dblRight = 0,
+ real dblRelPosition = 0.5,
+ bool bSmallBound = false)
+ {
+ pair pDiff = pTo - pFrom;
+ pair pPerpendicularDiff = unit(rotate(90) * pDiff);
+ real dblDistancePlus;
+ if ( dblDistance >= 0 )
+ dblDistancePlus = dblDistance + 1mm;
+ else
+ dblDistancePlus = dblDistance - 1mm;
+ draw(
+ pic = pic,
+ g = pFrom--(pFrom + dblDistancePlus*pPerpendicularDiff),
+ p = pMeasureHelpLine
+ );
+ draw(
+ pic = pic,
+ g = pTo--(pTo + dblDistancePlus*pPerpendicularDiff),
+ p = pMeasureHelpLine
+ );
+ MeasureLine(
+ pic = pic,
+ L = L,
+ pFrom = pFrom + dblDistance * pPerpendicularDiff,
+ pTo = pTo + dblDistance * pPerpendicularDiff,
+ dblLeft = dblLeft,
+ dblRight = dblRight,
+ dblRelPosition = dblRelPosition,
+ bSmallBound = bSmallBound);
+ } // end of MeasureParallel
+
+
+
+ path MakeFreehand(pair pFrom, pair pTo,
+ real dblRelDivisionLength = 12.5,
+ real dblRelDistortion = 2.5,
+ bool bIncludeTo = true)
+ {
+ pair pDiff = pTo - pFrom;
+ pair pPerpendicular = dblRelDistortion * linewidth(pFreehand) *
+ unit(rotate(90) * pDiff);
+
+ int nNumOfSubDivisions=ceil(length(pDiff) /
+ (dblRelDivisionLength * linewidth(pFreehand)));
+
+ restricted real[] dblDistortion = {1, -.5, .75, -.25, .25, -1, .5, -.75,
+ .25, -.25};
+ int nDistortion = 0;
+
+ guide g;
+ g = pFrom;
+ for ( int i = 1 ; i < nNumOfSubDivisions ; ++i )
+ {
+ g = g ..
+ (pFrom
+ + pDiff * i / (real)nNumOfSubDivisions
+ + pPerpendicular * dblDistortion[nDistortion]);
+ nDistortion += 1;
+ if ( nDistortion > 9 )
+ nDistortion = 0;
+ }
+
+ if ( bIncludeTo )
+ g = g .. pTo;
+
+ return g;
+ } // end of MakeFreehand
+
+
+
+} // end of CAD
+
diff --git a/graphics/asymptote/base/animate.asy b/graphics/asymptote/base/animate.asy
new file mode 100644
index 0000000000..586e71041b
--- /dev/null
+++ b/graphics/asymptote/base/animate.asy
@@ -0,0 +1,3 @@
+usepackage("animate");
+import animation;
+
diff --git a/graphics/asymptote/base/animation.asy b/graphics/asymptote/base/animation.asy
new file mode 100644
index 0000000000..e2fd1ea18c
--- /dev/null
+++ b/graphics/asymptote/base/animation.asy
@@ -0,0 +1,188 @@
+/*****
+ * animation.asy
+ * Andy Hammerlindl and John Bowman 2005/11/06
+ *
+ * Produce GIF, inline PDF, or other animations.
+ *****/
+
+// animation delay is in milliseconds
+real animationdelay=50;
+
+typedef frame enclosure(frame);
+
+frame NoBox(frame f) {
+ return f;
+}
+
+enclosure BBox(real xmargin=0, real ymargin=xmargin,
+ pen p=currentpen, filltype filltype=NoFill) {
+ return new frame(frame f) {
+ box(f,xmargin,ymargin,p,filltype,above=false);
+ return f;
+ };
+}
+
+struct animation {
+ picture[] pictures;
+ string[] files;
+ int index;
+
+ string prefix;
+ bool global; // If true, use a global scaling for all frames; this requires
+ // extra memory since the actual shipout is deferred until all frames have
+ // been generated.
+
+ void operator init(string prefix="", bool global=true) {
+ prefix=replace(stripdirectory(outprefix(prefix))," ","_");
+ this.prefix=prefix;
+ this.global=global;
+ }
+
+ string basename(string prefix=stripextension(prefix)) {
+ return "_"+prefix;
+ }
+
+ string name(string prefix, int index) {
+ return stripextension(prefix)+"+"+string(index);
+ }
+
+ private string nextname() {
+ string name=basename(name(prefix,index));
+ ++index;
+ return name;
+ }
+
+ void shipout(string name=nextname(), frame f) {
+ string format=nativeformat();
+ plain.shipout(name,f,format=format,view=false);
+ files.push(name+"."+format);
+ }
+
+ void add(picture pic=currentpicture, enclosure enclosure=NoBox) {
+ if(global) {
+ ++index;
+ pictures.push(pic.copy());
+ } else this.shipout(enclosure(pic.fit()));
+ }
+
+ void purge(bool keep=settings.keep) {
+ if(!keep) {
+ for(int i=0; i < files.length; ++i)
+ delete(files[i]);
+ }
+ }
+
+ int merge(int loops=0, real delay=animationdelay, string format="gif",
+ string options="", bool keep=settings.keep) {
+ string args="-loop " +(string) loops+" -delay "+(string)(delay/10)+
+ " -alpha Off -dispose Background "+options;
+ for(int i=0; i < files.length; ++i)
+ args += " " +files[i];
+ int rc=convert(args,prefix+"."+format,format=format);
+ this.purge(keep);
+ if(rc == 0) animate(file=prefix+"."+format,format=format);
+ else abort("merge failed");
+ return rc;
+ }
+
+ void glmovie(string prefix=prefix, projection P=currentprojection) {
+ if(!view() || settings.render == 0) return;
+ fit(prefix,pictures,view=true,P);
+ }
+
+ // Export all frames with the same scaling.
+ void export(string prefix=prefix, enclosure enclosure=NoBox,
+ bool multipage=false, bool view=false,
+ projection P=currentprojection) {
+ if(pictures.length == 0) return;
+ if(!global) multipage=false;
+ bool inlinetex=settings.inlinetex;
+ if(multipage)
+ settings.inlinetex=false;
+ frame multi;
+ frame[] fits=fit(prefix,pictures,view=false,P);
+ for(int i=0; i < fits.length; ++i) {
+ string s=name(prefix,i);
+ if(multipage) {
+ add(multi,enclosure(fits[i]));
+ newpage(multi);
+ files.push(s+"."+nativeformat());
+ } else {
+ if(pictures[i].empty3() || settings.render <= 0)
+ this.shipout(s,enclosure(fits[i]));
+ else // 3D frames
+ files.push(s+"."+nativeformat());
+ }
+ }
+ if(multipage) {
+ plain.shipout(prefix,multi,view=view);
+ settings.inlinetex=inlinetex;
+ }
+ }
+
+ string load(int frames, real delay=animationdelay, string options="",
+ bool multipage=false) {
+ if(!global) multipage=false;
+ string s="\animategraphics["+options+"]{"+format("%.18f",1000/delay,"C")+
+ "}{"+basename();
+ if(!multipage) s += "+";
+ s += "}{0}{"+string(frames-1)+"}";
+ return s;
+ }
+
+ bool pdflatex()
+ {
+ return latex() && pdf();
+ }
+
+ string pdf(enclosure enclosure=NoBox, real delay=animationdelay,
+ string options="", bool keep=settings.keep, bool multipage=true) {
+ settings.twice=true;
+ if(settings.inlinetex) multipage=true;
+ if(!global) multipage=false;
+ if(!pdflatex())
+ abort("inline pdf animations require -tex pdflatex or -tex xelatex");
+ if(settings.outformat != "") settings.outformat="pdf";
+
+ string filename=basename();
+ string pdfname=filename+".pdf";
+
+ if(global)
+ export(filename,enclosure,multipage=multipage);
+
+ if(!keep) {
+ exitfcn currentexitfunction=atexit();
+ void exitfunction() {
+ if(currentexitfunction != null) currentexitfunction();
+ if(multipage || !settings.inlinetex)
+ this.purge();
+ if(multipage && !settings.inlinetex)
+ delete(pdfname);
+ }
+ atexit(exitfunction);
+ }
+
+ if(!multipage)
+ delete(pdfname);
+
+ return load(index,delay,options,multipage);
+ }
+
+ int movie(enclosure enclosure=NoBox, int loops=0, real delay=animationdelay,
+ string format=settings.outformat == "" ? "gif" : settings.outformat,
+ string options="", bool keep=settings.keep) {
+ if(global) {
+ if(format == "pdf") {
+ export(enclosure,multipage=true,view=true);
+ return 0;
+ }
+ export(enclosure);
+ }
+ return merge(loops,delay,format,options,keep);
+ }
+}
+
+animation operator init() {
+ animation a=animation();
+ return a;
+}
diff --git a/graphics/asymptote/base/annotate.asy b/graphics/asymptote/base/annotate.asy
new file mode 100644
index 0000000000..aba9309cb6
--- /dev/null
+++ b/graphics/asymptote/base/annotate.asy
@@ -0,0 +1,16 @@
+void annotate(picture pic=currentpicture, string title, string text,
+ pair position)
+{
+ pic.add(new void(frame f, transform t) {
+ position=t*position;
+ label(f,"\special{!/pdfmark where
+ {pop} {userdict /pdfmark /cleartomark load put} ifelse
+ [/Rect["+(string) position.x+" 0 0 "+(string) position.y+"]
+ /Subtype /Text
+ /Name /Comment
+ /Title ("+title+")
+ /Contents ("+text+")
+ /ANN pdfmark}");
+ },true);
+ draw(pic,position,invisible);
+}
diff --git a/graphics/asymptote/base/asy-init.el b/graphics/asymptote/base/asy-init.el
new file mode 100644
index 0000000000..0e3f178a22
--- /dev/null
+++ b/graphics/asymptote/base/asy-init.el
@@ -0,0 +1,4 @@
+(autoload 'asy-mode "asy-mode.el" "Asymptote major mode." t)
+(autoload 'lasy-mode "asy-mode.el" "hybrid Asymptote/Latex major mode." t)
+(autoload 'asy-insinuate-latex "asy-mode.el" "Asymptote insinuate LaTeX." t)
+(add-to-list 'auto-mode-alist '("\\.asy$" . asy-mode))
diff --git a/graphics/asymptote/base/asy-kate.sh b/graphics/asymptote/base/asy-kate.sh
new file mode 100644
index 0000000000..5f482b376d
--- /dev/null
+++ b/graphics/asymptote/base/asy-kate.sh
@@ -0,0 +1,146 @@
+#!/bin/sh
+echo '<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE language SYSTEM "language.dtd">
+<!-- based on asy-keywords.el and Highlighting file asymptote.xml by Christoph Hormann-->
+<language version="1.0" kateversion="3.2.2" name="asymptote" section="Sources" extensions="*.asy" mimetype="text/x-asymptote" licence="LGPL" author="Carsten Brenner">
+
+<highlighting>
+' > asymptote.xml
+
+# 1. Change Name of lists in <\list> <list name="...">
+# 2. tail to get rid of the first lines
+# 3. building the right line ending
+# 4-5. kill linebreaks
+# 6. change spaces into <\item><item>
+# 7. Undo change (7.) in 'list name'
+# 8. do some formatting
+
+cat asy-keywords.el | sed 's/^(.*\-\([^\-]*\)\-.*/\n<list name="\1"><item>/' | tail -14 | sed 's/ ))/<\/item><\/list>/' | tr '\n' '@' | sed 's/@//g' | sed 's/ /<\/item><item>/g' | sed 's/list<\/item><item>name/list name/g' | sed 's/></>\n</g' >> asymptote.xml
+
+echo '
+<contexts>
+ <context attribute="Normal Text" lineEndContext="#stay" name="Normal">
+ <DetectSpaces />
+ <RegExpr attribute="Preprocessor" context="Outscoped" String="#\s*if\s+0" beginRegion="Outscoped" firstNonSpace="true" />
+ <DetectChar attribute="Preprocessor" context="Preprocessor" char="#" firstNonSpace="true" />
+ <StringDetect attribute="Region Marker" context="Region Marker" String="//BEGIN" beginRegion="Region1" firstNonSpace="true" />
+ <StringDetect attribute="Region Marker" context="Region Marker" String="//END" endRegion="Region1" firstNonSpace="true" />
+ <keyword attribute="Keyword" context="#stay" String="keyword" />
+ <keyword attribute="Extensions" context="#stay" String="extensions" />
+ <keyword attribute="Function" context="#stay" String="function" />
+ <keyword attribute="Data Type" context="#stay" String="type" />
+ <keyword attribute="Constants" context="#stay" String="constants" />
+ <keyword attribute="Variable" context="#stay" String="variable" />
+ <HlCChar attribute="Char" context="#stay"/>
+ <DetectChar attribute="String" context="String" char="&quot;"/>
+ <DetectIdentifier />
+ <Float attribute="Float" context="#stay">
+ <AnyChar String="fF" attribute="Float" context="#stay"/>
+ </Float>
+ <HlCOct attribute="Octal" context="#stay"/>
+ <HlCHex attribute="Hex" context="#stay"/>
+ <Int attribute="Decimal" context="#stay">
+ <StringDetect attribute="Decimal" context="#stay" String="ULL" insensitive="TRUE"/>
+ <StringDetect attribute="Decimal" context="#stay" String="LUL" insensitive="TRUE"/>
+ <StringDetect attribute="Decimal" context="#stay" String="LLU" insensitive="TRUE"/>
+ <StringDetect attribute="Decimal" context="#stay" String="UL" insensitive="TRUE"/>
+ <StringDetect attribute="Decimal" context="#stay" String="LU" insensitive="TRUE"/>
+ <StringDetect attribute="Decimal" context="#stay" String="LL" insensitive="TRUE"/>
+ <StringDetect attribute="Decimal" context="#stay" String="U" insensitive="TRUE"/>
+ <StringDetect attribute="Decimal" context="#stay" String="L" insensitive="TRUE"/>
+ </Int>
+ <IncludeRules context="##Doxygen" />
+ <Detect2Chars attribute="Comment" context="Commentar 1" char="/" char1="/"/>
+ <Detect2Chars attribute="Comment" context="Commentar 2" char="/" char1="*" beginRegion="Comment"/>
+ <DetectChar attribute="Symbol" context="#stay" char="{" beginRegion="Brace1" />
+ <DetectChar attribute="Symbol" context="#stay" char="}" endRegion="Brace1" />
+ <AnyChar attribute="Symbol" context="#stay" String=":!%&amp;()+,-/.*&lt;=&gt;?[]{|}~^&#59;"/>
+ </context>
+ <context attribute="String" lineEndContext="#pop" name="String">
+ <LineContinue attribute="String" context="#stay"/>
+ <HlCStringChar attribute="String Char" context="#stay"/>
+ <DetectChar attribute="String" context="#pop" char="&quot;"/>
+ </context>
+ <context attribute="Region Marker" lineEndContext="#pop" name="Region Marker">
+ </context>
+ <context attribute="Comment" lineEndContext="#pop" name="Commentar 1">
+ <DetectSpaces />
+ <IncludeRules context="##Alerts" />
+ <DetectIdentifier />
+ </context>
+ <context attribute="Comment" lineEndContext="#stay" name="Commentar 2">
+ <DetectSpaces />
+ <Detect2Chars attribute="Comment" context="#pop" char="*" char1="/" endRegion="Comment"/>
+ <IncludeRules context="##Alerts" />
+ <DetectIdentifier />
+ </context>
+ <context attribute="Preprocessor" lineEndContext="#pop" name="Preprocessor">
+ <LineContinue attribute="Preprocessor" context="#stay"/>
+ <RegExpr attribute="Preprocessor" context="Define" String="define.*((?=\\))"/>
+ <RegExpr attribute="Preprocessor" context="#stay" String="define.*"/>
+ <RangeDetect attribute="Prep. Lib" context="#stay" char="&quot;" char1="&quot;"/>
+ <RangeDetect attribute="Prep. Lib" context="#stay" char="&lt;" char1="&gt;"/>
+ <IncludeRules context="##Doxygen" />
+ <Detect2Chars attribute="Comment" context="Commentar 1" char="/" char1="/"/>
+ <Detect2Chars attribute="Comment" context="Commentar/Preprocessor" char="/" char1="*"/>
+ </context>
+ <context attribute="Preprocessor" lineEndContext="#pop" name="Define">
+ <LineContinue attribute="Preprocessor" context="#stay"/>
+ </context>
+ <context attribute="Comment" lineEndContext="#stay" name="Commentar/Preprocessor">
+ <DetectSpaces />
+ <Detect2Chars attribute="Comment" context="#pop" char="*" char1="/" />
+ <DetectIdentifier />
+ </context>
+ <context attribute="Comment" lineEndContext="#stay" name="Outscoped" >
+ <DetectSpaces />
+ <IncludeRules context="##Alerts" />
+ <DetectIdentifier />
+ <DetectChar attribute="String" context="String" char="&quot;"/>
+ <IncludeRules context="##Doxygen" />
+ <Detect2Chars attribute="Comment" context="Commentar 1" char="/" char1="/"/>
+ <Detect2Chars attribute="Comment" context="Commentar 2" char="/" char1="*" beginRegion="Comment"/>
+ <RegExpr attribute="Comment" context="Outscoped intern" String="#\s*if" beginRegion="Outscoped" firstNonSpace="true" />
+ <RegExpr attribute="Preprocessor" context="#pop" String="#\s*(endif|else|elif)" endRegion="Outscoped" firstNonSpace="true" />
+ </context>
+ <context attribute="Comment" lineEndContext="#stay" name="Outscoped intern">
+ <DetectSpaces />
+ <IncludeRules context="##Alerts" />
+ <DetectIdentifier />
+ <DetectChar attribute="String" context="String" char="&quot;"/>
+ <IncludeRules context="##Doxygen" />
+ <Detect2Chars attribute="Comment" context="Commentar 1" char="/" char1="/"/>
+ <Detect2Chars attribute="Comment" context="Commentar 2" char="/" char1="*" beginRegion="Comment"/>
+ <RegExpr attribute="Comment" context="Outscoped intern" String="#\s*if" beginRegion="Outscoped" firstNonSpace="true"/>
+ <RegExpr attribute="Comment" context="#pop" String="#\s*endif" endRegion="Outscoped" firstNonSpace="true"/>
+ </context>
+ </contexts>
+ <itemDatas>
+ <itemData name="Char" defStyleNum="dsChar"/>
+ <itemData name="Comment" defStyleNum="dsComment"/>
+ <itemData name="Data Type" defStyleNum="dsDataType"/>
+ <itemData name="Decimal" defStyleNum="dsDecVal"/>
+ <itemData name="Extensions" defStyleNum="dsKeyword" color="#0095ff" selColor="#ffffff" bold="1" italic="0"/>
+ <itemData name="Float" defStyleNum="dsFloat"/>
+ <itemData name="Function" defStyleNum="dsFunction" />
+ <itemData name="Hex" defStyleNum="dsBaseN"/>
+ <itemData name="Keyword" defStyleNum="dsKeyword"/>
+ <itemData name="Normal Text" defStyleNum="dsNormal"/>
+ <itemData name="Octal" defStyleNum="dsBaseN"/>
+ <itemData name="Prep. Lib" defStyleNum="dsOthers"/>
+ <itemData name="Preprocessor" defStyleNum="dsOthers"/>
+ <itemData name="Region Marker" defStyleNum="dsRegionMarker" />
+ <itemData name="String Char" defStyleNum="dsChar"/>
+ <itemData name="String" defStyleNum="dsString"/>
+ <itemData name="Symbol" defStyleNum="dsNormal"/>
+ <itemData name="Variable" defStyleNum="dsOthers" />
+ </itemDatas>
+ </highlighting>
+ <general>
+ <comments>
+ <comment name="singleLine" start="//" />
+ <comment name="multiLine" start="/*" end="*/" region="Comment"/>
+ </comments>
+ <keywords casesensitive="1" />
+ </general>
+ </language>' >> asymptote.xml
diff --git a/graphics/asymptote/base/asy-mode.el b/graphics/asymptote/base/asy-mode.el
new file mode 100644
index 0000000000..c1960e2303
--- /dev/null
+++ b/graphics/asymptote/base/asy-mode.el
@@ -0,0 +1,1600 @@
+;;; asy-mode.el --- Major mode for editing Asymptote source code.
+
+;; Copyright (C) 2006-8
+
+;; Author: Philippe IVALDI 20 August 2006
+;; Maintainer: John Bowman
+;; URL: https://github.com/vectorgraphics/asymptote
+;; Version: 1.6
+;; Keywords: language, mode
+
+;;; License:
+
+;; This program is free software ; you can redistribute it and/or modify
+;; it under the terms of the GNU Lesser 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
+;; Lesser General Public License for more details.
+;;
+;; You should have received a copy of the GNU Lesser General Public License
+;; along with this program ; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+;;; Commentary
+
+;; Major mode for editing Asymptote source code.
+
+;; INSTALLATION:
+;; Place this file (asy-mode.el) and asy-keywords.el in your Emacs load path.
+;; Then choose ONE of the following installation methods:
+
+;; * Method 1:
+;; Copy and uncomment the following lines to your .emacs initialization file:
+;;(autoload 'asy-mode "asy-mode.el" "Asymptote major mode." t)
+;;(autoload 'lasy-mode "asy-mode.el" "hybrid Asymptote/Latex major mode." t)
+;;(autoload 'asy-insinuate-latex "asy-mode.el" "Asymptote insinuate LaTeX." t)
+;;(add-to-list 'auto-mode-alist '("\\.asy$" . asy-mode))
+
+;; * Method 2:
+;; Copy and uncomment the following line to your .emacs initialization file:
+;;(require 'asy-mode)
+
+;; Notes:
+;;
+;; For full functionality the two-mode-mode package should also be installed
+;; (http://www.dedasys.com/freesoftware/files/two-mode-mode.el).
+;;
+;; See also paragraph II of the documentation below to automate asy-insinuate-latex.
+
+;;; Code:
+
+(defvar asy-mode-version "1.6")
+
+;;;###autoload
+(define-derived-mode asy-mode objc-mode "Asymptote"
+ "Emacs mode for editing Asymptote source code.
+For full functionality the `two-mode-mode' package should also be installed
+(http://www.dedasys.com/freesoftware/files/two-mode-mode.el).
+
+I. This package provides two modes:
+1- asy-mode:
+All the files with the extension '.asy' are edited in this mode, which provides the following features:
+* Syntax color highlighting;
+* Compiling and viewing current buffer with the key binding C-c C-c;
+* Moving cursor to the error by pressing the key F4.
+* Showing the available function prototypes for the command at the cursor with the key binding C-c ?
+* Compiling and viewing a TeX document linked with the current buffer (usually a document that includes the output picture).
+To link a Tex document try 'M-x asy-set-master-tex' follow by C-Return (see descriptions further of the key binding C-Return, C-S-Return, M-Return, M-S-Return etc within 2- lasy-mode)
+
+2- lasy-mode
+Editing a TeX file that contains Asymptote code is facilitated with the hybrid mode 'lasy-mode'.
+Toggle lasy-mode with M-x lasy-mode.
+In this hybrid mode the major mode is LaTeX when the cursor is in LaTeX code and becomes asy-mode when the cursor is between '\\begin{asy}' and '\\end{asy}'.
+All the features of asy-mode are provided and the key binding C-c C-c of asy-mode compiles and views only the code of the picture where the cursor is.
+Note that some keys binding are added to the LaTeX-mode-map in lasy-mode if the value of the variable lasy-extra-key is t (the default)
+.
+* C-return: compile (if the buffer/file is modified) and view the PostScript output with sequence [latex->[asy->latex]->dvips]->PSviewer
+* M-return: same with pdf output and with the sequence [pdflatex->[asy->pdflatex]]->PDFviewer
+* C-M-return: same with pdf output and with the sequence [latex->[asy->latex]->dvips->ps2pdf]->PSviewer
+* Add the Shift key to the sequence of keys to compile even if the file is not modified.
+
+II. To add a menu bar in current 'latex-mode' buffer and activate hot keys, use 'M-x asy-insinuate-latex <RET>'.
+You can automate this feature for all the 'latex-mode' buffers by inserting the five following lines in your .emacs initialization file:
+(eval-after-load \"latex\"
+ '(progn
+ ;; Add here your personal features for 'latex-mode':
+ (asy-insinuate-latex t) ;; Asymptote globally insinuates Latex.
+ ))
+
+You can access this help within Emacs by the key binding C-h f asy-mode <RET>
+
+BUGS:
+This package has been tested in:
+* Linux Debian Etch
+- GNU Emacs 22.0.50.1
+- GNU Emacs 21.4.1 (only basic errors management and basic font-lock features within lasy-mode are supported)
+* WindowsXP
+- GNU Emacs 22.0.990.1 (i386-mingw-nt5.1.2600)
+
+This package seems to work with XEmacs 21.4 but not all the features are available (in particular syntax highlighting).
+
+Report bugs to http://asymptote.sourceforge.net
+
+Some variables can be customized: M-x customize-group <RET> asymptote <RET>."
+
+ (setq c++-font-lock-extra-types (cons "true" c++-font-lock-extra-types)))
+
+(require 'font-lock)
+(require 'cc-mode)
+(require 'cl) ;; Common Lisp extensions for Emacs
+(require 'compile)
+(require 'wid-edit)
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.asy$" . asy-mode))
+
+(defvar running-xemacs-p (featurep 'xemacs))
+(defvar running-unix-p (not (string-match "windows-nt\\|ms-dos" (symbol-name system-type))))
+
+(when running-xemacs-p
+ (defalias 'turn-on-font-lock-if-enabled 'ignore)
+ (defalias 'line-number-at-pos 'line-number)
+ (defvar temporary-file-directory (temp-directory))
+ (defun replace-regexp-in-string (regexp rep string)
+ (replace-in-string string regexp rep))
+ )
+
+(when (or (< emacs-major-version 22) running-xemacs-p)
+ ;; Add regexp for parsing the compilation errors of asy
+ (add-to-list 'compilation-error-regexp-alist
+ '("\\(.*?.asy\\): \\(.*?\\)\\.\\(.*?\\):" 1 2 3)))
+
+(when (< emacs-major-version 22)
+ (defun line-number-at-pos (&optional pos)
+ "Return (narrowed) buffer line number at position POS.
+If POS is nil, use current buffer location.
+Counting starts at (point-min), so the value refers
+to the contents of the accessible portion of the buffer."
+ (let ((opoint (or pos (point))) start)
+ (save-excursion
+ (goto-char (point-min))
+ (setq start (point))
+ (goto-char opoint)
+ (forward-line 0)
+ (1+ (count-lines start (point)))))))
+
+(defcustom lasy-extra-key t
+ "* If on, the folowing binding keys are added in lasy-mode :
+ (define-key lasy-mode-map (kbd \"<C-return>\") 'lasy-view-ps)
+ (define-key lasy-mode-map (kbd \"<C-S-return>\") 'asy-master-tex-view-ps-f)
+ (define-key lasy-mode-map (kbd \"<M-return>\") 'lasy-view-pdf-via-pdflatex)
+ (define-key lasy-mode-map (kbd \"<M-S-return>\") 'asy-master-tex-view-pdflatex-f)
+ (define-key lasy-mode-map (kbd \"<C-M-return>\") 'lasy-view-pdf-via-ps2pdf)
+ (define-key lasy-mode-map (kbd \"<C-M-S-return>\") 'asy-master-tex-view-ps2pdf-f)
+
+If you also want this feature in pure latex-mode, you can set this variable to `nil' and add these lines in your .emacs:
+
+(require 'asy-mode)
+(eval-after-load \"latex\"
+ '(progn
+ (define-key LaTeX-mode-map (kbd \"<C-return>\") 'lasy-view-ps)
+ (define-key LaTeX-mode-map (kbd \"<C-S-return>\") 'asy-master-tex-view-ps-f)
+ (define-key LaTeX-mode-map (kbd \"<M-return>\") 'lasy-view-pdf-via-pdflatex)
+ (define-key LaTeX-mode-map (kbd \"<M-S-return>\") 'asy-master-tex-view-pdflatex-f)
+ (define-key LaTeX-mode-map (kbd \"<C-M-return>\") 'lasy-view-pdf-via-ps2pdf)
+ (define-key LaTeX-mode-map (kbd \"<C-M-S-return>\") 'asy-master-tex-view-ps2pdf-f)))"
+ :type 'boolean
+ :group 'asymptote)
+
+(defcustom asy-compilation-buffer 'none
+ " 'visible means keep compilation buffer visible ;
+ 'available means keep compilation buffer available in other buffer but not visible;
+ 'none means delete compilation buffer automatically after a *successful* compilation.
+ 'never means don't open any window or buffer attached to the compilation process.
+If the value is 'never':
+* Emacs is suspended until the child program returns;
+* the management of errors is poorer than with other value;
+* the compilation doesn't modify your current window configuration."
+ :type '(choice (const visible) (const available) (const none) (const never))
+ :group 'asymptote)
+
+(defcustom lasy-ask-about-temp-compilation-buffer t
+ "* If t, ask before visiting a temporary buffer of compilation."
+ :type 'boolean
+ :group 'asymptote)
+
+(defcustom lasy-compilation-inline-auto-detection nil
+ "* If t, lasy-mode detects automatically if the option 'inline' is passed to asymptote.sty.
+In case of 'inline' option, the compilation of a figure separately of the document is processed by rebuilding the preamble and compiling it as a file '.tex' containing only this picture.
+ If nil (the default), the compilation of a figure separately of the document is processed by building a file '.asy', without the features of the LaTeX preamble."
+ :type 'boolean
+ :group 'asymptote)
+
+(defcustom asy-command-location ""
+ "* If not in the path, you can put here the name of the directory containing Asy's binary files.
+this variable must end in /."
+ :type 'directory
+ :group 'asymptote)
+
+(defcustom asy-command "asy -V"
+ "* Command invoked to compile a Asymptote file.
+You can define the location of this command with the variable `asy-command-location'."
+ :type 'string
+ :group 'asymptote)
+
+(defcustom lasy-command "asy"
+ "* Command invoked to compile a Asymptote file generated compiling a .tex file.
+You can define the location of this command with the variable `asy-command-location'."
+ :type 'string
+ :group 'asymptote)
+
+(defcustom lasy-latex-command "latex -halt-on-error"
+ "* Command invoked to compile a .tex file with LaTeX."
+ :type 'string
+ :group 'asymptote)
+
+(defcustom lasy-pdflatex-command "pdflatex -halt-on-error"
+ "* Command invoked to compile a .tex file with pdflaTex."
+ :type 'string
+ :group 'asymptote)
+
+(defcustom lasy-dvips-pre-pdf-command "dvips -Ppdf"
+ "* Command invoked to convert a .dvi file to a temporary .ps file in order to
+generate a final .pdf file."
+ :type 'string
+ :group 'asymptote)
+
+(defcustom lasy-dvips-command "dvips -q"
+ "* Command invoked to convert a .dvi file to a final .ps file."
+ :type 'string
+ :group 'asymptote)
+
+(defcustom lasy-ps2pdf-command "ps2pdf14"
+ "* Command invoked to convert a .dvi file to .ps file."
+ :type 'string
+ :group 'asymptote)
+
+(defcustom asy-temp-dir temporary-file-directory
+ "*The name of a directory for Asy's temporary files.
+Such files are generated by functions like
+`asy-compile' when lasy-mode is enable."
+ :type 'directory
+ :group 'asymptote)
+
+(defcustom ps-view-command (if running-unix-p "gv" "")
+ "Command to view a PostScript file generated by compiling a tex file within lasy-mode.
+This variable is not used when running the Windows OS.
+See `asy-open-file'."
+ :type 'string
+ :group 'asymptote)
+
+(defcustom pdf-view-command
+ (if running-unix-p
+ "xpdf" "")
+ "Command to view a pdf file generated by compiling a tex file within lasy-mode.
+This variable is not used when running the Windows OS.
+See `asy-open-file'."
+ :type 'string
+ :group 'asymptote)
+
+(defvar asy-TeX-master-file nil
+ "TeX file associate with current asymptote code.
+This variable must be modified only using the function 'asy-set-master-tex by M-x asy-set-master-tex <RET>.")
+(make-variable-buffer-local 'asy-TeX-master-file)
+
+(defvar lasy-compile-tex nil
+ "* Internal use. t if LaTeX compilation come from latex-mode.")
+
+(when (fboundp 'font-lock-add-keywords)
+ (if (< max-specpdl-size 2000) (setq max-specpdl-size 2000))
+ (defun asy-add-function-keywords (function-keywords face-name)
+ (let* ((keyword-list (mapcar #'(lambda (x)
+ (symbol-name x))
+ function-keywords))
+ (keyword-regexp (concat "\\<\\("
+ (regexp-opt keyword-list)
+ "\\)(")))
+ (font-lock-add-keywords 'asy-mode
+ `((,keyword-regexp 1 ',face-name)))))
+
+ (defun asy-add-variable-keywords (function-keywords face-name)
+ (let* ((keyword-list (mapcar #'(lambda (x)
+ (symbol-name x))
+ function-keywords))
+ (keyword-regexp (concat "\\<[0-9]*\\("
+ (regexp-opt keyword-list)
+ "\\)\\(?:[^(a-zA-Z]\\|\\'\\)")))
+ (font-lock-add-keywords 'asy-mode
+ `((,keyword-regexp 1 ',face-name)))))
+
+ ;; External definitions of keywords:
+ ;; asy-function-name and asy-variable-name
+ (if (locate-library "asy-keywords.el")
+ (load "asy-keywords.el")
+ (progn
+ ;; Use dummy keyword definitions if asy-keywords.el is not found:
+ (defvar asy-keyword-name nil)
+ (defvar asy-type-name nil)
+ (defvar asy-function-name nil)
+ (defvar asy-variable-name nil)))
+
+ (defcustom asy-extra-type-name '()
+ "Extra user type names highlighted with 'font-lock-type-face"
+ :type '(repeat symbol)
+ :group 'asymptote)
+
+ (defcustom asy-extra-function-name
+ '()
+ "Extra user function names highlighted with 'font-lock-function-name-face"
+ :type '(repeat symbol)
+ :group 'asymptote)
+
+ (defcustom asy-extra-variable-name '()
+ "Extra user variable names highlighted with 'font-lock-constant-face"
+ :type '(repeat symbol)
+ :group 'asymptote)
+
+ (asy-add-variable-keywords
+ asy-keyword-name
+ 'font-lock-builtin-face)
+
+ (asy-add-variable-keywords
+ (nconc asy-type-name asy-extra-type-name)
+ 'font-lock-type-face)
+
+ (asy-add-function-keywords
+ (nconc asy-function-name asy-extra-function-name)
+ 'font-lock-function-name-face)
+
+ (asy-add-variable-keywords
+ (nconc asy-variable-name asy-extra-variable-name)
+ 'font-lock-constant-face)
+
+ (defface asy-environment-face
+ `((t
+ (:underline t :inverse-video t)))
+ "Face used to highlighting the keywords '\\begin{asy}' and '\\end{asy}' within lasy-mode."
+ :group 'asymptote)
+
+ (font-lock-add-keywords
+ 'asy-mode
+ '(("\\\\begin{asy}.*" . 'asy-environment-face)
+ ("\\\\end{asy}" . 'asy-environment-face)))
+
+ (defface asy-link-face ;; widget-field-face
+ `((t
+ (:underline t)))
+ "Face used to highlighting the links."
+ :group 'asymptote)
+
+ (font-lock-add-keywords
+ 'asy-mode
+ '(("\\[.*?\\.asy\\]" . 'asy-link-face)))
+ )
+
+(setq buffers-menu-max-size nil)
+(setq mode-name "Asymptote")
+
+(if running-xemacs-p
+ (defvar asy-menu
+ '("Asy"
+ ["Toggle lasy-mode" lasy-mode :active (and (featurep 'two-mode-mode) two-mode-bool)]
+ ["Compile/View" asy-compile t]
+ ["Go to error" asy-goto-error t]
+ ["Describe command" asy-show-function-at-point t]"--"
+ ("Master TeX file"
+ ["Set/Change value" (asy-set-master-tex) :active (not (and (boundp two-mode-bool) two-mode-bool))]
+ ["Erase value" (asy-unset-master-tex) :active (not (and (boundp two-mode-bool) two-mode-bool))]
+ ("Compile OR View"
+ ["PS" asy-master-tex-view-ps :active t]
+ ["PDF (pdflatex)" asy-master-tex-view-pdflatex :active t]
+ ["PDF (ps2pdf)" asy-master-tex-view-ps2pdf :active t])
+ ("Compile AND View"
+ ["PS" asy-master-tex-view-ps-f :active t]
+ ["PDF (pdflatex)" asy-master-tex-view-pdflatex-f :active t]
+ ["PDF (ps2pdf)" asy-master-tex-view-ps2pdf-f :active t]))
+ ["Asymptote insinuates globally LaTeX" asy-insinuate-latex-globally :active (not asy-insinuate-latex-globally-p)]"--"
+ ("Debugger Buffer"
+ ["Visible" (setq asy-compilation-buffer 'visible) :style radio :selected (eq asy-compilation-buffer 'visible) :active t]
+ ["Available" (setq asy-compilation-buffer 'available) :style radio :selected (eq asy-compilation-buffer 'available) :active t]
+ ["None" (setq asy-compilation-buffer 'none) :style radio :selected (eq asy-compilation-buffer 'none) :active t]
+ ["Never" (setq asy-compilation-buffer 'never) :style radio :selected (eq asy-compilation-buffer 'never) :active t])
+ ("Compilation Options" :included (and (featurep 'two-mode-mode) two-mode-bool)
+ ["Enable Automatic Detection of Option" (setq lasy-compilation-inline-auto-detection t) :style radio :selected lasy-compilation-inline-auto-detection :active t]
+ ["Disable Automatic Detection of Option" (setq lasy-compilation-inline-auto-detection nil) :style radio :selected (not lasy-compilation-inline-auto-detection) :active t])
+ ["Customize" (customize-group "asymptote") :active t]
+ ["Help" (describe-function 'asy-mode) :active t]
+ ))
+ (defvar asy-menu
+ '("Asy"
+ ["Toggle Lasy-Mode" lasy-mode :visible (and (featurep 'two-mode-mode) two-mode-bool)]
+ ["Compile/View" asy-compile t]
+ ["Go to Error" asy-goto-error t]
+ ["Describe Command" asy-show-function-at-point t]"--"
+ ("Master TeX File"
+ ["Set/Change Value" (asy-set-master-tex) :active (not (and (boundp two-mode-bool) two-mode-bool)) :key-sequence nil]
+ ["Erase Value" (asy-unset-master-tex) :active (not (and (boundp two-mode-bool) two-mode-bool)) :key-sequence nil]
+ ("Compile or View"
+ ["PS" asy-master-tex-view-ps :active t]
+ ["PDF (pdflatex)" asy-master-tex-view-pdflatex :active t]
+ ["PDF (ps2pdf)" asy-master-tex-view-ps2pdf :active t])
+ ("Compile and View"
+ ["PS" asy-master-tex-view-ps-f :active t]
+ ["PDF (pdflatex)" asy-master-tex-view-pdflatex-f :active t]
+ ["PDF (ps2pdf)" asy-master-tex-view-ps2pdf-f :active t]))
+ ["Asymptote Insinuates Globally LaTeX" asy-insinuate-latex-globally :active (not asy-insinuate-latex-globally-p)]"--"
+ ("Debugger Buffer"
+ ["Visible" (setq asy-compilation-buffer 'visible) :style radio :selected (eq asy-compilation-buffer 'visible) :active t :key-sequence nil]
+ ["Available" (setq asy-compilation-buffer 'available) :style radio :selected (eq asy-compilation-buffer 'available) :active t :key-sequence nil]
+ ["None" (setq asy-compilation-buffer 'none) :style radio :selected (eq asy-compilation-buffer 'none) :active t :key-sequence nil]
+ ["Never" (setq asy-compilation-buffer 'never) :style radio :selected (eq asy-compilation-buffer 'never) :active t :key-sequence nil])
+ ("Compilation Options" :visible (and (featurep 'two-mode-mode) two-mode-bool)
+ ["Enable Automatic Detection of Option" (setq lasy-compilation-inline-auto-detection t) :style radio :selected lasy-compilation-inline-auto-detection :active t :key-sequence nil]
+ ["Disable Automatic Detection of Option" (setq lasy-compilation-inline-auto-detection nil) :style radio :selected (not lasy-compilation-inline-auto-detection) :active t :key-sequence nil])
+ ["Customize" (customize-group "asymptote") :active t :key-sequence nil]
+ ["Help" (describe-function 'asy-mode) :active t :key-sequence nil]
+ )))
+(easy-menu-define asy-mode-menu asy-mode-map "Asymptote Mode Commands" asy-menu)
+;; On the hook for XEmacs only.
+(if running-xemacs-p
+ (add-hook 'asy-mode-hook
+ (lambda ()
+ (and (eq major-mode 'asy-mode)
+ (easy-menu-add asy-mode-menu asy-mode-map)))))
+
+(defun asy-protect-file-name(Filename)
+ (concat "\"" Filename "\""))
+
+(defun asy-get-temp-file-name(&optional noext)
+ "Get a temp file name for printing."
+ (if running-xemacs-p
+ (concat (make-temp-name asy-temp-dir) (if noext "" ".asy"))
+ (concat (make-temp-file
+ (expand-file-name "asy" asy-temp-dir)) (if noext "" ".asy"))))
+
+(defun asy-log-filename()
+ (concat buffer-file-name ".log"))
+
+(defun asy-compile()
+ "Compile Asymptote code."
+ (interactive)
+ (if (and (boundp two-mode-bool) two-mode-bool)
+ (lasy-compile) ;; compile asy code in a TeX file.
+ (progn ;; compile asy code in a asy file.
+ (let*
+ ((buffer-base-name (file-name-sans-extension (file-name-nondirectory buffer-file-name)))
+ (asy-compile-command
+ (concat asy-command-location asy-command
+ (if (eq asy-compilation-buffer 'never)
+ " " " -wait ")
+ (asy-protect-file-name buffer-base-name))))
+ (if (buffer-modified-p) (save-buffer))
+ (message "%s" asy-compile-command)
+ (asy-internal-compile asy-compile-command t t)))))
+
+(defun asy-error-message(&optional P)
+ (let ((asy-last-error
+ (asy-log-field-string
+ (asy-log-filename) 0)))
+ (if (and asy-last-error (not (string= asy-last-error "")))
+ (message (concat asy-last-error (if P "\nPress F4 to go to error" "")))
+ (when (and (boundp two-mode-bool) two-mode-bool lasy-run-tex (not (zerop asy-last-compilation-code)))
+ (message "The LaTeX code may be incorrect.")))))
+
+(defun asy-log-field-string(Filename Field)
+ "Return field of first line of file filename.
+Fields are defined as 'field1: field2.field3:field4' . Field=0 <-> all fields"
+ (let ((view-inhibit-help-message t))
+ (with-temp-buffer
+ (progn
+ (insert-file Filename)
+ (beginning-of-buffer)
+ (if (re-search-forward "^\\(.*?\\): \\(.*?\\)\\.\\(.*?\\):\\(.*\\)$" (point-max) t)
+ (match-string Field) nil)))))
+
+(defun asy-next-error(arg reset)
+ (if (> emacs-major-version 21)
+ (next-error arg reset)
+ (next-error arg)))
+
+(defun lasy-ask-visit-tem-compilation-buffer()
+ "* Ask before visiting a temporary compilation buffer depending the value of `lasy-ask-about-temp-compilation-buffer'."
+ (if lasy-ask-about-temp-compilation-buffer
+ (y-or-n-p "Visit temporary buffer of compilation ? ") t))
+
+(defun lasy-place-cursor-to-error(Filename li co)
+ (save-excursion
+ (with-temp-buffer
+ (insert-file-contents
+ (if running-unix-p Filename
+ (replace-regexp-in-string
+ "//" ":/"
+ (replace-regexp-in-string "/cygdrive/" "" Filename)))) ;; Not right,
+;;;maybe take a look at the code of compilation-find-file
+ (beginning-of-buffer)
+ (next-line (1- (string-to-number li)))
+ (setq line-err
+ (buffer-substring-no-properties
+ (progn (beginning-of-line) (point))
+ (progn (end-of-line) (point))))))
+ (beginning-of-buffer)
+ (search-forward line-err)
+ (beginning-of-line)
+ (forward-char (1- (string-to-number co))))
+
+(defun asy-goto-error(&optional arg reset)
+ "Go to point of last error within asy/lasy-mode."
+ (interactive "P")
+ (if (or (eq asy-compilation-buffer 'never)
+ (and (boundp two-mode-bool) two-mode-bool))
+ (let* ((log-file (asy-log-filename))
+ (li_ (asy-log-field-string log-file 2))
+ (co_ (asy-log-field-string log-file 3)))
+ (if (and (boundp two-mode-bool) two-mode-bool) ;; Within Lasy-mode
+ (progn ;; lasy-mode need the compilation of file.tex
+ ;; the error can be in Tex commands or in Asymptote commands
+ (if (eq asy-compilation-buffer 'never) ;; Find error in the log file.
+ (if li_ ;; Asy error found in the log-file
+ (progn
+ (lasy-place-cursor-to-error
+ (asy-log-field-string log-file 1) li_ co_)
+ (asy-error-message))
+ (message "There is an error in your LaTeX code..."))
+ (if (or running-xemacs-p (< emacs-major-version 22))
+ (when (lasy-ask-visit-tem-compilation-buffer)
+ (next-error arg))
+ (let ((msg)) ;; Find error in the compilation buffer
+ (save-excursion
+ (set-buffer (next-error-find-buffer))
+ (when reset
+ (setq compilation-current-error nil))
+ (let* ((columns compilation-error-screen-columns)
+ (last 1)
+ (loc (compilation-next-error (or arg 1) nil
+ (or compilation-current-error
+ compilation-messages-start
+ (point-min))))
+ (end-loc (nth 2 loc))
+ (marker (point-marker)))
+ (setq compilation-current-error (point-marker)
+ overlay-arrow-position
+ (if (bolp)
+ compilation-current-error
+ (copy-marker (line-beginning-position)))
+ loc (car loc)))
+ (if (re-search-forward "^\\(.*?\\): \\(.*?\\)\\.\\(.*?\\):\\(.*\\)$" (point-max) t)
+ (progn
+ (setq msg (match-string 0)
+ log-file (match-string 1)
+ li_ (match-string 2)
+ co_ (match-string 3)))
+ (error "Not other errors.")))
+ (lasy-place-cursor-to-error log-file li_ co_)
+ (message msg)))))
+ (if li_ ;;Pure asy-mode and compilation with shell-command
+ (progn
+ (goto-line (string-to-number li_))
+ (forward-char (1- (string-to-number co_)))
+ (asy-error-message))
+ (progn (message "No error.")))))
+ (asy-next-error arg reset)))
+
+(defun asy-grep (Regexp)
+ "Internal function used by asymptote."
+ (let ((Strout "")
+ (case-fold-search-asy case-fold-search))
+ (progn
+ (beginning-of-buffer)
+ (setq case-fold-search nil)
+ (while (re-search-forward Regexp (point-max) t)
+ (setq Strout (concat Strout (match-string 0) "\n\n")))
+ (setq case-fold-search case-fold-search-asy)
+ (if (string= Strout "") "No match.\n" Strout))))
+
+(defun asy-widget-open-file-at-pos (widget &optional event)
+ ""
+ (kill-buffer (current-buffer))
+ (find-file (widget-get widget :follow-link))
+ (goto-line (string-to-number (widget-get widget :value))))
+
+(defun asy-show-function-at-point()
+ "Show the Asymptote definitions of the command at point."
+ (interactive)
+ (save-excursion
+ (let ((cWord (current-word))
+ (cWindow (selected-window)))
+ (switch-to-buffer-other-window "*asy-help*")
+ (fundamental-mode)
+ (setq default-directory "/")
+ (if (> emacs-major-version 21)
+ (call-process-shell-command
+ (concat asy-command-location "asy -l --where") nil t nil)
+ (insert (shell-command-to-string "asy -l --where")))
+ (let ((rHelp (asy-grep (concat "^.*\\b" cWord "(\\(.\\)*?$")))
+ (tag)(file)(line))
+ (erase-buffer)
+ (insert rHelp)
+ (beginning-of-buffer)
+ (while (re-search-forward "\\(.*\\): \\([0-9]*\\)\\.\\([0-9]*\\)" (point-max) t)
+ (setq file (match-string 1)
+ line (match-string 2)
+ tag (file-name-nondirectory file))
+ (widget-create `(file-link
+ :tag ,tag
+ :follow-link ,file
+ :value ,line
+ :action asy-widget-open-file-at-pos
+ ))))
+ (beginning-of-buffer)
+ (while (re-search-forward "\\(.*: [0-9]*\\.[0-9]*\\)" (point-max) t)
+ (replace-match ""))
+ (asy-mode)
+ (use-local-map widget-keymap)
+ (widget-setup)
+ (goto-char (point-min))
+ (select-window cWindow))))
+
+(add-hook 'asy-mode-hook
+ (lambda ()
+ (c-set-style "gnu");
+ (c-set-offset (quote topmost-intro-cont) 0 nil)
+ (make-local-variable 'c-label-minimum-indentation)
+ (setq c-label-minimum-indentation 0)
+ (when (fboundp 'flyspell-mode) (flyspell-mode -1))
+ (turn-on-font-lock)
+ (column-number-mode t)
+ ))
+
+
+;;;###autoload (defun lasy-mode ())
+;;; ************************************
+;;; asy-mode mixed with LaTeX-mode: lasy
+;;; ************************************
+(if (locate-library "two-mode-mode")
+ (progn
+
+ (defvar lasy-fontify-asy-p nil
+ "Variable to communicate with `font-lock-unfontify-region'.
+Internal use, don't set in any fashion.")
+ (setq lasy-fontify-asy-p nil)
+
+ (eval-after-load "two-mode-mode"
+ '(progn
+ ;; Redefine `two-mode-mode-update-mode' to use regexp.
+ (defun two-mode-mode-update-mode ()
+ "Redefined in `asy-mode.el' to use regexp"
+ (when (and two-mode-bool two-mode-update)
+ (setq two-mode-update 0)
+ (let ((mode-list second-modes)
+ (flag 0))
+ (while mode-list
+ (let ((mode (car mode-list))
+ (lm -1)
+ (rm -1))
+ (save-excursion
+ (if (search-backward-regexp (cadr mode) nil t)
+ (setq lm (point))
+ (setq lm -1)))
+ (save-excursion
+ (if (search-backward-regexp (car (cddr mode)) nil t)
+ (setq rm (point))
+ (setq rm -1)))
+ (if (and (not (and (= lm -1) (= rm -1))) (>= lm rm))
+ (progn
+ (setq flag 1)
+ (setq mode-list '())
+ (two-mode-change-mode (car mode) (car (cdr (cddr mode)))))))
+ (setq mode-list (cdr mode-list)))
+ (if (= flag 0)
+ (two-mode-change-mode (car default-mode) (cadr default-mode))))))
+
+ (defun two-mode-change-mode (to-mode func)
+ "Redefined in asy-mode.
+Change the variable `lasy-fontify-asy-p' according to the value of func and
+the current mode."
+ (if (string= to-mode mode-name)
+ t
+ (progn
+ (setq lasy-fontify-asy-p (eq func 'asy-mode))
+ (funcall func)
+ (hack-local-variables)
+ (two-mode-mode-setup)
+ (if two-mode-switch-hook
+ (run-hooks 'two-mode-switch-hook))
+ (if (eq font-lock-mode t)
+ (font-lock-fontify-buffer))
+ (turn-on-font-lock-if-enabled))))
+ ))
+
+
+ (require 'two-mode-mode)
+
+ (defun lasy-mode ()
+ "Treat, in some cases, the current buffer as a literal Asymptote program."
+ (interactive)
+ (save-excursion
+ (let ((prefix
+ (progn
+ (goto-char (point-max))
+ (re-search-backward "^\\([^\n]+\\)Local Variables:"
+ (- (point-max) 3000) t)
+ (match-string 1)))
+ (pos-b (point)))
+ (when
+ (and prefix
+ (progn
+ (re-search-forward (regexp-quote
+ (concat prefix
+ "End:")) (point-max) t)
+ (re-search-backward (concat "\\(" prefix "mode: .*\\)") pos-b t))
+ )
+ (error (concat "lasy-mode can not work if a mode is specified as local file variable.
+You should remove the line " (int-to-string (line-number-at-pos)))))))
+ (set (make-local-variable 'asy-insinuate-latex-p) asy-insinuate-latex-p)
+ (make-local-variable 'lasy-fontify-asy-p)
+ (when (< emacs-major-version 22)
+ (make-local-variable 'font-lock-keywords-only))
+ (setq default-mode '("LaTeX" latex-mode)
+ second-modes '(("Asymptote"
+ "^\\\\begin{asy}.*$"
+ "^\\\\end{asy}"
+ asy-mode)))
+ (if two-mode-bool
+ (progn
+ (latex-mode)
+ (asy-insinuate-latex))
+ (progn
+ (two-mode-mode)
+ )))
+
+ (when (not running-xemacs-p)
+ (defadvice TeX-command-master (around asy-choose-compile act)
+ "Hack to circumvent the preempt of 'C-c C-c' by AucTeX within `lasy-mode'."
+ (if (string-match "asymptote" (downcase mode-name))
+ (asy-compile)
+ ad-do-it)))
+
+ (add-hook 'two-mode-switch-hook
+ (lambda ()
+ (if (eq major-mode 'latex-mode)
+ (progn ;; Switch to latex-mode
+ ;; Disable LaTeX-math-Mode within lasy-mode (because of incompatibility)
+ (when LaTeX-math-mode (LaTeX-math-mode -1))
+ (asy-insinuate-latex)
+ (when (< emacs-major-version 22)
+ (setq font-lock-keywords-only nil)))
+ (progn ;; Switch to asy-mode
+ (when (< emacs-major-version 22)
+ (setq font-lock-keywords-only t))
+ ))))
+ ;; (setq two-mode-switch-hook nil)
+
+ ;; Solve a problem restoring a TeX file via desktop.el previously in lasy-mode.
+ (if (boundp 'desktop-buffer-mode-handlers)
+ (progn
+ (defun asy-restore-desktop-buffer (desktop-b-f-name d-b-n d-b-m)
+ (find-file desktop-b-f-name))
+ (add-to-list 'desktop-buffer-mode-handlers
+ '(asy-mode . asy-restore-desktop-buffer))))
+
+ ;; Functions and 'advises' to restrict 'font-lock-unfontify-region'
+ ;; and 'font-lock-fontify-syntactically-region' within lasy-mode
+ ;; Special thanks to Olivier Ramaré for his help.
+ (when (and (fboundp 'font-lock-add-keywords) (> emacs-major-version 21))
+ (defun lasy-mode-at-pos (pos &optional interior strictly)
+ "If point at POS is in an asy environment return the list (start end)."
+ (save-excursion
+ (save-match-data
+ (goto-char pos)
+ (let* ((basy
+ (progn
+ (unless strictly (end-of-line))
+ (when (re-search-backward "^\\\\begin{asy}" (point-min) t)
+ (when interior (next-line))
+ (point))))
+ (easy
+ (and basy
+ (progn
+ (when (re-search-forward "^\\\\end{asy}" (point-max) t)
+ (when interior (previous-line)(beginning-of-line))
+ (point))))))
+ (and basy easy
+ (> pos (- basy (if interior 12 0)))
+ (< pos (+ easy (if interior 10 0)))
+ (list basy easy))))))
+
+ (defun lasy-region (start end &optional interior)
+ "If the region 'start to end' contains the beginning or
+the end of an asy environment return the list of points where
+the asy environment starts and ends."
+ (let* ((beg (min start end))
+ (lim (max start end)))
+ (or (lasy-mode-at-pos beg interior)
+ (save-match-data
+ (save-excursion
+ (goto-char beg)
+ (and (re-search-forward "^\\\\begin{asy}" lim t)
+ (lasy-mode-at-pos (point) interior)))))))
+
+ (defun lasy-tags (start end)
+ "Return associated list of points where the tags starts and ends
+restricted to the region (start end).
+\"b\" associated with (start-beginTag end-beginTag),
+\"e\" associated with (start-endTag end-endTag)."
+ (let*
+ ((beg (min start end))
+ (lim (max start end))
+ out)
+ (save-excursion
+ (goto-char beg)(beginning-of-line)
+ (while
+ (when (re-search-forward "^\\\\begin{asy}.*" lim t)
+ (push (list
+ (progn (beginning-of-line)(point))
+ (progn (end-of-line)(point))) out)))
+ (goto-char beg)(beginning-of-line)
+ (while
+ (when (re-search-forward "^\\\\end{asy}" lim t)
+ (push (list
+ (progn (beginning-of-line)(point))
+ (progn (end-of-line)(point))) out)))
+ out)))
+
+ (defun lasy-restrict-region (start end &optional interior)
+ "If the region 'start to end' contains the beginning or
+the end of an asy environment, returns the list of points wich
+restricts the region to the asy environment.
+Else, return (start end)."
+ (let*
+ ((beg (min start end))
+ (lim (max start end))
+ (be (if (lasy-mode-at-pos beg)
+ beg
+ (or (save-excursion
+ (goto-char beg)
+ (when (re-search-forward "^\\\\begin{asy}.*" lim t)
+ (unless interior (beginning-of-line))
+ (point)))
+ beg)))
+ (en (or (save-excursion
+ (goto-char be)
+ (when (re-search-forward "^\\\\end{asy}" lim t)
+ (when interior (beginning-of-line))
+ (point)))
+ lim)))
+ (list be en)))
+
+ (defun lasy-parse-region (start end)
+ "Return a list ((a (start1 end1)) (b (start2 end2)) [...]).
+where a, b, ... are nil or t; t means the region from 'startX' through 'endX' (are points)
+is in a asy environnement."
+ (let (regasy out rr brr err tags)
+ (save-excursion
+ (goto-char start)
+ (while (< (point) end)
+ (setq regasy (lasy-region (point) end))
+ (if regasy
+ (progn
+ (setq rr (lasy-mode-at-pos (point)))
+ (setq brr (and rr (nth 0 rr))
+ err (and rr (nth 1 rr)))
+ (if rr
+ (progn
+ (push (list t (list (max 1 (1- (point))) (min end err))) out)
+ (goto-char (min end err)))
+ (progn
+ (push (list nil (list (point) (nth 0 regasy))) out)
+ (goto-char (1+ (nth 0 regasy))))))
+ (progn
+ (push (list nil (list (min (1+ (point)) end) end)) out)
+ (goto-char end)))
+ ))
+ ;; Put start and end of tag in latex fontification.
+ (setq tags (lasy-tags start end))
+ (dolist (tag tags) (push (list nil tag) out))
+ (reverse out)))
+
+ (defadvice font-lock-unfontify-region
+ (around asy-font-lock-unfontify-region (beg end))
+ (if two-mode-bool
+ (let ((rstate (lasy-parse-region beg end))
+ curr reg asy-fontify latex-fontify)
+ (while (setq curr (pop rstate))
+ (setq reg (nth 1 curr))
+ (setq asy-fontify (and (nth 0 curr) lasy-fontify-asy-p)
+ latex-fontify (and (not (nth 0 curr))
+ (not lasy-fontify-asy-p)))
+ (when (or asy-fontify latex-fontify)
+ (setq beg (nth 0 reg)
+ end (nth 1 reg))
+ (save-excursion
+ (save-restriction
+ (narrow-to-region beg end)
+ ad-do-it
+ (widen))))))
+ ad-do-it))
+
+ (ad-activate 'font-lock-unfontify-region)
+ ;; (ad-deactivate 'font-lock-unfontify-region)
+
+ (defadvice font-lock-fontify-syntactically-region
+ (around asy-font-lock-fontify-syntactically-region
+ (start end &optional loudly))
+ (if (and two-mode-bool (eq major-mode 'asy-mode))
+ (let*((reg (lasy-restrict-region start end)))
+ (save-restriction
+ (setq start (nth 0 reg) end (nth 1 reg))
+ (narrow-to-region start end)
+ (condition-case nil
+ ad-do-it
+ (error nil))
+ (widen)
+ ))
+ ad-do-it))
+
+ (ad-activate 'font-lock-fontify-syntactically-region)
+ ;; (ad-deactivate 'font-lock-fontify-syntactically-region)
+
+ (defadvice font-lock-default-fontify-region
+ (around asy-font-lock-default-fontify-region
+ (beg end loudly))
+ (if two-mode-bool
+ (let ((rstate (lasy-parse-region beg end))
+ asy-fontify latex-fontify curr reg)
+ (while (setq curr (pop rstate))
+ (setq reg (nth 1 curr))
+ (setq asy-fontify (and (nth 0 curr) lasy-fontify-asy-p)
+ latex-fontify (and (not (nth 0 curr))
+ (not lasy-fontify-asy-p)))
+ (when (or asy-fontify latex-fontify)
+ (setq beg (nth 0 reg)
+ end (nth 1 reg))
+ (save-excursion
+ (save-restriction
+ (narrow-to-region beg end)
+ (condition-case nil
+ ad-do-it
+ (error nil))
+ (widen)
+ )))))
+ ad-do-it))
+
+ (ad-activate 'font-lock-default-fontify-region)
+ ;; (ad-deactivate 'font-lock-default-fontify-region)
+
+ ))
+ (progn
+ (defvar two-mode-bool nil)
+ (defun lasy-mode ()
+ (message "You must install the package two-mode-mode.el."))))
+
+(setq asy-latex-menu-item
+ '(["Toggle lasy-mode" lasy-mode :active (featurep 'two-mode-mode)]
+ ["View asy picture near cursor" lasy-compile :active t]"--"
+ ("Compile OR View"
+ ["PS" lasy-view-ps :active t]
+ ["PDF (pdflatex)" lasy-view-pdf-via-pdflatex :active t]
+ ["PDF (ps2pdf)" lasy-view-pdf-via-ps2pdf :active t])
+ ("Compile AND View"
+ ["PS" asy-master-tex-view-ps-f :active t]
+ ["PDF (pdflatex)" asy-master-tex-view-pdflatex-f :active t]
+ ["PDF (ps2pdf)" asy-master-tex-view-ps2pdf-f :active t])"--"
+ ["Asymptote insinuates globally LaTeX" asy-insinuate-latex-globally :active (not asy-insinuate-latex-globally-p)]
+ ("Disable Asymptote insinuate Latex"
+ ["locally" asy-no-insinuate-locally :active t]
+ ["globally" asy-no-insinuate-globally :active t])
+ ("Debugger Buffer"
+ ["Visible" (setq asy-compilation-buffer 'visible) :style radio :selected (eq asy-compilation-buffer 'visible) :active t]
+ ["Available" (setq asy-compilation-buffer 'available) :style radio :selected (eq asy-compilation-buffer 'available) :active t]
+ ["None" (setq asy-compilation-buffer 'none) :style radio :selected (eq asy-compilation-buffer 'none) :active t]
+ ["Never" (setq asy-compilation-buffer 'never) :style radio :selected (eq asy-compilation-buffer 'never) :active t])
+ ))
+(if running-xemacs-p
+ (setq asy-latex-menu-item (nconc '("Asymptote") asy-latex-menu-item))
+ (setq asy-latex-menu-item (nconc '("Asymptote" :visible asy-insinuate-latex-p) asy-latex-menu-item)))
+
+(defun asy-insinuate-latex-maybe ()
+ "This function is added to `LaTeX-mode-hook' to define the environment 'asy'
+and, eventually, set its indentation.
+For internal use only."
+ (when (or asy-insinuate-latex-globally-p
+ (save-excursion
+ (beginning-of-buffer)
+ (save-match-data
+ (search-forward "\\begin{asy}" nil t))))
+ (asy-insinuate-latex))
+ (LaTeX-add-environments
+ '("asy" (lambda (env &rest ignore)
+ (unless asy-insinuate-latex-p (asy-insinuate-latex))
+ (LaTeX-insert-environment env)))))
+
+;; (add-hook 'after-init-hook
+;; (lambda ()
+(eval-after-load "latex"
+ '(progn
+ (add-hook 'LaTeX-mode-hook 'asy-insinuate-latex-maybe)
+ (setq lasy-mode-map (copy-keymap LaTeX-mode-map))
+ (setq LaTeX-mode-map-backup (copy-keymap LaTeX-mode-map))
+
+ (defadvice TeX-add-local-master (after asy-adjust-local-variable ())
+ "Delete the line that defines the mode in a file .tex because two-mode-mode reread
+the local variables after switching mode."
+ (when (string= (file-name-extension buffer-file-name) "tex")
+ (save-excursion
+ (goto-char (point-max))
+ (delete-matching-lines
+ "mode: latex"
+ (re-search-backward "^\\([^\n]+\\)Local Variables:"
+ (- (point-max) 3000) t)
+ (re-search-forward (regexp-quote
+ (concat (match-string 1)
+ "End:"))) nil))))
+ (ad-activate 'TeX-add-local-master)
+ ;; (ad-deactivate 'TeX-add-local-master)
+
+ (when lasy-extra-key
+ (define-key lasy-mode-map (kbd "<C-return>")
+ (lambda ()
+ (interactive)
+ (lasy-view-ps nil nil t)))
+ (define-key lasy-mode-map (kbd "<C-S-return>")
+ (lambda ()
+ (interactive)
+ (lasy-view-ps t nil t)))
+ (define-key lasy-mode-map (kbd "<M-return>")
+ (lambda ()
+ (interactive)
+ (lasy-view-pdf-via-pdflatex nil nil t)))
+ (define-key lasy-mode-map (kbd "<M-S-return>")
+ (lambda ()
+ (interactive)
+ (lasy-view-pdf-via-pdflatex t nil t)))
+ (define-key lasy-mode-map (kbd "<C-M-return>")
+ (lambda ()
+ (interactive)
+ (lasy-view-pdf-via-ps2pdf nil nil t)))
+ (define-key lasy-mode-map (kbd "<C-M-S-return>")
+ (lambda ()
+ (interactive)
+ (lasy-view-pdf-via-ps2pdf t nil t)))
+ (define-key lasy-mode-map (kbd "<f4>") 'asy-goto-error))
+
+ (easy-menu-define asy-latex-mode-menu lasy-mode-map "Asymptote insinuates LaTeX" asy-latex-menu-item)
+ ))
+;; ))
+
+(defvar asy-insinuate-latex-p nil
+ "Not nil when current buffer is insinuated by Asymptote.
+May be a local variable.
+For internal use.")
+
+(defvar asy-insinuate-latex-globally-p nil
+ "Not nil when all latex-mode buffers is insinuated by Asymptote.
+For internal use.")
+
+(defun asy-set-latex-asy-indentation ()
+ "Set the indentation of environnment 'asy' like the environnment 'verbatim' is."
+ ;; Regexp matching environments with indentation at col 0 for begin/end.
+ (set (make-local-variable 'LaTeX-verbatim-regexp)
+ (concat (default-value 'LaTeX-verbatim-regexp) "\\|asy"))
+ ;; Alist of environments with special indentation.
+ (make-local-variable 'LaTeX-indent-environment-list)
+ (add-to-list 'LaTeX-indent-environment-list
+ '("asy" current-indentation)))
+
+(defun asy-unset-latex-asy-indentation ()
+ "Unset the indentation of environnment 'asy' like the environnment 'verbatim' is."
+ (set (make-local-variable 'LaTeX-verbatim-regexp)
+ (default-value 'LaTeX-verbatim-regexp))
+ (set (make-local-variable 'LaTeX-indent-environment-list)
+ (default-value 'LaTeX-indent-environment-list)))
+
+(defun asy-no-insinuate-locally ()
+ (interactive)
+ (set (make-local-variable 'asy-insinuate-latex-p) nil)
+ (setq asy-insinuate-latex-globally-p nil)
+ (asy-unset-latex-asy-indentation)
+ (if running-xemacs-p
+ (easy-menu-remove-item nil nil "Asymptote")
+ (menu-bar-update-buffers))
+ (if (and (boundp 'two-mode-bool) two-mode-bool)
+ (lasy-mode))
+ (use-local-map LaTeX-mode-map-backup))
+
+
+(defun asy-no-insinuate-globally ()
+ (interactive)
+ (if running-xemacs-p
+ (easy-menu-remove-item nil nil "Asymptote")
+ (easy-menu-remove-item LaTeX-mode-map nil "Asymptote"))
+ (kill-local-variable asy-insinuate-latex-p)
+ (setq-default asy-insinuate-latex-p nil)
+ (setq asy-insinuate-latex-globally-p nil)
+ (if (not running-xemacs-p)
+ (menu-bar-update-buffers))
+ (setq LaTeX-mode-map (copy-keymap LaTeX-mode-map-backup))
+ ;;Disable lasy-mode in all latex-mode buffers.
+ (when (featurep 'two-mode-mode)
+ (mapc (lambda (buffer)
+ (with-current-buffer buffer
+ (when (and (buffer-file-name) (string= (file-name-extension (buffer-file-name)) "tex"))
+ (asy-unset-latex-asy-indentation)
+ (latex-mode)
+ (setq asy-insinuate-latex-p nil))))
+ (buffer-list))))
+
+;;;###autoload
+(defun asy-insinuate-latex (&optional global)
+ "Add a menu bar in current 'latex-mode' buffer and activate asy keys bindings.
+If the optional parameter (only for internal use) 'global' is 't' then all the FUTURE 'latex-mode' buffers are insinuated.
+To insinuate all (current and future) 'latex-mode' buffers, use 'asy-insinuate-latex-globally' instead.
+You can automate this feature for all the 'latex-mode' buffers by inserting the five following lines in your .emacs initialization file:
+ (eval-after-load \"latex\"
+ '(progn
+ ;; Add here your personal features for 'latex-mode':
+ (asy-insinuate-latex t) ;; Asymptote insinuates globally Latex.
+ ))"
+ (interactive)
+ (if (and (not asy-insinuate-latex-globally-p) (or global (string= major-mode "latex-mode")))
+ (progn
+ (asy-set-latex-asy-indentation)
+ (if global
+ (progn
+ (setq asy-insinuate-latex-p t)
+ (setq asy-insinuate-latex-globally-p t)
+ (setq LaTeX-mode-map (copy-keymap lasy-mode-map))
+ (if running-xemacs-p
+ (add-hook 'LaTeX-mode-hook
+ (lambda ()
+ (if asy-insinuate-latex-globally-p
+ (easy-menu-add asy-latex-mode-menu lasy-mode-map))))))
+ (progn
+ (use-local-map lasy-mode-map)
+ (easy-menu-add asy-latex-mode-menu lasy-mode-map)
+ (set (make-local-variable 'asy-insinuate-latex-p) t)))
+ )))
+
+(defun asy-insinuate-latex-globally ()
+ "Insinuates all (current and future) 'latex-mode' buffers.
+See `asy-insinuate-latex'."
+ (interactive)
+ (asy-insinuate-latex t)
+ (if running-xemacs-p
+ (add-hook 'LaTeX-mode-hook
+ (lambda ()
+ (if asy-insinuate-latex-globally-p
+ (easy-menu-add asy-latex-mode-menu lasy-mode-map)))))
+ (mapc (lambda (buffer)
+ (with-current-buffer buffer
+ (when (and
+ (buffer-file-name)
+ (string= (file-name-extension (buffer-file-name)) "tex"))
+ (setq asy-insinuate-latex-p t)
+ (use-local-map LaTeX-mode-map)
+ (use-local-map lasy-mode-map)
+ (asy-set-latex-asy-indentation)
+ (easy-menu-add asy-latex-mode-menu lasy-mode-map))))
+ (buffer-list)))
+
+(defun lasy-inline-p()
+ "Return nil if the option 'inline' is not used or if `lasy-compilation-inline-auto-detection' value is nil."
+ (if lasy-compilation-inline-auto-detection
+ (save-excursion
+ (re-search-backward "^[^%]* *\\\\usepackage\\[ *inline *\\]{ *asymptote *}" 0 t))
+ nil))
+
+(defvar lasy-run-tex nil)
+(defun lasy-asydef()
+ "Return the content between the tags \\begin{asydef} and \\end{asydef}."
+ (save-excursion
+ (if (re-search-backward "\\\\begin{asydef}" 0 t)
+ (buffer-substring
+ (progn (next-line)(beginning-of-line)(point))
+ (progn (re-search-forward "\\\\end{asydef}")
+ (previous-line)(end-of-line)
+ (point)))
+ "")))
+
+(defun lasy-compile-tex()
+ "Compile region between \\begin{asy}[text with backslash] and \\end{asy} through a reconstructed file .tex."
+ (interactive)
+ (setq lasy-run-tex t)
+ (save-excursion
+ (let* ((Filename (asy-get-temp-file-name t))
+ (FilenameTex (concat Filename ".tex"))
+ (asydef (lasy-asydef)))
+ (save-excursion
+ (beginning-of-buffer)
+ (write-region (point)
+ (progn
+ (re-search-forward "\\\\begin{document}.*\n")
+ (point)) FilenameTex)
+ (write-region (concat "\\begin{asydef}\n" asydef "\n\\end{asydef}\n") 0 FilenameTex t))
+ (re-search-backward "\\\\begin{asy}")
+ (write-region (point) (progn
+ (re-search-forward "\\\\end{asy}")
+ (point)) FilenameTex t)
+ (with-temp-file FilenameTex
+ (insert-file FilenameTex)
+ (end-of-buffer)
+ (insert "\n\\end{document}"))
+ (let ((default-directory asy-temp-dir))
+ (lasy-view-ps t Filename)))))
+
+(defun lasy-compile()
+ "Compile region between \\begin{asy} and \\end{asy}."
+ (interactive)
+ (if (or (lasy-inline-p) (progn ;; find \begin{asy}[any backslash]
+ (save-excursion
+ (re-search-forward "\\\\end{asy}" (point-max) t)
+ (re-search-backward "\\\\begin{asy}.*\\(\\[.*\\\\.*\\]\\)" 0 t))
+ (match-string 1)))
+ (progn
+ (lasy-compile-tex)) ;; a temporary TeX file must be reconstructed.
+ (progn
+ (setq lasy-run-tex nil)
+ (save-excursion
+ (let ((Filename (asy-get-temp-file-name))
+ (asydef (lasy-asydef)))
+ (write-region (match-string 0) 0 Filename)
+ (re-search-backward "\\\\begin{asy}")
+ (write-region (point) (progn
+ (re-search-forward "\\\\end{asy}")
+ (point)) Filename)
+ (with-temp-file Filename
+ (insert-file-contents Filename)
+ (beginning-of-buffer)
+ (if (re-search-forward "\\\\begin{asy}\\[\\(.*\\)\\]" (point-max) t)
+ (let ((sz (match-string 1)))
+ (replace-match "")
+ (insert (concat asydef "\nsize(" sz ");")))
+ (when (re-search-forward "\\\\begin{asy}" (point-max) t)
+ (replace-match "")
+ (insert asydef)))
+ (while (re-search-forward "\\\\end{asy}" (point-max) t)
+ (replace-match "")))
+ (let* ((asy-compile-command
+ (concat asy-command-location
+ asy-command
+ (if (eq asy-compilation-buffer 'never)
+ " " " -wait ")
+ (asy-protect-file-name Filename))))
+ (asy-internal-compile
+ asy-compile-command t
+ (not (eq asy-compilation-buffer 'never)))))))))
+
+(defun asy-set-master-tex ()
+ "Set the local variable 'asy-TeX-master-file.
+This variable is used by 'asy-master-tex-view-ps"
+ (interactive)
+ (set (make-local-variable 'asy-TeX-master-file)
+ (file-name-sans-extension
+ (file-relative-name
+ (expand-file-name
+ (read-file-name "TeX document: ")))))
+ (if (string= (concat default-directory asy-TeX-master-file)
+ (file-name-sans-extension buffer-file-name))
+ (prog1
+ (set (make-local-variable 'asy-TeX-master-file) nil)
+ (error "You should never give the same name to the TeX file and the Asymptote file"))
+ (save-excursion
+ (end-of-buffer)
+ (if (re-search-backward "asy-TeX-master-file\\(.\\)*$" 0 t)
+ (replace-match (concat "asy-TeX-master-file: \"" asy-TeX-master-file "\""))
+ (insert (concat "
+/// Local Variables:
+/// asy-TeX-master-file: \"" asy-TeX-master-file "\"
+/// End:")) t))))
+
+(defun asy-unset-master-tex ()
+ "Set the local variable 'asy-TeX-master-file to 'nil.
+This variable is used by 'asy-master-tex-view-ps"
+ (interactive)
+ (set (make-local-variable 'asy-TeX-master-file) nil)
+ (save-excursion
+ (end-of-buffer)
+ (if (re-search-backward "^.*asy-TeX-master-file:.*\n" 0 t)
+ (replace-match ""))))
+
+(defun asy-master-tex-error ()
+ "Asy-mode internal use..."
+ (if (y-or-n-p "You try to compile the TeX document that contains this picture.
+You must set the local variable asy-TeX-master-file.
+Do you want set this variable now ?")
+ (asy-set-master-tex) nil))
+
+(defun asy-master-tex-view (Func-view &optional Force fromtex)
+ "Compile the LaTeX document that contains the picture of the current Asymptote code with the function Func-view.
+Func-view can be one of 'lasy-view-ps, 'lasy-view-pdf-via-pdflatex, 'lasy-view-pdf-via-ps2pdf."
+ (interactive)
+ (if (or
+ (and (boundp two-mode-bool) two-mode-bool)
+ (string-match "latex" (downcase mode-name)))
+ (progn ;; Current mode is lasy-mode or latex-mode not asy-mode
+ (funcall Func-view Force nil fromtex))
+ (if asy-TeX-master-file
+ (if (string= asy-TeX-master-file
+ (file-name-sans-extension buffer-file-name))
+ (error "You should never give the same name to the TeX file and the Asymptote file")
+ (funcall Func-view Force asy-TeX-master-file fromtex))
+ (if (asy-master-tex-error)
+ (funcall Func-view Force asy-TeX-master-file fromtex)))))
+
+(defvar asy-last-compilation-code nil
+ "Code returned by the last compilation with `compile'.")
+
+(defvar asy-compilation-auto-close nil
+ "Variable to communicate with `asy-compilation-finish-function'.
+Do not set this variable in any fashion.")
+
+(defun asy-compilation-finish-function (buf msg)
+ "Function to automatically close the compilation buffer '*asy-compilation*'
+when no error or warning occurs."
+ (when (string-match "*asy-compilation*" (buffer-name buf))
+ (when (and asy-compilation-auto-close
+ (eq asy-compilation-buffer 'none))
+ (setq asy-compilation-auto-close nil)
+ (if (not (string-match "exited abnormally" msg))
+ (progn
+ (save-excursion
+ (set-buffer buf)
+ (beginning-of-buffer)
+ (if (not (search-forward-regexp "[wW]arning" nil t))
+ (when (not (eq asy-compilation-buffer 'visible))
+ ;;no errors/Warning, make the compilation window go away
+ (run-at-time 0.5 nil (lambda (buf_)
+ (delete-windows-on buf_)
+ (kill-buffer buf_)) buf)
+ (message (replace-regexp-in-string "\n" "" msg)))
+ (message "Compilation warnings..."))))))))
+
+(if running-xemacs-p
+ (setq compilation-finish-function 'asy-compilation-finish-function)
+ (add-to-list 'compilation-finish-functions
+ 'asy-compilation-finish-function))
+
+(defun asy-compilation-wait(&optional pass auto-close)
+ "Wait for process in *asy-compilation* exits.
+If pass is 't' don't wait.
+If auto-close is 't' close the window if the process exit with success."
+ (setq asy-compilation-auto-close auto-close)
+ (let* ((buff (get-buffer "*asy-compilation*"))
+ (comp-proc (get-buffer-process buff)))
+ (while (and comp-proc
+ (not (eq (process-status comp-proc) 'exit))
+ (not pass))
+ (setq comp-proc (get-buffer-process buff))
+ (sit-for 1)
+ (message "Waiting process...") ;; need message in Windows system
+ )
+ (message "") ;; Erase previous message.
+ (if (and (not pass) comp-proc)
+ (setq asy-last-compilation-code (process-exit-status comp-proc))
+ (setq asy-last-compilation-code 0))
+ (when (and (eq asy-compilation-buffer 'available)
+ (zerop asy-last-compilation-code))
+ (delete-windows-on buff))))
+
+
+(defun asy-internal-shell (command &optional pass)
+ "Execute 'command' in a inferior shell discarding output and
+redirecting stderr in the file given by the command `asy-log-filename'.
+`asy-internal-shell' waits for PROGRAM to terminate and returns a numeric exit status.
+The variable `asy-last-compilation-code' is always set to the exit status.
+The optional argument pass, for compatibility, is not used."
+ (let* ((log-file (asy-log-filename))
+ (discard (if pass 0 nil))
+ (status
+ (progn
+ (let ((view-inhibit-help-message t))(write-region "" 0 log-file nil))
+ (message "%s" command)
+ (call-process shell-file-name nil (list nil log-file) nil shell-command-switch command))))
+ (setq asy-last-compilation-code (if status status 0))
+ (if status status nil)))
+
+;; (defun asy-internal-shell (command &optional pass)
+;; "Execute 'command' in a inferior shell discarding output and
+;; redirecting stderr in the file given by the command `asy-log-filename'.
+;; pass non-nil means `asy-internal-shell' returns immediately with nil value.
+;; Otherwise it waits for PROGRAM to terminate and returns a numeric exit status.
+;; The variable `asy-last-compilation-code' is always set to the exit status or 0 if the
+;; process returns immediately."
+;; (let* ((log-file (asy-log-filename))
+;; (discard (if pass 0 nil))
+;; (status
+;; (progn
+;; (let ((inhibit-redisplay t))(write-region "" 0 log-file nil))
+;; (message "%s" command)
+;; (call-process shell-file-name nil (list discard log-file) nil shell-command-switch command))))
+;; (setq asy-last-compilation-code (if status status 0))
+;; (when pass (sit-for 1))
+;; (if status status nil)))
+
+(defun asy-internal-compile (command &optional pass auto-close stderr)
+ "Execute command.
+pass non-nil means don't wait the end of the process.
+auto-close non-nil means automatically close the compilation buffer.
+stderr non-nil means redirect the standard output error to the file
+returned by `asy-log-filename'.
+In this case command is running in an inferior shell without any output and
+the parameter auto-close is not used (see `asy-internal-shell')."
+ (setq asy-last-compilation-code -1)
+ (let* ((compilation-buffer-name "*asy-compilation*")
+ (compilation-buffer-name-function (lambda (mj) compilation-buffer-name)))
+ (if (or stderr (eq asy-compilation-buffer 'never))
+ (progn
+ (asy-internal-shell command pass)
+ (asy-error-message t))
+ (progn
+ (let ((comp-proc (get-buffer-process compilation-buffer-name)))
+ (if comp-proc
+ (condition-case ()
+ (progn
+ (interrupt-process comp-proc)
+ (sit-for 1)
+ (delete-process comp-proc)
+ (when (and asy-compilation-auto-close
+ (eq asy-compilation-buffer 'none)
+ (not (eq asy-compilation-buffer 'visible)))
+ (sit-for 0.6)))
+ (error ""))
+ ))
+ (let ((view-inhibit-help-message t))
+ (write-region "" 0 (asy-log-filename) nil))
+ (compile command))
+ (asy-compilation-wait pass auto-close))))
+
+(defun asy-open-file(Filename)
+ "Open the ps or pdf file Filename.
+In unix-like system the variables `ps-view-command' and `pdf-view-command' are used.
+In Windows the associated system file type is used instead."
+ (let ((command
+ (if running-unix-p
+ (let ((ext (file-name-extension Filename)))
+ (cond
+ ((string= ext "ps") ps-view-command)
+ ((string= ext "pdf") pdf-view-command)
+ (t (error "Extension Not Supported."))))
+ (asy-protect-file-name (file-name-nondirectory Filename))))
+ )
+ (if running-unix-p
+ (start-process "" nil command Filename)
+ (call-process-shell-command command nil 0))))
+
+(defun lasy-TeX-master-file ()
+ "Return the file name of the master file for the current document.
+The returned string contain the directory but does not contain the extension of the file."
+ (expand-file-name
+ (concat (TeX-master-directory) (TeX-master-file nil t))))
+
+(defun lasy-must-compile-p (TeX-Master-File out-file &optional Force)
+ ""
+ (or Force
+ (file-newer-than-file-p
+ (concat TeX-Master-File ".tex") out-file)
+ (and (stringp (TeX-master-file)) ;; current buffer is not a mater tex file
+ (file-newer-than-file-p buffer-file-name out-file))))
+
+(defun lasy-view-ps (&optional Force Filename fromtex)
+ "Compile a LaTeX document embedding Asymptote code with latex->asy->latex->dvips and/or view the PostScript output.
+If optional argument Force is t then force compilation."
+ (interactive)
+ (setq lasy-run-tex t)
+ (setq lasy-compile-tex fromtex)
+ (if (buffer-modified-p) (save-buffer))
+ (when (eq asy-compilation-buffer 'never) (write-region "" 0 (asy-log-filename) nil))
+ (let*
+ ((b-b-n (if Filename Filename (lasy-TeX-master-file)))
+ (b-b-n-tex (asy-protect-file-name (concat b-b-n ".tex")))
+ (b-b-n-ps (asy-protect-file-name (concat b-b-n ".ps")))
+ (b-b-n-dvi (asy-protect-file-name (concat b-b-n ".dvi")))
+ (b-b-n-asy (asy-protect-file-name (concat b-b-n ".asy")))
+ (stderr (eq asy-compilation-buffer 'never)))
+ (if (lasy-must-compile-p b-b-n (concat b-b-n ".ps") Force)
+ (progn
+ (let ((default-directory (file-name-directory b-b-n)))
+ (asy-internal-compile (concat lasy-latex-command " " b-b-n-tex))
+ (when (and (zerop asy-last-compilation-code) (file-readable-p (concat b-b-n ".asy")))
+ (asy-internal-compile (concat asy-command-location lasy-command " " b-b-n-asy) nil nil stderr)
+ (when (zerop asy-last-compilation-code)
+ (asy-internal-compile (concat lasy-latex-command " " b-b-n-tex))))
+ (when (zerop asy-last-compilation-code)
+ (asy-internal-compile (concat lasy-dvips-command " " b-b-n-dvi " -o " b-b-n-ps) nil t)
+ (when (zerop asy-last-compilation-code)
+ (asy-open-file (concat b-b-n ".ps"))))))
+ (asy-open-file (concat b-b-n ".ps")))))
+
+(defun lasy-view-pdf-via-pdflatex (&optional Force Filename fromtex)
+ "Compile a LaTeX document embedding Asymptote code with pdflatex->asy->pdflatex and/or view the PDF output.
+If optional argument Force is t then force compilation."
+ (interactive)
+ (setq lasy-run-tex t)
+ (setq lasy-compile-tex fromtex)
+ (if (buffer-modified-p) (save-buffer))
+ (when (eq asy-compilation-buffer 'never) (write-region "" 0 (asy-log-filename) nil))
+ (let*
+ ((b-b-n (if Filename Filename (lasy-TeX-master-file)))
+ (b-b-n-tex (asy-protect-file-name (concat b-b-n ".tex")))
+ (b-b-n-pdf (asy-protect-file-name (concat b-b-n ".pdf")))
+ (b-b-n-asy (asy-protect-file-name (concat b-b-n ".asy")))
+ ;; (stderr (or (eq asy-compilation-buffer 'never) lasy-compile-tex)))
+ (stderr (eq asy-compilation-buffer 'never)))
+ (if (lasy-must-compile-p b-b-n (concat b-b-n ".pdf") Force)
+ (progn
+ (let ((default-directory (file-name-directory b-b-n)))
+ (asy-internal-compile (concat lasy-pdflatex-command " " b-b-n-tex))
+ (when (and (zerop asy-last-compilation-code) (file-readable-p (concat b-b-n ".asy")))
+ (asy-internal-compile (concat asy-command-location lasy-command " " b-b-n-asy) nil nil stderr)
+ (when (zerop asy-last-compilation-code)
+ (asy-internal-compile (concat lasy-pdflatex-command " " b-b-n-tex) t)))
+ (when (zerop asy-last-compilation-code)
+ (asy-open-file (concat b-b-n ".pdf")))))
+ (asy-open-file (concat b-b-n ".pdf")))))
+
+(defun lasy-view-pdf-via-ps2pdf (&optional Force Filename fromtex)
+ "Compile a LaTeX document embedding Asymptote code with latex->asy->latex->dvips->ps2pdf14 and/or view the PDF output.
+If optional argument Force is t then force compilation."
+ (interactive)
+ (setq lasy-run-tex t)
+ (setq lasy-compile-tex fromtex)
+ (if (buffer-modified-p) (save-buffer))
+ (when (eq asy-compilation-buffer 'never) (write-region "" 0 (asy-log-filename) nil))
+ (let*
+ ((b-b-n (if Filename Filename (lasy-TeX-master-file)))
+ (b-b-n-tex (asy-protect-file-name (concat b-b-n ".tex")))
+ (b-b-n-ps (asy-protect-file-name (concat b-b-n ".ps")))
+ (b-b-n-dvi (asy-protect-file-name (concat b-b-n ".dvi")))
+ (b-b-n-pdf (asy-protect-file-name (concat b-b-n ".pdf")))
+ (b-b-n-asy (asy-protect-file-name (concat b-b-n ".asy")))
+ ;; (stderr (or (eq asy-compilation-buffer 'never) lasy-compile-tex)))
+ (stderr (eq asy-compilation-buffer 'never)))
+ (if (lasy-must-compile-p b-b-n (concat b-b-n ".pdf") Force)
+ (progn
+ (let ((default-directory (file-name-directory b-b-n)))
+ (asy-internal-compile (concat lasy-latex-command " " b-b-n-tex))
+ (when (and (zerop asy-last-compilation-code) (file-readable-p (concat b-b-n ".asy")))
+ (asy-internal-compile (concat asy-command-location lasy-command " " b-b-n-asy) nil nil stderr)
+ (when (zerop asy-last-compilation-code)
+ (asy-internal-compile (concat lasy-latex-command " " b-b-n-tex))))
+ (when (zerop asy-last-compilation-code)
+ (asy-internal-compile (concat lasy-dvips-pre-pdf-command " " b-b-n-dvi " -o " b-b-n-ps))
+ (when (zerop asy-last-compilation-code)
+ (asy-internal-compile (concat lasy-ps2pdf-command " " b-b-n-ps " " b-b-n-pdf) t)
+ (when (zerop asy-last-compilation-code)
+ (asy-open-file (concat b-b-n ".pdf")))))))
+ (asy-open-file (concat b-b-n ".pdf")))))
+
+;; Goto error of last compilation
+(define-key asy-mode-map (kbd "<f4>") 'asy-goto-error)
+
+;; Save and compile the file with option -V
+(define-key asy-mode-map (kbd "C-c C-c") 'asy-compile)
+
+;; Show the definitions of command at point
+(define-key asy-mode-map (kbd "C-c ?") 'asy-show-function-at-point)
+
+;; new line and indent
+(define-key asy-mode-map (kbd "RET") 'newline-and-indent)
+
+(defun asy-master-tex-view-ps ()
+ "Look at `asy-master-tex-view'"
+ (interactive)
+ (asy-master-tex-view 'lasy-view-ps nil t))
+(define-key asy-mode-map (kbd "<C-return>") 'asy-master-tex-view-ps)
+
+(defun asy-master-tex-view-ps-f ()
+ "Look at `asy-master-tex-view'"
+ (interactive)
+ (asy-master-tex-view 'lasy-view-ps t t))
+(define-key asy-mode-map (kbd "<C-S-return>") 'asy-master-tex-view-ps-f)
+
+(defun asy-master-tex-view-pdflatex ()
+ "Look at `asy-master-tex-view'"
+ (interactive)
+ (asy-master-tex-view 'lasy-view-pdf-via-pdflatex nil t))
+(define-key asy-mode-map (kbd "<M-return>") 'asy-master-tex-view-pdflatex)
+
+(defun asy-master-tex-view-pdflatex-f ()
+ "Look at `asy-master-tex-view'"
+ (interactive)
+ (asy-master-tex-view 'lasy-view-pdf-via-pdflatex t t))
+(define-key asy-mode-map (kbd "<M-S-return>") 'asy-master-tex-view-pdflatex-f)
+
+(defun asy-master-tex-view-ps2pdf ()
+ "Look at `asy-master-tex-view'"
+ (interactive)
+ (asy-master-tex-view 'lasy-view-pdf-via-ps2pdf nil t))
+(define-key asy-mode-map (kbd "<C-M-return>") 'asy-master-tex-view-ps2pdf)
+
+(defun asy-master-tex-view-ps2pdf-f ()
+ "Look at `asy-master-tex-view'"
+ (interactive)
+ (asy-master-tex-view 'lasy-view-pdf-via-ps2pdf t t))
+(define-key asy-mode-map (kbd "<C-M-S-return>") 'asy-master-tex-view-ps2pdf-f)
+
+(provide `asy-mode)
+;;; asy-mode.el ends here
diff --git a/graphics/asymptote/base/asy.vim b/graphics/asymptote/base/asy.vim
new file mode 100644
index 0000000000..4cb897f131
--- /dev/null
+++ b/graphics/asymptote/base/asy.vim
@@ -0,0 +1,202 @@
+" Vim syntax file
+" Language: Asymptote
+" Maintainer: Andy Hammerlindl
+" Last Change: 2005 Aug 23
+
+" Hacked together from Bram Moolenaar's C syntax file, and Claudio Fleiner's
+" Java syntax file.
+
+" For version 5.x: Clear all syntax items
+" For version 6.x: Quit when a syntax file was already loaded
+if version < 600
+ syntax clear
+elseif exists("b:current_syntax")
+ finish
+endif
+
+" A bunch of useful C keywords
+syn keyword asyStatement break return continue unravel
+syn keyword asyConditional if else
+syn keyword asyRepeat while for do
+syn keyword asyExternal access from import include
+syn keyword asyOperator new operator
+
+syn keyword asyTodo contained TODO FIXME XXX
+
+" asyCommentGroup allows adding matches for special things in comments
+syn cluster asyCommentGroup contains=asyTodo
+
+" String and Character constants
+" Highlight special characters (those proceding a double backslash) differently
+syn match asySpecial display contained "\\\\."
+" Highlight line continuation slashes
+syn match asySpecial display contained "\\$"
+syn region asyString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=asySpecial
+ " asyCppString: same as asyString, but ends at end of line
+if 0
+syn region asyCppString start=+"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end='$' contains=asySpecial
+endif
+
+"when wanted, highlight trailing white space
+if exists("asy_space_errors")
+ if !exists("asy_no_trail_space_error")
+ syn match asySpaceError display excludenl "\s\+$"
+ endif
+ if !exists("asy_no_tab_space_error")
+ syn match asySpaceError display " \+\t"me=e-1
+ endif
+endif
+
+"catch errors caused by wrong parenthesis and brackets
+syn cluster asyParenGroup contains=asyParenError,asyIncluded,asySpecial,asyCommentSkip,asyCommentString,asyComment2String,@asyCommentGroup,asyCommentStartError,asyUserCont,asyUserLabel,asyBitField,asyCommentSkip,asyOctalZero,asyCppOut,asyCppOut2,asyCppSkip,asyFormat,asyNumber,asyFloat,asyOctal,asyOctalError,asyNumbersCom
+if exists("asy_no_bracket_error")
+ syn region asyParen transparent start='(' end=')' contains=ALLBUT,@asyParenGroup,asyCppParen,asyCppString
+ " asyCppParen: same as asyParen but ends at end-of-line; used in asyDefine
+ syn region asyCppParen transparent start='(' skip='\\$' excludenl end=')' end='$' contained contains=ALLBUT,@asyParenGroup,asyParen,asyString
+ syn match asyParenError display ")"
+ syn match asyErrInParen display contained "[{}]"
+else
+ syn region asyParen transparent start='(' end=')' contains=ALLBUT,@asyParenGroup,asyCppParen,asyErrInBracket,asyCppBracket,asyCppString
+ " asyCppParen: same as asyParen but ends at end-of-line; used in asyDefine
+ syn region asyCppParen transparent start='(' skip='\\$' excludenl end=')' end='$' contained contains=ALLBUT,@asyParenGroup,asyErrInBracket,asyParen,asyBracket,asyString
+if 0
+ syn match asyParenError display "[\])]"
+ syn match asyErrInParen display contained "[\]]"
+endif
+ syn region asyBracket transparent start='\[' end=']' contains=ALLBUT,@asyParenGroup,asyErrInParen,asyCppParen,asyCppBracket,asyCppString
+ " asyCppBracket: same as asyParen but ends at end-of-line; used in asyDefine
+ syn region asyCppBracket transparent start='\[' skip='\\$' excludenl end=']' end='$' contained contains=ALLBUT,@asyParenGroup,asyErrInParen,asyParen,asyBracket,asyString
+ syn match asyErrInBracket display contained "[);]"
+endif
+
+"integer number, or floating point number without a dot and with "f".
+syn case ignore
+syn match asyNumbers display transparent "\<\d\|\.\d" contains=asyNumber,asyFloat
+syn match asyNumber display contained "\d\+"
+"floating point number, with dot, optional exponent
+syn match asyFloat display contained "\d\+\.\d*\(e[-+]\=\d\+\)\="
+"floating point number, starting with a dot, optional exponent
+syn match asyFloat display contained "\.\d\+\(e[-+]\=\d\+\)\="
+"floating point number, without dot, with exponent
+syn match asyFloat display contained "\d\+e[-+]\=\d\+"
+syn case match
+
+if exists("asy_comment_strings")
+ " A comment can contain asyString, asyCharacter and asyNumber.
+ " But a "*/" inside a asyString in a asyComment DOES end the comment! So we
+ " need to use a special type of asyString: asyCommentString, which also ends on
+ " "*/", and sees a "*" at the start of the line as comment again.
+ " Unfortunately this doesn't very well work for // type of comments :-(
+ syntax match asyCommentSkip contained "^\s*\*\($\|\s\+\)"
+ syntax region asyCommentString contained start=+L\="+ skip=+\\\\\|\\"+ end=+"+ end=+\*/+me=s-1 contains=asySpecial,asyCommentSkip
+ syntax region asyComment2String contained start=+L\="+ skip=+\\\\\|\\"+ end=+"+ end="$" contains=asySpecial
+ syntax region asyCommentL start="//" skip="\\$" end="$" keepend contains=@asyCommentGroup,asyComment2String,asyCharacter,asyNumbersCom,asySpaceError
+ syntax region asyComment matchgroup=asyCommentStart start="/\*" matchgroup=NONE end="\*/" contains=@asyCommentGroup,asyCommentStartError,asyCommentString,asyCharacter,asyNumbersCom,asySpaceError
+else
+ syn region asyCommentL start="//" skip="\\$" end="$" keepend contains=@asyCommentGroup,asySpaceError
+ syn region asyComment matchgroup=asyCommentStart start="/\*" matchgroup=NONE end="\*/" contains=@asyCommentGroup,asyCommentStartError,asySpaceError
+endif
+" keep a // comment separately, it terminates a preproc. conditional
+syntax match asyCommentError display "\*/"
+syntax match asyCommentStartError display "/\*"me=e-1 contained
+
+syn keyword asyType void bool int real string
+syn keyword asyType pair triple transform guide path pen frame
+syn keyword asyType picture
+
+syn keyword asyStructure struct typedef
+syn keyword asyStorageClass static public readable private explicit
+
+syn keyword asyPathSpec and cycle controls tension atleast curl
+
+syn keyword asyConstant true false
+syn keyword asyConstant null nullframe nullpath
+
+if exists("asy_syn_plain")
+ syn keyword asyConstant currentpicture currentpen currentprojection
+ syn keyword asyConstant inch inches cm mm bp pt up down right left
+ syn keyword asyConstant E NE N NW W SW S SE
+ syn keyword asyConstant ENE NNE NNW WNW WSW SSW SSE ESE
+ syn keyword asyConstant I pi twopi
+ syn keyword asyConstant solid dotted dashed dashdotted
+ syn keyword asyConstant longdashed longdashdotted
+ syn keyword asyConstant squarecap roundcap extendcap
+ syn keyword asyConstant miterjoin roundjoin beveljoin
+ syn keyword asyConstant zerowinding evenodd
+ syn keyword asyConstant invisible black gray grey white
+ syn keyword asyConstant lightgray lightgrey
+ syn keyword asyConstant red green blue
+ syn keyword asyConstant cmyk Cyan Magenta Yellow Black
+ syn keyword asyConstant yellow magenta cyan
+ syn keyword asyConstant brown darkgreen darkblue
+ syn keyword asyConstant orange purple royalblue olive
+ syn keyword asyConstant chartreuse fuchsia salmon lightblue springgreen
+ syn keyword asyConstant pink
+endif
+
+syn sync ccomment asyComment minlines=15
+
+" Define the default highlighting.
+" For version 5.7 and earlier: only when not done already
+" For version 5.8 and later: only when an item doesn't have highlighting yet
+if version >= 508 || !exists("did_asy_syn_inits")
+ if version < 508
+ let did_asy_syn_inits = 1
+ command -nargs=+ HiLink hi link <args>
+ else
+ command -nargs=+ HiLink hi def link <args>
+ endif
+
+ HiLink asyFormat asySpecial
+ HiLink asyCppString asyString
+ HiLink asyCommentL asyComment
+ HiLink asyCommentStart asyComment
+ HiLink asyLabel Label
+ HiLink asyUserLabel Label
+ HiLink asyConditional Conditional
+ HiLink asyRepeat Repeat
+ HiLink asyCharacter Character
+ HiLink asySpecialCharacter asySpecial
+ HiLink asyNumber Number
+ HiLink asyOctal Number
+ HiLink asyOctalZero PreProc " link this to Error if you want
+ HiLink asyFloat Float
+ HiLink asyOctalError asyError
+ HiLink asyParenError asyError
+ HiLink asyErrInParen asyError
+ HiLink asyErrInBracket asyError
+ HiLink asyCommentError asyError
+ HiLink asyCommentStartError asyError
+ HiLink asySpaceError asyError
+ HiLink asySpecialError asyError
+ HiLink asyOperator Operator
+ HiLink asyStructure Structure
+ HiLink asyStorageClass StorageClass
+ HiLink asyExternal Include
+ HiLink asyPreProc PreProc
+ HiLink asyDefine Macro
+ HiLink asyIncluded asyString
+ HiLink asyError Error
+ HiLink asyStatement Statement
+ HiLink asyPreCondit PreCondit
+ HiLink asyType Type
+ HiLink asyConstant Constant
+ HiLink asyCommentString asyString
+ HiLink asyComment2String asyString
+ HiLink asyCommentSkip asyComment
+ HiLink asyString String
+ HiLink asyComment Comment
+ HiLink asySpecial SpecialChar
+ HiLink asyTodo Todo
+ HiLink asyCppSkip asyCppOut
+ HiLink asyCppOut2 asyCppOut
+ HiLink asyCppOut Comment
+ HiLink asyPathSpec Statement
+
+
+ delcommand HiLink
+endif
+
+let b:current_syntax = "c"
+
+" vim: ts=8
diff --git a/graphics/asymptote/base/asy_filetype.vim b/graphics/asymptote/base/asy_filetype.vim
new file mode 100644
index 0000000000..3b614edf12
--- /dev/null
+++ b/graphics/asymptote/base/asy_filetype.vim
@@ -0,0 +1,3 @@
+" Vim filetype detection file
+" Language: Asymptote
+au BufNewFile,BufRead *.asy setfiletype asy
diff --git a/graphics/asymptote/base/asymptote.py b/graphics/asymptote/base/asymptote.py
new file mode 100755
index 0000000000..19b72c5082
--- /dev/null
+++ b/graphics/asymptote/base/asymptote.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+
+# Python module to feed Asymptote with commands
+# (modified from gnuplot.py)
+from subprocess import *
+class asy:
+ def __init__(self):
+ self.session = Popen(['asy','-quiet','-inpipe=0','-outpipe=2'],stdin=PIPE)
+ self.help()
+ def send(self, cmd):
+ self.session.stdin.write(bytes(cmd+'\n','utf-8'))
+ self.session.stdin.flush()
+ def size(self, size):
+ self.send("size(%d);" % size)
+ def draw(self, str):
+ self.send("draw(%s);" % str)
+ def fill(self, str):
+ self.send("fill(%s);" % str)
+ def clip(self, str):
+ self.send("clip(%s);" % str)
+ def label(self, str):
+ self.send("label(%s);" % str)
+ def shipout(self, str):
+ self.send("shipout(\"%s\");" % str)
+ def erase(self):
+ self.send("erase();")
+ def help(self):
+ print("Asymptote session is open. Available methods are:")
+ print(" help(), size(int), draw(str), fill(str), clip(str), label(str), shipout(str), send(str), erase()")
+ def __del__(self):
+ print("closing Asymptote session...")
+ self.send('quit');
+ self.session.stdin.close();
+ self.session.wait()
+
+
+
+
+
+if __name__=="__main__":
+ g = asy()
+ g.size(200)
+ g.draw("unitcircle")
+ g.send("draw(unitsquare)")
+ g.fill("unitsquare, blue")
+ g.clip("unitcircle")
+ g.label("\"$O$\", (0,0), SW")
+ raw_input("press ENTER to continue")
+ g.erase()
+ del g
diff --git a/graphics/asymptote/base/babel.asy b/graphics/asymptote/base/babel.asy
new file mode 100644
index 0000000000..e93dec2a5c
--- /dev/null
+++ b/graphics/asymptote/base/babel.asy
@@ -0,0 +1,4 @@
+void babel(string s)
+{
+ usepackage("babel",s);
+}
diff --git a/graphics/asymptote/base/bezulate.asy b/graphics/asymptote/base/bezulate.asy
new file mode 100644
index 0000000000..38d35718d4
--- /dev/null
+++ b/graphics/asymptote/base/bezulate.asy
@@ -0,0 +1,310 @@
+// Bezier triangulation routines written by Orest Shardt, 2008.
+
+private real fuzz=sqrtEpsilon;
+real duplicateFuzz=1e-3; // Work around font errors.
+real maxrefinements=10;
+
+private real[][] intersections(pair a, pair b, path p)
+{
+ pair delta=fuzz*unit(b-a);
+ return intersections(a-delta--b+delta,p,fuzz);
+}
+
+int countIntersections(path[] p, pair start, pair end)
+{
+ int intersects=0;
+ for(path q : p)
+ intersects += intersections(start,end,q).length;
+ return intersects;
+}
+
+path[][] containmentTree(path[] paths)
+{
+ path[][] result;
+ for(path g : paths) {
+ // check if current curve contains or is contained in a group of curves
+ int j;
+ for(j=0; j < result.length; ++j) {
+ path[] resultj=result[j];
+ int test=inside(g,resultj[0],zerowinding);
+ if(test == 1) {
+ // current curve contains group's toplevel curve;
+ // replace toplevel curve with current curve
+ resultj.insert(0,g);
+ // check to see if any other groups are contained within this curve
+ for(int k=j+1; k < result.length;) {
+ if(inside(g,result[k][0],zerowinding) == 1) {
+ resultj.append(result[k]);
+ result.delete(k);
+ } else ++k;
+ }
+ break;
+ } else if(test == -1) {
+ // current curve contained within group's toplevel curve
+ resultj.push(g);
+ break;
+ }
+ }
+ // create a new group if this curve does not belong to another group
+ if(j == result.length)
+ result.push(new path[] {g});
+ }
+ return result;
+}
+
+bool isDuplicate(pair a, pair b, real relSize)
+{
+ return abs(a-b) <= duplicateFuzz*relSize;
+}
+
+path removeDuplicates(path p)
+{
+ real relSize = abs(max(p)-min(p));
+ bool cyclic=cyclic(p);
+ for(int i=0; i < length(p); ++i) {
+ if(isDuplicate(point(p,i),point(p,i+1),relSize)) {
+ p=subpath(p,0,i)&subpath(p,i+1,length(p));
+ --i;
+ }
+ }
+ return cyclic ? p&cycle : p;
+}
+
+path section(path p, real t1, real t2, bool loop=false)
+{
+ if(t2 < t1 || loop && t1 == t2)
+ t2 += length(p);
+ return subpath(p,t1,t2);
+}
+
+path uncycle(path p, real t)
+{
+ return subpath(p,t,t+length(p));
+}
+
+// returns outer paths
+void connect(path[] paths, path[] result, path[] patch)
+{
+ path[][] tree=containmentTree(paths);
+ for(path[] group : tree) {
+ path outer = group[0];
+ group.delete(0);
+ path[][] innerTree = containmentTree(group);
+ path[] remainingCurves;
+ path[] inners;
+ for(path[] innerGroup:innerTree)
+ {
+ inners.push(innerGroup[0]);
+ if(innerGroup.length>1)
+ remainingCurves.append(innerGroup[1:]);
+ }
+ connect(remainingCurves,result,patch);
+ real d=2*abs(max(outer)-min(outer));
+ while(inners.length > 0) {
+ int curveIndex = 0;
+ //pair direction=I*dir(inners[curveIndex],0,1); // Use outgoing direction
+ //if(direction == 0) // Try a random direction
+ // direction=expi(2pi*unitrand());
+ //pair start=point(inners[curveIndex],0);
+
+ // find shortest distance between a node on the inner curve and a node
+ // on the outer curve
+
+ real mindist = d;
+ int inner_i = 0;
+ int outer_i = 0;
+ for(int ni = 0; ni < length(inners[curveIndex]); ++ni)
+ {
+ for(int no = 0; no < length(outer); ++no)
+ {
+ real dist = abs(point(inners[curveIndex],ni)-point(outer,no));
+ if(dist < mindist)
+ {
+ inner_i = ni;
+ outer_i = no;
+ mindist = dist;
+ }
+ }
+ }
+ pair start=point(inners[curveIndex],inner_i);
+ pair end = point(outer,outer_i);
+
+ // find first intersection of line segment with outer curve
+ //real[][] ints=intersections(start,start+d*direction,outer);
+ real[][] ints=intersections(start,end,outer);
+ assert(ints.length != 0);
+ real endtime=ints[0][1]; // endtime is time on outer
+ end = point(outer,endtime);
+ // find first intersection of end--start with any inner curve
+ real starttime=inner_i; // starttime is time on inners[curveIndex]
+ real earliestTime=1;
+ for(int j=0; j < inners.length; ++j) {
+ real[][] ints=intersections(end,start,inners[j]);
+
+ if(ints.length > 0 && ints[0][0] < earliestTime) {
+ earliestTime=ints[0][0]; // time on end--start
+ starttime=ints[0][1]; // time on inner curve
+ curveIndex=j;
+ }
+ }
+ start=point(inners[curveIndex],starttime);
+
+
+ bool found_forward = false;
+ real timeoffset_forward = 2;
+ path portion_forward;
+ path[] allCurves = {outer};
+ allCurves.append(inners);
+
+ while(!found_forward && timeoffset_forward > fuzz) {
+ timeoffset_forward /= 2;
+ if(countIntersections(allCurves,start,
+ point(outer,endtime+timeoffset_forward)) == 2)
+ {
+ portion_forward = subpath(outer,endtime,endtime+timeoffset_forward)--start--cycle;
+
+ found_forward=true;
+ // check if an inner curve is inside the portion
+ for(int k = 0; found_forward && k < inners.length; ++k)
+ {
+ if(k!=curveIndex &&
+ inside(portion_forward,point(inners[k],0),zerowinding))
+ found_forward = false;
+ }
+ }
+ }
+
+ bool found_backward = false;
+ real timeoffset_backward = -2;
+ path portion_backward;
+ while(!found_backward && timeoffset_backward < -fuzz) {
+ timeoffset_backward /= 2;
+ if(countIntersections(allCurves,start,
+ point(outer,endtime+timeoffset_backward))==2)
+ {
+ portion_backward = subpath(outer,endtime+timeoffset_backward,endtime)--start--cycle;
+ found_backward = true;
+ // check if an inner curve is inside the portion
+ for(int k = 0; found_backward && k < inners.length; ++k)
+ {
+ if(k!=curveIndex &&
+ inside(portion_backward,point(inners[k],0),zerowinding))
+ found_backward = false;
+ }
+ }
+ }
+ assert(found_forward || found_backward);
+ real timeoffset;
+ path portion;
+ if(found_forward && !found_backward)
+ {
+ timeoffset = timeoffset_forward;
+ portion = portion_forward;
+ }
+ else if(found_backward && !found_forward)
+ {
+ timeoffset = timeoffset_backward;
+ portion = portion_backward;
+ }
+ else // assert handles case of neither found
+ {
+ if(timeoffset_forward > -timeoffset_backward)
+ {
+ timeoffset = timeoffset_forward;
+ portion = portion_forward;
+ }
+ else
+ {
+ timeoffset = timeoffset_backward;
+ portion = portion_backward;
+ }
+ }
+
+ endtime=min(endtime,endtime+timeoffset);
+ // or go from timeoffset+timeoffset_backward to timeoffset+timeoffset_forward?
+ timeoffset=abs(timeoffset);
+
+ // depends on the curves having opposite orientations
+ path remainder=section(outer,endtime+timeoffset,endtime)
+ --uncycle(inners[curveIndex],
+ starttime)--cycle;
+ inners.delete(curveIndex);
+ outer = remainder;
+ patch.append(portion);
+ }
+ result.append(outer);
+ }
+}
+
+bool checkSegment(path g, pair p, pair q)
+{
+ pair mid=0.5*(p+q);
+ return intersections(p,q,g).length == 2 &&
+ inside(g,mid,zerowinding) && intersections(g,mid).length == 0;
+}
+
+path subdivide(path p)
+{
+ path q;
+ int l=length(p);
+ for(int i=0; i < l; ++i)
+ q=q&(straight(p,i) ? subpath(p,i,i+1) :
+ subpath(p,i,i+0.5)&subpath(p,i+0.5,i+1));
+ return cyclic(p) ? q&cycle : q;
+}
+
+path[] bezulate(path[] p)
+{
+ if(p.length == 1 && length(p[0]) <= 4) return p;
+ path[] patch;
+ path[] result;
+ connect(p,result,patch);
+ for(int i=0; i < result.length; ++i) {
+ path p=result[i];
+ int refinements=0;
+ if(size(p) <= 1) return p;
+ if(!cyclic(p))
+ abort("path must be cyclic and nonselfintersecting.");
+ p=removeDuplicates(p);
+ if(length(p) > 4) {
+ static real SIZE_STEPS=10;
+ static real factor=1.05/SIZE_STEPS;
+ for(int k=1; k <= SIZE_STEPS; ++k) {
+ real L=factor*k*abs(max(p)-min(p));
+ for(int i=0; length(p) > 4 && i < length(p); ++i) {
+ bool found=false;
+ pair start=point(p,i);
+ //look for quadrilaterals and triangles with one line, 4 | 3 curves
+ for(int desiredSides=4; !found && desiredSides >= 3;
+ --desiredSides) {
+ if(desiredSides == 3 && length(p) <= 3)
+ break;
+ pair end;
+ int endi=i+desiredSides-1;
+ end=point(p,endi);
+ found=checkSegment(p,start,end) && abs(end-start) < L;
+ if(found) {
+ path p1=subpath(p,endi,i+length(p))--cycle;
+ patch.append(subpath(p,i,endi)--cycle);
+ p=removeDuplicates(p1);
+ i=-1; // increment will make i be 0
+ }
+ }
+ if(!found && k == SIZE_STEPS && length(p) > 4 && i == length(p)-1) {
+ // avoid infinite recursion
+ ++refinements;
+ if(refinements > maxrefinements) {
+ warning("subdivisions","too many subdivisions",position=true);
+ } else {
+ p=subdivide(p);
+ i=-1;
+ }
+ }
+ }
+ }
+ }
+ if(length(p) <= 4)
+ patch.append(p);
+ }
+ return patch;
+}
diff --git a/graphics/asymptote/base/binarytree.asy b/graphics/asymptote/base/binarytree.asy
new file mode 100644
index 0000000000..31e0e3aab6
--- /dev/null
+++ b/graphics/asymptote/base/binarytree.asy
@@ -0,0 +1,383 @@
+/* **********************************************************************
+ * binarytree: An Asymptote module to draw binary trees *
+ * *
+ * Copyright(C) 2006 *
+ * Tobias Langner tobias[at]langner[dot]nightlabs[dot]de *
+ * *
+ * Modified by John Bowman *
+ * *
+ * Condensed mode: *
+ * Copyright(C) 2012 *
+ * Gerasimos Dimitriadis dimeg [at] intracom [dot] gr *
+ * *
+ ************************************************************************
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Lesser General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 3 of the License, or(at your option) any later version. *
+ * *
+ * This library 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 *
+ * Lesser General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public *
+ * License along with this library; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin St, Fifth Floor, *
+ * Boston, MA 02110-1301 USA *
+ * *
+ * Or get it online: *
+ * http: //www.gnu.org/copyleft/lesser.html *
+ * *
+ ***********************************************************************/
+
+// default values
+real minDistDefault=0.2cm;
+real nodeMarginDefault=0.1cm;
+
+// structure to represent nodes in a binary tree
+struct binarytreeNode {
+ int key;
+ binarytreeNode left;
+ binarytreeNode right;
+ binarytreeNode parent;
+ bool spans_calculated=false;
+ int left_span,total_left_span;
+ int right_span,total_right_span;
+ void update_spans();
+
+ // Get the horizontal span of the tree consisting of the current
+ // node plus the whole subtree that is rooted at the right child
+ // (condensed mode)
+ int getTotalRightSpan() {
+ if(spans_calculated == false) {
+ update_spans();
+ }
+
+ return total_right_span;
+ }
+
+ // Get the horizontal span of the tree consisting of the current
+ // node plus the whole subtree that is rooted at the left child
+ // (condensed mode)
+ int getTotalLeftSpan() {
+ if(spans_calculated == false) {
+ update_spans();
+ }
+ return total_left_span;
+ }
+
+ // Get the horizontal distance between this node and its right child
+ // (condensed mode)
+ int getRightSpan() {
+ if(spans_calculated == false) {
+ update_spans();
+ }
+ return right_span;
+ }
+
+ // Get the horizontal distance between this node and its left child
+ // (condensed mode)
+ int getLeftSpan() {
+ if(spans_calculated == false) {
+ update_spans();
+ }
+ return left_span;
+ }
+
+ // Update all span figures for this node.
+ // condensed mode)
+ update_spans=new void() {
+ if(spans_calculated == true)
+ return;
+
+ left_span=0;
+ total_left_span=0;
+ right_span=0;
+ total_right_span=0;
+
+ if(left != null) {
+ left_span=left.getTotalRightSpan()+1;
+ total_left_span=left_span+left.getTotalLeftSpan();
+ }
+
+ if(right != null) {
+ right_span=right.getTotalLeftSpan()+1;
+ total_right_span=right_span+right.getTotalRightSpan();
+ }
+ spans_calculated=true;
+ };
+
+ // set the left child of this node
+ void setLeft(binarytreeNode left) {
+ this.left=left;
+ this.left.parent=this;
+ }
+
+ // set the right child of this node
+ void setRight(binarytreeNode right) {
+ this.right=right;
+ this.right.parent=this;
+ }
+
+ // return a boolean indicating whether this node is the root
+ bool isRoot() {
+ return parent == null;
+ }
+
+ // return the level of the subtree rooted at this node.
+ int getLevel() {
+ if(isRoot())
+ return 1;
+ else
+ return parent.getLevel()+1;
+ }
+
+ // set the children of this binarytreeNode
+ void setChildren(binarytreeNode left, binarytreeNode right) {
+ setLeft(left);
+ setRight(right);
+ }
+
+ // create a new binarytreeNode with key <key>
+ static binarytreeNode binarytreeNode(int key) {
+ binarytreeNode toReturn=new binarytreeNode;
+ toReturn.key=key;
+ return toReturn;
+ }
+
+ // returns the height of the subtree rooted at this node.
+ int getHeight() {
+ if(left == null && right == null)
+ return 1;
+ if(left == null)
+ return right.getHeight()+1;
+ if(right == null)
+ return left.getHeight()+1;
+
+ return max(left.getHeight(),right.getHeight())+1;
+ }
+}
+
+binarytreeNode operator init() {return null;}
+
+// "constructor" for binarytreeNode
+binarytreeNode binarytreeNode(int key)=binarytreeNode.binarytreeNode;
+
+// draw the tree rooted at the given <node> at the given position <pos>, with
+// <height>=the height of the containing tree,
+// <minDist>=the minimal horizontal distance of two nodes at the lowest level,
+// <levelDist>=the vertical distance between two levels,
+// <nodeDiameter>=the diameter of one node.
+object draw(picture pic=currentpicture, binarytreeNode node, pair pos,
+ int height, real minDist, real levelDist, real nodeDiameter,
+ pen p=currentpen, bool condensed=false) {
+ Label label=Label(math((string) node.key),pos);
+
+ binarytreeNode left=node.left;
+ binarytreeNode right=node.right;
+
+ // return the distance for two nodes at the given <level> when the
+ // containing tree has height <height>
+ // and the minimal distance between two nodes is <minDist> .
+ real getDistance(int level, int height, real minDist) {
+ return(nodeDiameter+minDist)*2^(height-level);
+ }
+
+ // return the horiontal distance between node <n> and its left child
+ // (condensed mode)
+ real getLeftDistance(binarytreeNode n) {
+ return(nodeDiameter+minDist) *(real)n.getLeftSpan() * 0.5;
+ }
+
+ // return the horiontal distance between node <n> and its right child
+ // (condensed mode)
+ real getRightDistance(binarytreeNode n) {
+ return(nodeDiameter+minDist) *(real)n.getRightSpan() * 0.5;
+ }
+
+ real dist=getDistance(node.getLevel(),height,minDist)/2;
+
+ // draw the connection between the two nodes at the given positions
+ // by calculating the connection points and drawing the corresponding
+ // arrow.
+ void deferredDrawNodeConnection(pair parentPos, pair childPos) {
+ pic.add(new void(frame f, transform t) {
+ pair start,end;
+ // calculate connection path
+ transform T=shift(nodeDiameter/2*unit(t*childPos-t*parentPos));
+ path arr=(T*t*parentPos)--(inverse(T)*t*childPos);
+ draw(f,PenMargin(arr,p).g,p,Arrow(5));
+ });
+ pic.addPoint(parentPos);
+ pic.addPoint(childPos);
+ }
+
+ if(left != null) {
+ pair childPos;
+ if(condensed == false) {
+ childPos=pos-(0,levelDist)-(dist/2,0);
+ }
+ else {
+ childPos=pos-(0,levelDist)-((real)getLeftDistance(node),0);
+ }
+ draw(pic,left,childPos,height,minDist,levelDist,nodeDiameter,p,condensed);
+ deferredDrawNodeConnection(pos,childPos);
+ }
+
+ if(right != null) {
+ pair childPos;
+ if(condensed == false) {
+ childPos=pos-(0,levelDist)+(dist/2,0);
+ }
+ else {
+ childPos=pos-(0,levelDist)+((real)getRightDistance(node),0);
+ }
+ draw(pic,right,childPos,height,minDist,levelDist,nodeDiameter,p,condensed);
+ deferredDrawNodeConnection(pos,childPos);
+ }
+
+ picture obj;
+ draw(obj,circle((0,0),nodeDiameter/2),p);
+ label(obj,label,(0,0),p);
+
+ add(pic,obj,pos);
+
+ return label;
+}
+
+struct key {
+ int n;
+ bool active;
+}
+
+key key(int n, bool active=true) {key k; k.n=n; k.active=active; return k;}
+
+key operator cast(int n) {return key(n);}
+int operator cast(key k) {return k.n;}
+int[] operator cast(key[] k) {
+ int[] I;
+ for(int i=0; i < k.length; ++i)
+ I[i]=k[i].n;
+ return I;
+}
+
+key nil=key(0,false);
+
+// structure to represent a binary tree.
+struct binarytree {
+ binarytreeNode root;
+ int[] keys;
+
+ // add the given <key> to the tree by searching for its place and
+ // inserting it there.
+ void addKey(int key) {
+ binarytreeNode newNode=binarytreeNode(key);
+
+ if(root == null) {
+ root=newNode;
+ keys.push(key);
+ return;
+ }
+
+ binarytreeNode n=root;
+ while(n != null) {
+ if(key < n.key) {
+ if(n.left != null)
+ n=n.left;
+ else {
+ n.setLeft(newNode);
+ keys.push(key);
+ return;
+ }
+ } else if(key > n.key) {
+ if(n.right != null)
+ n=n.right;
+ else {
+ n.setRight(newNode);
+ keys.push(key);
+ return;
+ }
+ }
+ }
+ }
+
+ // return the height of the tree
+ int getHeight() {
+ if(root == null)
+ return 0;
+ else
+ return root.getHeight();
+ }
+
+ // add all given keys to the tree sequentially
+ void addSearchKeys(int[] keys) {
+ for(int i=0; i < keys.length; ++i) {
+ int key=keys[i];
+ // Ignore duplicate keys
+ if(find(this.keys == key) == -1)
+ addKey(key);
+ }
+ }
+
+ binarytreeNode build(key[] keys, int[] ind) {
+ if(ind[0] >= keys.length) return null;
+ key k=keys[ind[0]];
+ ++ind[0];
+ if(!k.active) return null;
+ binarytreeNode bt=binarytreeNode(k);
+ binarytreeNode left=build(keys,ind);
+ binarytreeNode right=build(keys,ind);
+ bt.left=left; bt.right=right;
+ if(left != null) left.parent=bt;
+ if(right != null) right.parent=bt;
+ return bt;
+ }
+
+ void addKeys(key[] keys) {
+ int[] ind={0};
+ root=build(keys,ind);
+ this.keys=keys;
+ }
+
+
+ // return all key in the tree
+ int[] getKeys() {
+ return keys;
+ }
+}
+
+binarytree searchtree(...int[] keys)
+{
+ binarytree bt;
+ bt.addSearchKeys(keys);
+ return bt;
+}
+
+binarytree binarytree(...key[] keys)
+{
+ binarytree bt;
+ bt.addKeys(keys);
+ return bt;
+}
+
+// draw the given binary tree.
+void draw(picture pic=currentpicture, binarytree tree,
+ real minDist=minDistDefault, real nodeMargin=nodeMarginDefault,
+ pen p=currentpen, bool condensed=false)
+{
+ int[] keys=tree.getKeys();
+
+ // calculate the node diameter so that all keys fit into it
+ frame f;
+ for(int i=0; i < keys.length; ++i)
+ label(f,math(string(keys[i])),p);
+
+ real nodeDiameter=abs(max(f)-min(f))+2*nodeMargin;
+ real levelDist=nodeDiameter*1.8;
+
+ draw(pic,tree.root,(0,0),tree.getHeight(),minDist,levelDist,nodeDiameter,p,
+ condensed);
+}
diff --git a/graphics/asymptote/base/bsp.asy b/graphics/asymptote/base/bsp.asy
new file mode 100644
index 0000000000..cf9d7e0781
--- /dev/null
+++ b/graphics/asymptote/base/bsp.asy
@@ -0,0 +1,209 @@
+private import math;
+import three;
+
+real epsilon=10*realEpsilon;
+
+// Routines for hidden surface removal (via binary space partition):
+// Structure face is derived from picture.
+struct face {
+ picture pic;
+ transform t;
+ frame fit;
+ triple normal,point;
+ triple min,max;
+ void operator init(path3 p) {
+ this.normal=normal(p);
+ if(this.normal == O) abort("path is linear");
+ this.point=point(p,0);
+ min=min(p);
+ max=max(p);
+ }
+ face copy() {
+ face f=new face;
+ f.pic=pic.copy();
+ f.t=t;
+ f.normal=normal;
+ f.point=point;
+ f.min=min;
+ f.max=max;
+ add(f.fit,fit);
+ return f;
+ }
+}
+
+picture operator cast(face f) {return f.pic;}
+face operator cast(path3 p) {return face(p);}
+
+struct line {
+ triple point;
+ triple dir;
+}
+
+private line intersection(face a, face b)
+{
+ line L;
+ L.point=intersectionpoint(a.normal,a.point,b.normal,b.point);
+ L.dir=unit(cross(a.normal,b.normal));
+ return L;
+}
+
+struct half {
+ pair[] left,right;
+
+ // Sort the points in the pair array z according to whether they lie on the
+ // left or right side of the line L in the direction dir passing through P.
+ // Points exactly on L are considered to be on the right side.
+ // Also push any points of intersection of L with the path operator --(... z)
+ // onto each of the arrays left and right.
+ void operator init(pair dir, pair P ... pair[] z) {
+ pair lastz;
+ pair invdir=dir != 0 ? 1/dir : 0;
+ bool left,last;
+ for(int i=0; i < z.length; ++i) {
+ left=(invdir*z[i]).y > (invdir*P).y;
+ if(i > 0 && last != left) {
+ pair w=extension(P,P+dir,lastz,z[i]);
+ this.left.push(w);
+ this.right.push(w);
+ }
+ if(left) this.left.push(z[i]);
+ else this.right.push(z[i]);
+ last=left;
+ lastz=z[i];
+ }
+ }
+}
+
+struct splitface {
+ face back,front;
+}
+
+// Return the pieces obtained by splitting face a by face cut.
+splitface split(face a, face cut, projection P)
+{
+ splitface S;
+
+ void nointersection() {
+ if(abs(dot(a.point-P.camera,a.normal)) >=
+ abs(dot(cut.point-P.camera,cut.normal))) {
+ S.back=a;
+ S.front=null;
+ } else {
+ S.back=null;
+ S.front=a;
+ }
+ }
+
+ if(P.infinity) {
+ P=P.copy();
+ static real factor=1/sqrtEpsilon;
+ P.camera *= factor*max(abs(a.min),abs(a.max),
+ abs(cut.min),abs(cut.max));
+ }
+
+ if((abs(a.normal-cut.normal) < epsilon ||
+ abs(a.normal+cut.normal) < epsilon)) {
+ nointersection();
+ return S;
+ }
+
+ line L=intersection(a,cut);
+
+ if(dot(P.camera-L.point,P.camera-P.target) < 0) {
+ nointersection();
+ return S;
+ }
+
+ pair point=a.t*project(L.point,P);
+ pair dir=a.t*project(L.point+L.dir,P)-point;
+ pair invdir=dir != 0 ? 1/dir : 0;
+ triple apoint=L.point+cross(L.dir,a.normal);
+ bool left=(invdir*(a.t*project(apoint,P))).y >= (invdir*point).y;
+
+ real t=intersect(apoint,P.camera,cut.normal,cut.point);
+ bool rightfront=left ^ (t <= 0 || t >= 1);
+
+ face back=a, front=a.copy();
+ pair max=max(a.fit);
+ pair min=min(a.fit);
+ half h=half(dir,point,max,(min.x,max.y),min,(max.x,min.y),max);
+ if(h.right.length == 0) {
+ if(rightfront) front=null;
+ else back=null;
+ } else if(h.left.length == 0) {
+ if(rightfront) back=null;
+ else front=null;
+ }
+ if(front != null)
+ clip(front.fit,operator --(... rightfront ? h.right : h.left)--cycle,
+ zerowinding);
+ if(back != null)
+ clip(back.fit,operator --(... rightfront ? h.left : h.right)--cycle,
+ zerowinding);
+ S.back=back;
+ S.front=front;
+ return S;
+}
+
+// A binary space partition
+struct bsp
+{
+ bsp back;
+ bsp front;
+ face node;
+
+ // Construct the bsp.
+ void operator init(face[] faces, projection P) {
+ if(faces.length != 0) {
+ this.node=faces.pop();
+ face[] front,back;
+ for(int i=0; i < faces.length; ++i) {
+ splitface split=split(faces[i],this.node,P);
+ if(split.front != null) front.push(split.front);
+ if(split.back != null) back.push(split.back);
+ }
+ this.front=bsp(front,P);
+ this.back=bsp(back,P);
+ }
+ }
+
+ // Draw from back to front.
+ void add(frame f) {
+ if(back != null) back.add(f);
+ add(f,node.fit,group=true);
+ if(labels(node.fit)) layer(f); // Draw over any existing TeX layers.
+ if(front != null) front.add(f);
+ }
+}
+
+void add(picture pic=currentpicture, face[] faces,
+ projection P=currentprojection)
+{
+ int n=faces.length;
+ face[] Faces=new face[n];
+ for(int i=0; i < n; ++i)
+ Faces[i]=faces[i].copy();
+
+ pic.add(new void (frame f, transform t, transform T,
+ pair m, pair M) {
+ // Fit all of the pictures so we know their exact sizes.
+ face[] faces=new face[n];
+ for(int i=0; i < n; ++i) {
+ faces[i]=Faces[i].copy();
+ face F=faces[i];
+ F.t=t*T*F.pic.T;
+ F.fit=F.pic.fit(t,T*F.pic.T,m,M);
+ }
+
+ bsp bsp=bsp(faces,P);
+ if(bsp != null) bsp.add(f);
+ });
+
+ for(int i=0; i < n; ++i) {
+ picture F=Faces[i].pic;
+ pic.userBox3(F.userMin3(), F.userMax3());
+ pic.bounds.append(F.T, F.bounds);
+ // The above 2 lines should be replaced with a routine in picture which
+ // copies only sizing data from another picture.
+ }
+}
diff --git a/graphics/asymptote/base/colormap.asy b/graphics/asymptote/base/colormap.asy
new file mode 100644
index 0000000000..4b2242d661
--- /dev/null
+++ b/graphics/asymptote/base/colormap.asy
@@ -0,0 +1,3890 @@
+// author: Fabian Hassler
+// year: 2019
+
+// This module implements a list of colormaps
+// the code has been converted from the python library
+// matplotlib 3.0.2 license under BSD
+// Feel free to use or to modify the code
+
+// example: the generate a palette wistia
+// pen[] Palette = wistia.palette()
+//
+// There are two types of palettes. For a complete list see below:
+//
+// 1) The segmented palettes can be used as
+// <name>.palette(int NColors=256, real gamma=1.)
+// NColors are the number of colors in the palette
+// gamma is the gamma-factor
+//
+// 2) The listed palettes can only be used as
+// <name>.palette()
+//
+// Both functions return pen[] that can be used as a palette in the
+// module palette.
+
+// list of palettes
+// see also https://matplotlib.org/tutorials/colors/colormaps.html
+
+// segmented palettes:
+// CMRmap
+// autumn
+// binary
+// bone
+// cool
+// coolwarm
+// copper
+// gist_earth
+// gist_ncar
+// gist_stern
+// gray
+// hot
+// hsv
+// jet
+// nipy_spectral
+// pink
+// spring
+// summer
+// winter
+// wistia
+
+// listed palettes:
+// Accent
+// Blues
+// BrBG
+// BuGn
+// BuPu
+// Dark2
+// GnBu
+// Greens
+// Greys
+// OrRd
+// Oranges
+// PRGn
+// Paired
+// Pastel1
+// Pastel2
+// PiYG
+// PuBuGn
+// PuBu
+// PuOr
+// PuRd
+// Purples
+// RdBu
+// RdGy
+// RdPu
+// RdYlBu
+// RdYlGn
+// Reds
+// Set1
+// Set2
+// Set3
+// Spectral
+// YlGnBu
+// YlGn
+// YlOrBr
+// YlOrRd
+// brg
+// bwr
+// seismic
+// tab10
+// tab20
+// tab20b
+// tab20c
+// cividis
+// inferno
+// magma
+// plasma
+// twilight
+// twilight_shifted
+// viridis
+
+
+// Example of usage:
+
+// import graph;
+// import palette;
+// import colormap;
+// int NColors=5;
+// pen[] Palette=spring.palette(NColors);
+// palette(bounds(0,1),(0.,0),(500,50),Bottom,Palette);
+
+//
+// SOURCE CODE
+//
+private real[] makeMappingArray(int N, triple[] data, real gamma=1.) {
+ real[] x;
+ real[] y0;
+ real[] y1;
+
+ for (int i=0; i<data.length; ++i) {
+ x.push(data[i].x);
+ y0.push(data[i].y);
+ y1.push(data[i].z);
+ };
+
+ x = x*(N-1);
+ real[] lut = new real[N];
+ real[] xind = (N - 1) * uniform(0, 1, N-1) ** gamma;
+ int[] ind = map(new int(real xi) {return search(x, xi);}, xind);
+ ind = ind[1:N-1]; // note that the index is shifted from python
+
+ real[] dist = (xind[1:N-1] - x[ind])/(x[ind+1] - x[ind]);
+
+ lut[1:N-1] = dist * (y0[ind+1] - y1[ind]) + y1[ind];
+ lut[0] = y1[0];
+ lut[N-1] = y0[y0.length-1];
+ return lut;
+}
+
+// struct for segmented data
+struct seg_data {
+ private triple[] r; // red
+ private triple[] g; // green
+ private triple[] b; // blue
+
+ void operator init(triple[] r, triple[] g, triple[] b) {
+ this.r=r;
+ this.g=g;
+ this.b=b;
+ }
+
+ pen[] palette(int NColors=256, real gamma=1.) {
+ real[] red = makeMappingArray(NColors, this.r, gamma);
+ real[] green = makeMappingArray(NColors, this.g, gamma);
+ real[] blue = makeMappingArray(NColors, this.b, gamma);
+
+ pen[] pal =
+ sequence(new pen(int i) {return rgb(red[i], green[i], blue[i]);},
+ NColors);
+
+ return pal;
+ }
+}
+
+// struct for list data
+struct list_data {
+ private pen[] data;
+ pen[] palette(){return data;}
+ void operator init(pen[] d) {
+ this.data=d;
+ }
+}
+
+//
+// DATA
+//
+list_data Accent = list_data(new pen[] {
+ rgb (0.4980392156862745, 0.788235294117647, 0.4980392156862745) ,
+ rgb (0.7450980392156863, 0.6823529411764706, 0.8313725490196079) ,
+ rgb (0.9921568627450981, 0.7529411764705882, 0.5254901960784314) ,
+ rgb (1.0, 1.0, 0.6) ,
+ rgb (0.2196078431372549, 0.4235294117647059, 0.6901960784313725) ,
+ rgb (0.9411764705882353, 0.00784313725490196, 0.4980392156862745) ,
+ rgb (0.7490196078431373, 0.3568627450980392, 0.09019607843137253) ,
+ rgb (0.4, 0.4, 0.4)
+});
+
+
+list_data Blues = list_data(new pen[] {
+ rgb (0.9686274509803922, 0.984313725490196, 1.0) ,
+ rgb (0.8705882352941177, 0.9215686274509803, 0.9686274509803922) ,
+ rgb (0.7764705882352941, 0.8588235294117647, 0.9372549019607843) ,
+ rgb (0.6196078431372549, 0.792156862745098, 0.8823529411764706) ,
+ rgb (0.4196078431372549, 0.6823529411764706, 0.8392156862745098) ,
+ rgb (0.25882352941176473, 0.5725490196078431, 0.7764705882352941) ,
+ rgb (0.12941176470588237, 0.44313725490196076, 0.7098039215686275) ,
+ rgb (0.03137254901960784, 0.3176470588235294, 0.611764705882353) ,
+ rgb (0.03137254901960784, 0.18823529411764706, 0.4196078431372549)
+});
+
+
+list_data BrBG = list_data(new pen[] {
+ rgb (0.32941176470588235, 0.18823529411764706, 0.0196078431372549) ,
+ rgb (0.5490196078431373, 0.3176470588235294, 0.0392156862745098) ,
+ rgb (0.7490196078431373, 0.5058823529411764, 0.17647058823529413) ,
+ rgb (0.8745098039215686, 0.7607843137254902, 0.49019607843137253) ,
+ rgb (0.9647058823529412, 0.9098039215686274, 0.7647058823529411) ,
+ rgb (0.9607843137254902, 0.9607843137254902, 0.9607843137254902) ,
+ rgb (0.7803921568627451, 0.9176470588235294, 0.8980392156862745) ,
+ rgb (0.5019607843137255, 0.803921568627451, 0.7568627450980392) ,
+ rgb (0.20784313725490197, 0.592156862745098, 0.5607843137254902) ,
+ rgb (0.00392156862745098, 0.4, 0.3686274509803922) ,
+ rgb (0.0, 0.23529411764705882, 0.18823529411764706)
+});
+
+
+list_data BuGn = list_data(new pen[] {
+ rgb (0.9686274509803922, 0.9882352941176471, 0.9921568627450981) ,
+ rgb (0.8980392156862745, 0.9607843137254902, 0.9764705882352941) ,
+ rgb (0.8, 0.9254901960784314, 0.9019607843137255) ,
+ rgb (0.6, 0.8470588235294118, 0.788235294117647) ,
+ rgb (0.4, 0.7607843137254902, 0.6431372549019608) ,
+ rgb (0.2549019607843137, 0.6823529411764706, 0.4627450980392157) ,
+ rgb (0.13725490196078433, 0.5450980392156862, 0.27058823529411763) ,
+ rgb (0.0, 0.42745098039215684, 0.17254901960784313) ,
+ rgb (0.0, 0.26666666666666666, 0.10588235294117647)
+});
+
+
+list_data BuPu = list_data(new pen[] {
+ rgb (0.9686274509803922, 0.9882352941176471, 0.9921568627450981) ,
+ rgb (0.8784313725490196, 0.9254901960784314, 0.9568627450980393) ,
+ rgb (0.7490196078431373, 0.8274509803921568, 0.9019607843137255) ,
+ rgb (0.6196078431372549, 0.7372549019607844, 0.8549019607843137) ,
+ rgb (0.5490196078431373, 0.5882352941176471, 0.7764705882352941) ,
+ rgb (0.5490196078431373, 0.4196078431372549, 0.6941176470588235) ,
+ rgb (0.5333333333333333, 0.2549019607843137, 0.615686274509804) ,
+ rgb (0.5058823529411764, 0.05882352941176471, 0.48627450980392156) ,
+ rgb (0.30196078431372547, 0.0, 0.29411764705882354)
+});
+
+
+seg_data CMRmap = seg_data(
+ new triple[] { // red
+ (0.0, 0.0, 0.0) ,
+ (0.125, 0.15, 0.15) ,
+ (0.25, 0.3, 0.3) ,
+ (0.375, 0.6, 0.6) ,
+ (0.5, 1.0, 1.0) ,
+ (0.625, 0.9, 0.9) ,
+ (0.75, 0.9, 0.9) ,
+ (0.875, 0.9, 0.9) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (0.125, 0.15, 0.15) ,
+ (0.25, 0.15, 0.15) ,
+ (0.375, 0.2, 0.2) ,
+ (0.5, 0.25, 0.25) ,
+ (0.625, 0.5, 0.5) ,
+ (0.75, 0.75, 0.75) ,
+ (0.875, 0.9, 0.9) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // blue
+ (0.0, 0.0, 0.0) ,
+ (0.125, 0.5, 0.5) ,
+ (0.25, 0.75, 0.75) ,
+ (0.375, 0.5, 0.5) ,
+ (0.5, 0.15, 0.15) ,
+ (0.625, 0.0, 0.0) ,
+ (0.75, 0.1, 0.1) ,
+ (0.875, 0.5, 0.5) ,
+ (1.0, 1.0, 1.0)
+ }
+);
+
+
+list_data Dark2 = list_data(new pen[] {
+ rgb (0.10588235294117647, 0.6196078431372549, 0.4666666666666667) ,
+ rgb (0.8509803921568627, 0.37254901960784315, 0.00784313725490196) ,
+ rgb (0.4588235294117647, 0.4392156862745098, 0.7019607843137254) ,
+ rgb (0.9058823529411765, 0.1607843137254902, 0.5411764705882353) ,
+ rgb (0.4, 0.6509803921568628, 0.11764705882352941) ,
+ rgb (0.9019607843137255, 0.6705882352941176, 0.00784313725490196) ,
+ rgb (0.6509803921568628, 0.4627450980392157, 0.11372549019607843) ,
+ rgb (0.4, 0.4, 0.4)
+});
+
+
+list_data GnBu = list_data(new pen[] {
+ rgb (0.9686274509803922, 0.9882352941176471, 0.9411764705882353) ,
+ rgb (0.8784313725490196, 0.9529411764705882, 0.8588235294117647) ,
+ rgb (0.8, 0.9215686274509803, 0.7725490196078432) ,
+ rgb (0.6588235294117647, 0.8666666666666667, 0.7098039215686275) ,
+ rgb (0.4823529411764706, 0.8, 0.7686274509803922) ,
+ rgb (0.3058823529411765, 0.7019607843137254, 0.8274509803921568) ,
+ rgb (0.16862745098039217, 0.5490196078431373, 0.7450980392156863) ,
+ rgb (0.03137254901960784, 0.40784313725490196, 0.6745098039215687) ,
+ rgb (0.03137254901960784, 0.25098039215686274, 0.5058823529411764)
+});
+
+
+list_data Greens = list_data(new pen[] {
+ rgb (0.9686274509803922, 0.9882352941176471, 0.9607843137254902) ,
+ rgb (0.8980392156862745, 0.9607843137254902, 0.8784313725490196) ,
+ rgb (0.7803921568627451, 0.9137254901960784, 0.7529411764705882) ,
+ rgb (0.6313725490196078, 0.8509803921568627, 0.6078431372549019) ,
+ rgb (0.4549019607843137, 0.7686274509803922, 0.4627450980392157) ,
+ rgb (0.2549019607843137, 0.6705882352941176, 0.36470588235294116) ,
+ rgb (0.13725490196078433, 0.5450980392156862, 0.27058823529411763) ,
+ rgb (0.0, 0.42745098039215684, 0.17254901960784313) ,
+ rgb (0.0, 0.26666666666666666, 0.10588235294117647)
+});
+
+
+list_data Greys = list_data(new pen[] {
+ rgb (1.0, 1.0, 1.0) ,
+ rgb (0.9411764705882353, 0.9411764705882353, 0.9411764705882353) ,
+ rgb (0.8509803921568627, 0.8509803921568627, 0.8509803921568627) ,
+ rgb (0.7411764705882353, 0.7411764705882353, 0.7411764705882353) ,
+ rgb (0.5882352941176471, 0.5882352941176471, 0.5882352941176471) ,
+ rgb (0.45098039215686275, 0.45098039215686275, 0.45098039215686275) ,
+ rgb (0.3215686274509804, 0.3215686274509804, 0.3215686274509804) ,
+ rgb (0.1450980392156863, 0.1450980392156863, 0.1450980392156863) ,
+ rgb (0.0, 0.0, 0.0)
+});
+
+
+list_data OrRd = list_data(new pen[] {
+ rgb (1.0, 0.9686274509803922, 0.9254901960784314) ,
+ rgb (0.996078431372549, 0.9098039215686274, 0.7843137254901961) ,
+ rgb (0.9921568627450981, 0.8313725490196079, 0.6196078431372549) ,
+ rgb (0.9921568627450981, 0.7333333333333333, 0.5176470588235295) ,
+ rgb (0.9882352941176471, 0.5529411764705883, 0.34901960784313724) ,
+ rgb (0.9372549019607843, 0.396078431372549, 0.2823529411764706) ,
+ rgb (0.8431372549019608, 0.18823529411764706, 0.12156862745098039) ,
+ rgb (0.7019607843137254, 0.0, 0.0) ,
+ rgb (0.4980392156862745, 0.0, 0.0)
+});
+
+
+list_data Oranges = list_data(new pen[] {
+ rgb (1.0, 0.9607843137254902, 0.9215686274509803) ,
+ rgb (0.996078431372549, 0.9019607843137255, 0.807843137254902) ,
+ rgb (0.9921568627450981, 0.8156862745098039, 0.6352941176470588) ,
+ rgb (0.9921568627450981, 0.6823529411764706, 0.4196078431372549) ,
+ rgb (0.9921568627450981, 0.5529411764705883, 0.23529411764705882) ,
+ rgb (0.9450980392156862, 0.4117647058823529, 0.07450980392156863) ,
+ rgb (0.8509803921568627, 0.2823529411764706, 0.00392156862745098) ,
+ rgb (0.6509803921568628, 0.21176470588235294, 0.01176470588235294) ,
+ rgb (0.4980392156862745, 0.15294117647058825, 0.01568627450980392)
+});
+
+
+list_data PRGn = list_data(new pen[] {
+ rgb (0.25098039215686274, 0.0, 0.29411764705882354) ,
+ rgb (0.4627450980392157, 0.16470588235294117, 0.5137254901960784) ,
+ rgb (0.6, 0.4392156862745098, 0.6705882352941176) ,
+ rgb (0.7607843137254902, 0.6470588235294118, 0.8117647058823529) ,
+ rgb (0.9058823529411765, 0.8313725490196079, 0.9098039215686274) ,
+ rgb (0.9686274509803922, 0.9686274509803922, 0.9686274509803922) ,
+ rgb (0.8509803921568627, 0.9411764705882353, 0.8274509803921568) ,
+ rgb (0.6509803921568628, 0.8588235294117647, 0.6274509803921569) ,
+ rgb (0.35294117647058826, 0.6823529411764706, 0.3803921568627451) ,
+ rgb (0.10588235294117647, 0.47058823529411764, 0.21568627450980393) ,
+ rgb (0.0, 0.26666666666666666, 0.10588235294117647)
+});
+
+
+list_data Paired = list_data(new pen[] {
+ rgb (0.6509803921568628, 0.807843137254902, 0.8901960784313725) ,
+ rgb (0.12156862745098039, 0.47058823529411764, 0.7058823529411765) ,
+ rgb (0.6980392156862745, 0.8745098039215686, 0.5411764705882353) ,
+ rgb (0.2, 0.6274509803921569, 0.17254901960784313) ,
+ rgb (0.984313725490196, 0.6039215686274509, 0.6) ,
+ rgb (0.8901960784313725, 0.10196078431372549, 0.10980392156862745) ,
+ rgb (0.9921568627450981, 0.7490196078431373, 0.43529411764705883) ,
+ rgb (1.0, 0.4980392156862745, 0.0) ,
+ rgb (0.792156862745098, 0.6980392156862745, 0.8392156862745098) ,
+ rgb (0.41568627450980394, 0.23921568627450981, 0.6039215686274509) ,
+ rgb (1.0, 1.0, 0.6) ,
+ rgb (0.6941176470588235, 0.34901960784313724, 0.1568627450980392)
+});
+
+
+list_data Pastel1 = list_data(new pen[] {
+ rgb (0.984313725490196, 0.7058823529411765, 0.6823529411764706) ,
+ rgb (0.7019607843137254, 0.803921568627451, 0.8901960784313725) ,
+ rgb (0.8, 0.9215686274509803, 0.7725490196078432) ,
+ rgb (0.8705882352941177, 0.796078431372549, 0.8941176470588236) ,
+ rgb (0.996078431372549, 0.8509803921568627, 0.6509803921568628) ,
+ rgb (1.0, 1.0, 0.8) ,
+ rgb (0.8980392156862745, 0.8470588235294118, 0.7411764705882353) ,
+ rgb (0.9921568627450981, 0.8549019607843137, 0.9254901960784314) ,
+ rgb (0.9490196078431372, 0.9490196078431372, 0.9490196078431372)
+});
+
+
+list_data Pastel2 = list_data(new pen[] {
+ rgb (0.7019607843137254, 0.8862745098039215, 0.803921568627451) ,
+ rgb (0.9921568627450981, 0.803921568627451, 0.6745098039215687) ,
+ rgb (0.796078431372549, 0.8352941176470589, 0.9098039215686274) ,
+ rgb (0.9568627450980393, 0.792156862745098, 0.8941176470588236) ,
+ rgb (0.9019607843137255, 0.9607843137254902, 0.788235294117647) ,
+ rgb (1.0, 0.9490196078431372, 0.6823529411764706) ,
+ rgb (0.9450980392156862, 0.8862745098039215, 0.8) ,
+ rgb (0.8, 0.8, 0.8)
+});
+
+
+list_data PiYG = list_data(new pen[] {
+ rgb (0.5568627450980392, 0.00392156862745098, 0.3215686274509804) ,
+ rgb (0.7725490196078432, 0.10588235294117647, 0.49019607843137253) ,
+ rgb (0.8705882352941177, 0.4666666666666667, 0.6823529411764706) ,
+ rgb (0.9450980392156862, 0.7137254901960784, 0.8549019607843137) ,
+ rgb (0.9921568627450981, 0.8784313725490196, 0.9372549019607843) ,
+ rgb (0.9686274509803922, 0.9686274509803922, 0.9686274509803922) ,
+ rgb (0.9019607843137255, 0.9607843137254902, 0.8156862745098039) ,
+ rgb (0.7215686274509804, 0.8823529411764706, 0.5254901960784314) ,
+ rgb (0.4980392156862745, 0.7372549019607844, 0.2549019607843137) ,
+ rgb (0.30196078431372547, 0.5725490196078431, 0.12941176470588237) ,
+ rgb (0.15294117647058825, 0.39215686274509803, 0.09803921568627451)
+});
+
+
+list_data PuBuGn = list_data(new pen[] {
+ rgb (1.0, 0.9686274509803922, 0.984313725490196) ,
+ rgb (0.9254901960784314, 0.8862745098039215, 0.9411764705882353) ,
+ rgb (0.8156862745098039, 0.8196078431372549, 0.9019607843137255) ,
+ rgb (0.6509803921568628, 0.7411764705882353, 0.8588235294117647) ,
+ rgb (0.403921568627451, 0.6627450980392157, 0.8117647058823529) ,
+ rgb (0.21176470588235294, 0.5647058823529412, 0.7529411764705882) ,
+ rgb (0.00784313725490196, 0.5058823529411764, 0.5411764705882353) ,
+ rgb (0.00392156862745098, 0.4235294117647059, 0.34901960784313724) ,
+ rgb (0.00392156862745098, 0.27450980392156865, 0.21176470588235294)
+});
+
+
+list_data PuBu = list_data(new pen[] {
+ rgb (1.0, 0.9686274509803922, 0.984313725490196) ,
+ rgb (0.9254901960784314, 0.9058823529411765, 0.9490196078431372) ,
+ rgb (0.8156862745098039, 0.8196078431372549, 0.9019607843137255) ,
+ rgb (0.6509803921568628, 0.7411764705882353, 0.8588235294117647) ,
+ rgb (0.4549019607843137, 0.6627450980392157, 0.8117647058823529) ,
+ rgb (0.21176470588235294, 0.5647058823529412, 0.7529411764705882) ,
+ rgb (0.0196078431372549, 0.4392156862745098, 0.6901960784313725) ,
+ rgb (0.01568627450980392, 0.35294117647058826, 0.5529411764705883) ,
+ rgb (0.00784313725490196, 0.2196078431372549, 0.34509803921568627)
+});
+
+
+list_data PuOr = list_data(new pen[] {
+ rgb (0.4980392156862745, 0.23137254901960785, 0.03137254901960784) ,
+ rgb (0.7019607843137254, 0.34509803921568627, 0.02352941176470588) ,
+ rgb (0.8784313725490196, 0.5098039215686274, 0.0784313725490196) ,
+ rgb (0.9921568627450981, 0.7215686274509804, 0.38823529411764707) ,
+ rgb (0.996078431372549, 0.8784313725490196, 0.7137254901960784) ,
+ rgb (0.9686274509803922, 0.9686274509803922, 0.9686274509803922) ,
+ rgb (0.8470588235294118, 0.8549019607843137, 0.9215686274509803) ,
+ rgb (0.6980392156862745, 0.6705882352941176, 0.8235294117647058) ,
+ rgb (0.5019607843137255, 0.45098039215686275, 0.6745098039215687) ,
+ rgb (0.32941176470588235, 0.15294117647058825, 0.5333333333333333) ,
+ rgb (0.17647058823529413, 0.0, 0.29411764705882354)
+});
+
+
+list_data PuRd = list_data(new pen[] {
+ rgb (0.9686274509803922, 0.9568627450980393, 0.9764705882352941) ,
+ rgb (0.9058823529411765, 0.8823529411764706, 0.9372549019607843) ,
+ rgb (0.8313725490196079, 0.7254901960784313, 0.8549019607843137) ,
+ rgb (0.788235294117647, 0.5803921568627451, 0.7803921568627451) ,
+ rgb (0.8745098039215686, 0.396078431372549, 0.6901960784313725) ,
+ rgb (0.9058823529411765, 0.1607843137254902, 0.5411764705882353) ,
+ rgb (0.807843137254902, 0.07058823529411765, 0.33725490196078434) ,
+ rgb (0.596078431372549, 0.0, 0.2627450980392157) ,
+ rgb (0.403921568627451, 0.0, 0.12156862745098039)
+});
+
+
+list_data Purples = list_data(new pen[] {
+ rgb (0.9882352941176471, 0.984313725490196, 0.9921568627450981) ,
+ rgb (0.9372549019607843, 0.9294117647058824, 0.9607843137254902) ,
+ rgb (0.8549019607843137, 0.8549019607843137, 0.9215686274509803) ,
+ rgb (0.7372549019607844, 0.7411764705882353, 0.8627450980392157) ,
+ rgb (0.6196078431372549, 0.6039215686274509, 0.7843137254901961) ,
+ rgb (0.5019607843137255, 0.49019607843137253, 0.7294117647058823) ,
+ rgb (0.41568627450980394, 0.3176470588235294, 0.6392156862745098) ,
+ rgb (0.32941176470588235, 0.15294117647058825, 0.5607843137254902) ,
+ rgb (0.24705882352941178, 0.0, 0.49019607843137253)
+});
+
+
+list_data RdBu = list_data(new pen[] {
+ rgb (0.403921568627451, 0.0, 0.12156862745098039) ,
+ rgb (0.6980392156862745, 0.09411764705882353, 0.16862745098039217) ,
+ rgb (0.8392156862745098, 0.3764705882352941, 0.30196078431372547) ,
+ rgb (0.9568627450980393, 0.6470588235294118, 0.5098039215686274) ,
+ rgb (0.9921568627450981, 0.8588235294117647, 0.7803921568627451) ,
+ rgb (0.9686274509803922, 0.9686274509803922, 0.9686274509803922) ,
+ rgb (0.8196078431372549, 0.8980392156862745, 0.9411764705882353) ,
+ rgb (0.5725490196078431, 0.7725490196078432, 0.8705882352941177) ,
+ rgb (0.2627450980392157, 0.5764705882352941, 0.7647058823529411) ,
+ rgb (0.12941176470588237, 0.4, 0.6745098039215687) ,
+ rgb (0.0196078431372549, 0.18823529411764706, 0.3803921568627451)
+});
+
+
+list_data RdGy = list_data(new pen[] {
+ rgb (0.403921568627451, 0.0, 0.12156862745098039) ,
+ rgb (0.6980392156862745, 0.09411764705882353, 0.16862745098039217) ,
+ rgb (0.8392156862745098, 0.3764705882352941, 0.30196078431372547) ,
+ rgb (0.9568627450980393, 0.6470588235294118, 0.5098039215686274) ,
+ rgb (0.9921568627450981, 0.8588235294117647, 0.7803921568627451) ,
+ rgb (1.0, 1.0, 1.0) ,
+ rgb (0.8784313725490196, 0.8784313725490196, 0.8784313725490196) ,
+ rgb (0.7294117647058823, 0.7294117647058823, 0.7294117647058823) ,
+ rgb (0.5294117647058824, 0.5294117647058824, 0.5294117647058824) ,
+ rgb (0.30196078431372547, 0.30196078431372547, 0.30196078431372547) ,
+ rgb (0.10196078431372549, 0.10196078431372549, 0.10196078431372549)
+});
+
+
+list_data RdPu = list_data(new pen[] {
+ rgb (1.0, 0.9686274509803922, 0.9529411764705882) ,
+ rgb (0.9921568627450981, 0.8784313725490196, 0.8666666666666667) ,
+ rgb (0.9882352941176471, 0.7725490196078432, 0.7529411764705882) ,
+ rgb (0.9803921568627451, 0.6235294117647059, 0.7098039215686275) ,
+ rgb (0.9686274509803922, 0.40784313725490196, 0.6313725490196078) ,
+ rgb (0.8666666666666667, 0.20392156862745098, 0.592156862745098) ,
+ rgb (0.6823529411764706, 0.00392156862745098, 0.49411764705882355) ,
+ rgb (0.47843137254901963, 0.00392156862745098, 0.4666666666666667) ,
+ rgb (0.28627450980392155, 0.0, 0.41568627450980394)
+});
+
+
+list_data RdYlBu = list_data(new pen[] {
+ rgb (0.6470588235294118, 0.0, 0.14901960784313725) ,
+ rgb (0.8431372549019608, 0.18823529411764706, 0.15294117647058825) ,
+ rgb (0.9568627450980393, 0.42745098039215684, 0.2627450980392157) ,
+ rgb (0.9921568627450981, 0.6823529411764706, 0.3803921568627451) ,
+ rgb (0.996078431372549, 0.8784313725490196, 0.5647058823529412) ,
+ rgb (1.0, 1.0, 0.7490196078431373) ,
+ rgb (0.8784313725490196, 0.9529411764705882, 0.9725490196078431) ,
+ rgb (0.6705882352941176, 0.8509803921568627, 0.9137254901960784) ,
+ rgb (0.4549019607843137, 0.6784313725490196, 0.8196078431372549) ,
+ rgb (0.27058823529411763, 0.4588235294117647, 0.7058823529411765) ,
+ rgb (0.19215686274509805, 0.21176470588235294, 0.5843137254901961)
+});
+
+
+list_data RdYlGn = list_data(new pen[] {
+ rgb (0.6470588235294118, 0.0, 0.14901960784313725) ,
+ rgb (0.8431372549019608, 0.18823529411764706, 0.15294117647058825) ,
+ rgb (0.9568627450980393, 0.42745098039215684, 0.2627450980392157) ,
+ rgb (0.9921568627450981, 0.6823529411764706, 0.3803921568627451) ,
+ rgb (0.996078431372549, 0.8784313725490196, 0.5450980392156862) ,
+ rgb (1.0, 1.0, 0.7490196078431373) ,
+ rgb (0.8509803921568627, 0.9372549019607843, 0.5450980392156862) ,
+ rgb (0.6509803921568628, 0.8509803921568627, 0.41568627450980394) ,
+ rgb (0.4, 0.7411764705882353, 0.38823529411764707) ,
+ rgb (0.10196078431372549, 0.596078431372549, 0.3137254901960784) ,
+ rgb (0.0, 0.40784313725490196, 0.21568627450980393)
+});
+
+
+list_data Reds = list_data(new pen[] {
+ rgb (1.0, 0.9607843137254902, 0.9411764705882353) ,
+ rgb (0.996078431372549, 0.8784313725490196, 0.8235294117647058) ,
+ rgb (0.9882352941176471, 0.7333333333333333, 0.6313725490196078) ,
+ rgb (0.9882352941176471, 0.5725490196078431, 0.4470588235294118) ,
+ rgb (0.984313725490196, 0.41568627450980394, 0.2901960784313726) ,
+ rgb (0.9372549019607843, 0.23137254901960785, 0.17254901960784313) ,
+ rgb (0.796078431372549, 0.09411764705882353, 0.11372549019607843) ,
+ rgb (0.6470588235294118, 0.058823529411764705, 0.08235294117647057) ,
+ rgb (0.403921568627451, 0.0, 0.05098039215686274)
+});
+
+
+list_data Set1 = list_data(new pen[] {
+ rgb (0.8941176470588236, 0.10196078431372549, 0.10980392156862745) ,
+ rgb (0.21568627450980393, 0.49411764705882355, 0.7215686274509804) ,
+ rgb (0.30196078431372547, 0.6862745098039216, 0.2901960784313726) ,
+ rgb (0.596078431372549, 0.3058823529411765, 0.6392156862745098) ,
+ rgb (1.0, 0.4980392156862745, 0.0) ,
+ rgb (1.0, 1.0, 0.2) ,
+ rgb (0.6509803921568628, 0.33725490196078434, 0.1568627450980392) ,
+ rgb (0.9686274509803922, 0.5058823529411764, 0.7490196078431373) ,
+ rgb (0.6, 0.6, 0.6)
+});
+
+
+list_data Set2 = list_data(new pen[] {
+ rgb (0.4, 0.7607843137254902, 0.6470588235294118) ,
+ rgb (0.9882352941176471, 0.5529411764705883, 0.3843137254901961) ,
+ rgb (0.5529411764705883, 0.6274509803921569, 0.796078431372549) ,
+ rgb (0.9058823529411765, 0.5411764705882353, 0.7647058823529411) ,
+ rgb (0.6509803921568628, 0.8470588235294118, 0.32941176470588235) ,
+ rgb (1.0, 0.8509803921568627, 0.1843137254901961) ,
+ rgb (0.8980392156862745, 0.7686274509803922, 0.5803921568627451) ,
+ rgb (0.7019607843137254, 0.7019607843137254, 0.7019607843137254)
+});
+
+
+list_data Set3 = list_data(new pen[] {
+ rgb (0.5529411764705883, 0.8274509803921568, 0.7803921568627451) ,
+ rgb (1.0, 1.0, 0.7019607843137254) ,
+ rgb (0.7450980392156863, 0.7294117647058823, 0.8549019607843137) ,
+ rgb (0.984313725490196, 0.5019607843137255, 0.4470588235294118) ,
+ rgb (0.5019607843137255, 0.6941176470588235, 0.8274509803921568) ,
+ rgb (0.9921568627450981, 0.7058823529411765, 0.3843137254901961) ,
+ rgb (0.7019607843137254, 0.8705882352941177, 0.4117647058823529) ,
+ rgb (0.9882352941176471, 0.803921568627451, 0.8980392156862745) ,
+ rgb (0.8509803921568627, 0.8509803921568627, 0.8509803921568627) ,
+ rgb (0.7372549019607844, 0.5019607843137255, 0.7411764705882353) ,
+ rgb (0.8, 0.9215686274509803, 0.7725490196078432) ,
+ rgb (1.0, 0.9294117647058824, 0.43529411764705883)
+});
+
+
+list_data Spectral = list_data(new pen[] {
+ rgb (0.6196078431372549, 0.00392156862745098, 0.25882352941176473) ,
+ rgb (0.8352941176470589, 0.24313725490196078, 0.30980392156862746) ,
+ rgb (0.9568627450980393, 0.42745098039215684, 0.2627450980392157) ,
+ rgb (0.9921568627450981, 0.6823529411764706, 0.3803921568627451) ,
+ rgb (0.996078431372549, 0.8784313725490196, 0.5450980392156862) ,
+ rgb (1.0, 1.0, 0.7490196078431373) ,
+ rgb (0.9019607843137255, 0.9607843137254902, 0.596078431372549) ,
+ rgb (0.6705882352941176, 0.8666666666666667, 0.6431372549019608) ,
+ rgb (0.4, 0.7607843137254902, 0.6470588235294118) ,
+ rgb (0.19607843137254902, 0.5333333333333333, 0.7411764705882353) ,
+ rgb (0.3686274509803922, 0.30980392156862746, 0.6352941176470588)
+});
+
+
+list_data YlGnBu = list_data(new pen[] {
+ rgb (1.0, 1.0, 0.8509803921568627) ,
+ rgb (0.9294117647058824, 0.9725490196078431, 0.6941176470588235) ,
+ rgb (0.7803921568627451, 0.9137254901960784, 0.7058823529411765) ,
+ rgb (0.4980392156862745, 0.803921568627451, 0.7333333333333333) ,
+ rgb (0.2549019607843137, 0.7137254901960784, 0.7686274509803922) ,
+ rgb (0.11372549019607843, 0.5686274509803921, 0.7529411764705882) ,
+ rgb (0.13333333333333333, 0.3686274509803922, 0.6588235294117647) ,
+ rgb (0.1450980392156863, 0.20392156862745098, 0.5803921568627451) ,
+ rgb (0.03137254901960784, 0.11372549019607843, 0.34509803921568627)
+});
+
+
+list_data YlGn = list_data(new pen[] {
+ rgb (1.0, 1.0, 0.8980392156862745) ,
+ rgb (0.9686274509803922, 0.9882352941176471, 0.7254901960784313) ,
+ rgb (0.8509803921568627, 0.9411764705882353, 0.6392156862745098) ,
+ rgb (0.6784313725490196, 0.8666666666666667, 0.5568627450980392) ,
+ rgb (0.47058823529411764, 0.7764705882352941, 0.4745098039215686) ,
+ rgb (0.2549019607843137, 0.6705882352941176, 0.36470588235294116) ,
+ rgb (0.13725490196078433, 0.5176470588235295, 0.2627450980392157) ,
+ rgb (0.0, 0.40784313725490196, 0.21568627450980393) ,
+ rgb (0.0, 0.27058823529411763, 0.1607843137254902)
+});
+
+
+list_data YlOrBr = list_data(new pen[] {
+ rgb (1.0, 1.0, 0.8980392156862745) ,
+ rgb (1.0, 0.9686274509803922, 0.7372549019607844) ,
+ rgb (0.996078431372549, 0.8901960784313725, 0.5686274509803921) ,
+ rgb (0.996078431372549, 0.7686274509803922, 0.30980392156862746) ,
+ rgb (0.996078431372549, 0.6, 0.1607843137254902) ,
+ rgb (0.9254901960784314, 0.4392156862745098, 0.0784313725490196) ,
+ rgb (0.8, 0.2980392156862745, 0.00784313725490196) ,
+ rgb (0.6, 0.20392156862745098, 0.01568627450980392) ,
+ rgb (0.4, 0.1450980392156863, 0.02352941176470588)
+});
+
+
+list_data YlOrRd = list_data(new pen[] {
+ rgb (1.0, 1.0, 0.8) ,
+ rgb (1.0, 0.9294117647058824, 0.6274509803921569) ,
+ rgb (0.996078431372549, 0.8509803921568627, 0.4627450980392157) ,
+ rgb (0.996078431372549, 0.6980392156862745, 0.2980392156862745) ,
+ rgb (0.9921568627450981, 0.5529411764705883, 0.23529411764705882) ,
+ rgb (0.9882352941176471, 0.3058823529411765, 0.16470588235294117) ,
+ rgb (0.8901960784313725, 0.10196078431372549, 0.10980392156862745) ,
+ rgb (0.7411764705882353, 0.0, 0.14901960784313725) ,
+ rgb (0.5019607843137255, 0.0, 0.14901960784313725)
+});
+
+
+seg_data autumn = seg_data(
+ new triple[] { // red
+ (0.0, 1.0, 1.0) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // blue
+ (0.0, 0.0, 0.0) ,
+ (1.0, 0.0, 0.0)
+ }
+);
+
+
+seg_data binary = seg_data(
+ new triple[] { // red
+ (0.0, 1.0, 1.0) ,
+ (1.0, 0.0, 0.0)
+ },
+ new triple[] { // green
+ (0.0, 1.0, 1.0) ,
+ (1.0, 0.0, 0.0)
+ },
+ new triple[] { // blue
+ (0.0, 1.0, 1.0) ,
+ (1.0, 0.0, 0.0)
+ }
+);
+
+
+seg_data bone = seg_data(
+ new triple[] { // red
+ (0.0, 0.0, 0.0) ,
+ (0.746032, 0.652778, 0.652778) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (0.365079, 0.319444, 0.319444) ,
+ (0.746032, 0.777778, 0.777778) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // blue
+ (0.0, 0.0, 0.0) ,
+ (0.365079, 0.444444, 0.444444) ,
+ (1.0, 1.0, 1.0)
+ }
+);
+
+
+list_data brg = list_data(new pen[] {
+ rgb (0.0, 0.0, 1.0) ,
+ rgb (1.0, 0.0, 0.0) ,
+ rgb (0.0, 1.0, 0.0)
+});
+
+
+list_data bwr = list_data(new pen[] {
+ rgb (0.0, 0.0, 1.0) ,
+ rgb (1.0, 1.0, 1.0) ,
+ rgb (1.0, 0.0, 0.0)
+});
+
+
+seg_data cool = seg_data(
+ new triple[] { // red
+ (0.0, 0.0, 0.0) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // green
+ (0.0, 1.0, 1.0) ,
+ (1.0, 0.0, 0.0)
+ },
+ new triple[] { // blue
+ (0.0, 1.0, 1.0) ,
+ (1.0, 1.0, 1.0)
+ }
+);
+
+
+seg_data coolwarm = seg_data(
+ new triple[] { // red
+ (0.0, 0.2298057, 0.2298057) ,
+ (0.03125, 0.26623388, 0.26623388) ,
+ (0.0625, 0.30386891, 0.30386891) ,
+ (0.09375, 0.342804478, 0.342804478) ,
+ (0.125, 0.38301334, 0.38301334) ,
+ (0.15625, 0.424369608, 0.424369608) ,
+ (0.1875, 0.46666708, 0.46666708) ,
+ (0.21875, 0.509635204, 0.509635204) ,
+ (0.25, 0.552953156, 0.552953156) ,
+ (0.28125, 0.596262162, 0.596262162) ,
+ (0.3125, 0.639176211, 0.639176211) ,
+ (0.34375, 0.681291281, 0.681291281) ,
+ (0.375, 0.722193294, 0.722193294) ,
+ (0.40625, 0.761464949, 0.761464949) ,
+ (0.4375, 0.798691636, 0.798691636) ,
+ (0.46875, 0.833466556, 0.833466556) ,
+ (0.5, 0.865395197, 0.865395197) ,
+ (0.53125, 0.897787179, 0.897787179) ,
+ (0.5625, 0.924127593, 0.924127593) ,
+ (0.59375, 0.944468518, 0.944468518) ,
+ (0.625, 0.958852946, 0.958852946) ,
+ (0.65625, 0.96732803, 0.96732803) ,
+ (0.6875, 0.969954137, 0.969954137) ,
+ (0.71875, 0.966811177, 0.966811177) ,
+ (0.75, 0.958003065, 0.958003065) ,
+ (0.78125, 0.943660866, 0.943660866) ,
+ (0.8125, 0.923944917, 0.923944917) ,
+ (0.84375, 0.89904617, 0.89904617) ,
+ (0.875, 0.869186849, 0.869186849) ,
+ (0.90625, 0.834620542, 0.834620542) ,
+ (0.9375, 0.795631745, 0.795631745) ,
+ (0.96875, 0.752534934, 0.752534934) ,
+ (1.0, 0.705673158, 0.705673158)
+ },
+ new triple[] { // green
+ (0.0, 0.298717966, 0.298717966) ,
+ (0.03125, 0.353094838, 0.353094838) ,
+ (0.0625, 0.406535296, 0.406535296) ,
+ (0.09375, 0.458757618, 0.458757618) ,
+ (0.125, 0.50941904, 0.50941904) ,
+ (0.15625, 0.558148092, 0.558148092) ,
+ (0.1875, 0.604562568, 0.604562568) ,
+ (0.21875, 0.648280772, 0.648280772) ,
+ (0.25, 0.688929332, 0.688929332) ,
+ (0.28125, 0.726149107, 0.726149107) ,
+ (0.3125, 0.759599947, 0.759599947) ,
+ (0.34375, 0.788964712, 0.788964712) ,
+ (0.375, 0.813952739, 0.813952739) ,
+ (0.40625, 0.834302879, 0.834302879) ,
+ (0.4375, 0.849786142, 0.849786142) ,
+ (0.46875, 0.860207984, 0.860207984) ,
+ (0.5, 0.86541021, 0.86541021) ,
+ (0.53125, 0.848937047, 0.848937047) ,
+ (0.5625, 0.827384882, 0.827384882) ,
+ (0.59375, 0.800927443, 0.800927443) ,
+ (0.625, 0.769767752, 0.769767752) ,
+ (0.65625, 0.734132809, 0.734132809) ,
+ (0.6875, 0.694266682, 0.694266682) ,
+ (0.71875, 0.650421156, 0.650421156) ,
+ (0.75, 0.602842431, 0.602842431) ,
+ (0.78125, 0.551750968, 0.551750968) ,
+ (0.8125, 0.49730856, 0.49730856) ,
+ (0.84375, 0.439559467, 0.439559467) ,
+ (0.875, 0.378313092, 0.378313092) ,
+ (0.90625, 0.312874446, 0.312874446) ,
+ (0.9375, 0.24128379, 0.24128379) ,
+ (0.96875, 0.157246067, 0.157246067) ,
+ (1.0, 0.01555616, 0.01555616)
+ },
+ new triple[] { // blue
+ (0.0, 0.753683153, 0.753683153) ,
+ (0.03125, 0.801466763, 0.801466763) ,
+ (0.0625, 0.84495867, 0.84495867) ,
+ (0.09375, 0.883725899, 0.883725899) ,
+ (0.125, 0.917387822, 0.917387822) ,
+ (0.15625, 0.945619588, 0.945619588) ,
+ (0.1875, 0.968154911, 0.968154911) ,
+ (0.21875, 0.98478814, 0.98478814) ,
+ (0.25, 0.995375608, 0.995375608) ,
+ (0.28125, 0.999836203, 0.999836203) ,
+ (0.3125, 0.998151185, 0.998151185) ,
+ (0.34375, 0.990363227, 0.990363227) ,
+ (0.375, 0.976574709, 0.976574709) ,
+ (0.40625, 0.956945269, 0.956945269) ,
+ (0.4375, 0.931688648, 0.931688648) ,
+ (0.46875, 0.901068838, 0.901068838) ,
+ (0.5, 0.865395561, 0.865395561) ,
+ (0.53125, 0.820880546, 0.820880546) ,
+ (0.5625, 0.774508472, 0.774508472) ,
+ (0.59375, 0.726736146, 0.726736146) ,
+ (0.625, 0.678007945, 0.678007945) ,
+ (0.65625, 0.628751763, 0.628751763) ,
+ (0.6875, 0.579375448, 0.579375448) ,
+ (0.71875, 0.530263762, 0.530263762) ,
+ (0.75, 0.481775914, 0.481775914) ,
+ (0.78125, 0.434243684, 0.434243684) ,
+ (0.8125, 0.387970225, 0.387970225) ,
+ (0.84375, 0.343229596, 0.343229596) ,
+ (0.875, 0.300267182, 0.300267182) ,
+ (0.90625, 0.259301199, 0.259301199) ,
+ (0.9375, 0.220525627, 0.220525627) ,
+ (0.96875, 0.184115123, 0.184115123) ,
+ (1.0, 0.150232812, 0.150232812)
+ }
+);
+
+
+seg_data copper = seg_data(
+ new triple[] { // red
+ (0.0, 0.0, 0.0) ,
+ (0.809524, 1.0, 1.0) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (1.0, 0.7812, 0.7812)
+ },
+ new triple[] { // blue
+ (0.0, 0.0, 0.0) ,
+ (1.0, 0.4975, 0.4975)
+ }
+);
+
+
+seg_data gist_earth = seg_data(
+ new triple[] { // red
+ (0.0, 0.0, 0.0) ,
+ (0.2824, 0.1882, 0.1882) ,
+ (0.4588, 0.2714, 0.2714) ,
+ (0.549, 0.4719, 0.4719) ,
+ (0.698, 0.7176, 0.7176) ,
+ (0.7882, 0.7553, 0.7553) ,
+ (1.0, 0.9922, 0.9922)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (0.0275, 0.0, 0.0) ,
+ (0.1098, 0.1893, 0.1893) ,
+ (0.1647, 0.3035, 0.3035) ,
+ (0.2078, 0.3841, 0.3841) ,
+ (0.2824, 0.502, 0.502) ,
+ (0.5216, 0.6397, 0.6397) ,
+ (0.698, 0.7171, 0.7171) ,
+ (0.7882, 0.6392, 0.6392) ,
+ (0.7922, 0.6413, 0.6413) ,
+ (0.8, 0.6447, 0.6447) ,
+ (0.8078, 0.6481, 0.6481) ,
+ (0.8157, 0.6549, 0.6549) ,
+ (0.8667, 0.6991, 0.6991) ,
+ (0.8745, 0.7103, 0.7103) ,
+ (0.8824, 0.7216, 0.7216) ,
+ (0.8902, 0.7323, 0.7323) ,
+ (0.898, 0.743, 0.743) ,
+ (0.9412, 0.8275, 0.8275) ,
+ (0.9569, 0.8635, 0.8635) ,
+ (0.9647, 0.8816, 0.8816) ,
+ (0.9961, 0.9733, 0.9733) ,
+ (1.0, 0.9843, 0.9843)
+ },
+ new triple[] { // blue
+ (0.0, 0.0, 0.0) ,
+ (0.0039, 0.1684, 0.1684) ,
+ (0.0078, 0.2212, 0.2212) ,
+ (0.0275, 0.4329, 0.4329) ,
+ (0.0314, 0.4549, 0.4549) ,
+ (0.2824, 0.5004, 0.5004) ,
+ (0.4667, 0.2748, 0.2748) ,
+ (0.5451, 0.3205, 0.3205) ,
+ (0.7843, 0.3961, 0.3961) ,
+ (0.8941, 0.6651, 0.6651) ,
+ (1.0, 0.9843, 0.9843)
+ }
+);
+
+
+seg_data gist_ncar = seg_data(
+ new triple[] { // red
+ (0.0, 0.0, 0.0) ,
+ (0.3098, 0.0, 0.0) ,
+ (0.3725, 0.3993, 0.3993) ,
+ (0.4235, 0.5003, 0.5003) ,
+ (0.5333, 1.0, 1.0) ,
+ (0.7922, 1.0, 1.0) ,
+ (0.8471, 0.6218, 0.6218) ,
+ (0.898, 0.9235, 0.9235) ,
+ (1.0, 0.9961, 0.9961)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (0.051, 0.3722, 0.3722) ,
+ (0.1059, 0.0, 0.0) ,
+ (0.1569, 0.7202, 0.7202) ,
+ (0.1608, 0.7537, 0.7537) ,
+ (0.1647, 0.7752, 0.7752) ,
+ (0.2157, 1.0, 1.0) ,
+ (0.2588, 0.9804, 0.9804) ,
+ (0.2706, 0.9804, 0.9804) ,
+ (0.3176, 1.0, 1.0) ,
+ (0.3686, 0.8081, 0.8081) ,
+ (0.4275, 1.0, 1.0) ,
+ (0.5216, 1.0, 1.0) ,
+ (0.6314, 0.7292, 0.7292) ,
+ (0.6863, 0.2796, 0.2796) ,
+ (0.7451, 0.0, 0.0) ,
+ (0.7922, 0.0, 0.0) ,
+ (0.8431, 0.1753, 0.1753) ,
+ (0.898, 0.5, 0.5) ,
+ (1.0, 0.9725, 0.9725)
+ },
+ new triple[] { // blue
+ (0.0, 0.502, 0.502) ,
+ (0.051, 0.0222, 0.0222) ,
+ (0.1098, 1.0, 1.0) ,
+ (0.2039, 1.0, 1.0) ,
+ (0.2627, 0.6145, 0.6145) ,
+ (0.3216, 0.0, 0.0) ,
+ (0.4157, 0.0, 0.0) ,
+ (0.4745, 0.2342, 0.2342) ,
+ (0.5333, 0.0, 0.0) ,
+ (0.5804, 0.0, 0.0) ,
+ (0.6314, 0.0549, 0.0549) ,
+ (0.6902, 0.0, 0.0) ,
+ (0.7373, 0.0, 0.0) ,
+ (0.7922, 0.9738, 0.9738) ,
+ (0.8, 1.0, 1.0) ,
+ (0.8431, 1.0, 1.0) ,
+ (0.898, 0.9341, 0.9341) ,
+ (1.0, 0.9961, 0.9961)
+ }
+);
+
+
+seg_data gist_stern = seg_data(
+ new triple[] { // red
+ (0.0, 0.0, 0.0) ,
+ (0.0547, 1.0, 1.0) ,
+ (0.25, 0.027, 0.25) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // green
+ (0, 0, 0) ,
+ (1, 1, 1)
+ },
+ new triple[] { // blue
+ (0.0, 0.0, 0.0) ,
+ (0.5, 1.0, 1.0) ,
+ (0.735, 0.0, 0.0) ,
+ (1.0, 1.0, 1.0)
+ }
+);
+
+
+seg_data gray = seg_data(
+ new triple[] { // red
+ (0.0, 0, 0) ,
+ (1.0, 1, 1)
+ },
+ new triple[] { // green
+ (0.0, 0, 0) ,
+ (1.0, 1, 1)
+ },
+ new triple[] { // blue
+ (0.0, 0, 0) ,
+ (1.0, 1, 1)
+ }
+);
+
+
+seg_data hot = seg_data(
+ new triple[] { // red
+ (0.0, 0.0416, 0.0416) ,
+ (0.365079, 1.0, 1.0) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (0.365079, 0.0, 0.0) ,
+ (0.746032, 1.0, 1.0) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // blue
+ (0.0, 0.0, 0.0) ,
+ (0.746032, 0.0, 0.0) ,
+ (1.0, 1.0, 1.0)
+ }
+);
+
+
+seg_data hsv = seg_data(
+ new triple[] { // red
+ (0.0, 1.0, 1.0) ,
+ (0.15873, 1.0, 1.0) ,
+ (0.174603, 0.96875, 0.96875) ,
+ (0.333333, 0.03125, 0.03125) ,
+ (0.349206, 0.0, 0.0) ,
+ (0.666667, 0.0, 0.0) ,
+ (0.68254, 0.03125, 0.03125) ,
+ (0.84127, 0.96875, 0.96875) ,
+ (0.857143, 1.0, 1.0) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (0.15873, 0.9375, 0.9375) ,
+ (0.174603, 1.0, 1.0) ,
+ (0.507937, 1.0, 1.0) ,
+ (0.666667, 0.0625, 0.0625) ,
+ (0.68254, 0.0, 0.0) ,
+ (1.0, 0.0, 0.0)
+ },
+ new triple[] { // blue
+ (0.0, 0.0, 0.0) ,
+ (0.333333, 0.0, 0.0) ,
+ (0.349206, 0.0625, 0.0625) ,
+ (0.507937, 1.0, 1.0) ,
+ (0.84127, 1.0, 1.0) ,
+ (0.857143, 0.9375, 0.9375) ,
+ (1.0, 0.09375, 0.09375)
+ }
+);
+
+
+seg_data jet = seg_data(
+ new triple[] { // red
+ (0.0, 0, 0) ,
+ (0.35, 0, 0) ,
+ (0.66, 1, 1) ,
+ (0.89, 1, 1) ,
+ (1, 0.5, 0.5)
+ },
+ new triple[] { // green
+ (0.0, 0, 0) ,
+ (0.125, 0, 0) ,
+ (0.375, 1, 1) ,
+ (0.64, 1, 1) ,
+ (0.91, 0, 0) ,
+ (1, 0, 0)
+ },
+ new triple[] { // blue
+ (0.0, 0.5, 0.5) ,
+ (0.11, 1, 1) ,
+ (0.34, 1, 1) ,
+ (0.65, 0, 0) ,
+ (1, 0, 0)
+ }
+);
+
+
+seg_data nipy_spectral = seg_data(
+ new triple[] { // red
+ (0.0, 0.0, 0.0) ,
+ (0.05, 0.4667, 0.4667) ,
+ (0.1, 0.5333, 0.5333) ,
+ (0.15, 0.0, 0.0) ,
+ (0.2, 0.0, 0.0) ,
+ (0.25, 0.0, 0.0) ,
+ (0.3, 0.0, 0.0) ,
+ (0.35, 0.0, 0.0) ,
+ (0.4, 0.0, 0.0) ,
+ (0.45, 0.0, 0.0) ,
+ (0.5, 0.0, 0.0) ,
+ (0.55, 0.0, 0.0) ,
+ (0.6, 0.0, 0.0) ,
+ (0.65, 0.7333, 0.7333) ,
+ (0.7, 0.9333, 0.9333) ,
+ (0.75, 1.0, 1.0) ,
+ (0.8, 1.0, 1.0) ,
+ (0.85, 1.0, 1.0) ,
+ (0.9, 0.8667, 0.8667) ,
+ (0.95, 0.8, 0.8) ,
+ (1.0, 0.8, 0.8)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (0.05, 0.0, 0.0) ,
+ (0.1, 0.0, 0.0) ,
+ (0.15, 0.0, 0.0) ,
+ (0.2, 0.0, 0.0) ,
+ (0.25, 0.4667, 0.4667) ,
+ (0.3, 0.6, 0.6) ,
+ (0.35, 0.6667, 0.6667) ,
+ (0.4, 0.6667, 0.6667) ,
+ (0.45, 0.6, 0.6) ,
+ (0.5, 0.7333, 0.7333) ,
+ (0.55, 0.8667, 0.8667) ,
+ (0.6, 1.0, 1.0) ,
+ (0.65, 1.0, 1.0) ,
+ (0.7, 0.9333, 0.9333) ,
+ (0.75, 0.8, 0.8) ,
+ (0.8, 0.6, 0.6) ,
+ (0.85, 0.0, 0.0) ,
+ (0.9, 0.0, 0.0) ,
+ (0.95, 0.0, 0.0) ,
+ (1.0, 0.8, 0.8)
+ },
+ new triple[] { // blue
+ (0.0, 0.0, 0.0) ,
+ (0.05, 0.5333, 0.5333) ,
+ (0.1, 0.6, 0.6) ,
+ (0.15, 0.6667, 0.6667) ,
+ (0.2, 0.8667, 0.8667) ,
+ (0.25, 0.8667, 0.8667) ,
+ (0.3, 0.8667, 0.8667) ,
+ (0.35, 0.6667, 0.6667) ,
+ (0.4, 0.5333, 0.5333) ,
+ (0.45, 0.0, 0.0) ,
+ (0.5, 0.0, 0.0) ,
+ (0.55, 0.0, 0.0) ,
+ (0.6, 0.0, 0.0) ,
+ (0.65, 0.0, 0.0) ,
+ (0.7, 0.0, 0.0) ,
+ (0.75, 0.0, 0.0) ,
+ (0.8, 0.0, 0.0) ,
+ (0.85, 0.0, 0.0) ,
+ (0.9, 0.0, 0.0) ,
+ (0.95, 0.0, 0.0) ,
+ (1.0, 0.8, 0.8)
+ }
+);
+
+
+seg_data pink = seg_data(
+ new triple[] { // red
+ (0.0, 0.1178, 0.1178) ,
+ (0.015873, 0.195857, 0.195857) ,
+ (0.031746, 0.250661, 0.250661) ,
+ (0.047619, 0.295468, 0.295468) ,
+ (0.063492, 0.334324, 0.334324) ,
+ (0.079365, 0.369112, 0.369112) ,
+ (0.095238, 0.400892, 0.400892) ,
+ (0.111111, 0.430331, 0.430331) ,
+ (0.126984, 0.457882, 0.457882) ,
+ (0.142857, 0.483867, 0.483867) ,
+ (0.15873, 0.508525, 0.508525) ,
+ (0.174603, 0.532042, 0.532042) ,
+ (0.190476, 0.554563, 0.554563) ,
+ (0.206349, 0.576204, 0.576204) ,
+ (0.222222, 0.597061, 0.597061) ,
+ (0.238095, 0.617213, 0.617213) ,
+ (0.253968, 0.636729, 0.636729) ,
+ (0.269841, 0.655663, 0.655663) ,
+ (0.285714, 0.674066, 0.674066) ,
+ (0.301587, 0.69198, 0.69198) ,
+ (0.31746, 0.709441, 0.709441) ,
+ (0.333333, 0.726483, 0.726483) ,
+ (0.349206, 0.743134, 0.743134) ,
+ (0.365079, 0.759421, 0.759421) ,
+ (0.380952, 0.766356, 0.766356) ,
+ (0.396825, 0.773229, 0.773229) ,
+ (0.412698, 0.780042, 0.780042) ,
+ (0.428571, 0.786796, 0.786796) ,
+ (0.444444, 0.793492, 0.793492) ,
+ (0.460317, 0.800132, 0.800132) ,
+ (0.47619, 0.806718, 0.806718) ,
+ (0.492063, 0.81325, 0.81325) ,
+ (0.507937, 0.81973, 0.81973) ,
+ (0.52381, 0.82616, 0.82616) ,
+ (0.539683, 0.832539, 0.832539) ,
+ (0.555556, 0.83887, 0.83887) ,
+ (0.571429, 0.845154, 0.845154) ,
+ (0.587302, 0.851392, 0.851392) ,
+ (0.603175, 0.857584, 0.857584) ,
+ (0.619048, 0.863731, 0.863731) ,
+ (0.634921, 0.869835, 0.869835) ,
+ (0.650794, 0.875897, 0.875897) ,
+ (0.666667, 0.881917, 0.881917) ,
+ (0.68254, 0.887896, 0.887896) ,
+ (0.698413, 0.893835, 0.893835) ,
+ (0.714286, 0.899735, 0.899735) ,
+ (0.730159, 0.905597, 0.905597) ,
+ (0.746032, 0.911421, 0.911421) ,
+ (0.761905, 0.917208, 0.917208) ,
+ (0.777778, 0.922958, 0.922958) ,
+ (0.793651, 0.928673, 0.928673) ,
+ (0.809524, 0.934353, 0.934353) ,
+ (0.825397, 0.939999, 0.939999) ,
+ (0.84127, 0.945611, 0.945611) ,
+ (0.857143, 0.95119, 0.95119) ,
+ (0.873016, 0.956736, 0.956736) ,
+ (0.888889, 0.96225, 0.96225) ,
+ (0.904762, 0.967733, 0.967733) ,
+ (0.920635, 0.973185, 0.973185) ,
+ (0.936508, 0.978607, 0.978607) ,
+ (0.952381, 0.983999, 0.983999) ,
+ (0.968254, 0.989361, 0.989361) ,
+ (0.984127, 0.994695, 0.994695) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (0.015873, 0.102869, 0.102869) ,
+ (0.031746, 0.145479, 0.145479) ,
+ (0.047619, 0.178174, 0.178174) ,
+ (0.063492, 0.205738, 0.205738) ,
+ (0.079365, 0.230022, 0.230022) ,
+ (0.095238, 0.251976, 0.251976) ,
+ (0.111111, 0.272166, 0.272166) ,
+ (0.126984, 0.290957, 0.290957) ,
+ (0.142857, 0.308607, 0.308607) ,
+ (0.15873, 0.3253, 0.3253) ,
+ (0.174603, 0.341178, 0.341178) ,
+ (0.190476, 0.356348, 0.356348) ,
+ (0.206349, 0.370899, 0.370899) ,
+ (0.222222, 0.3849, 0.3849) ,
+ (0.238095, 0.39841, 0.39841) ,
+ (0.253968, 0.411476, 0.411476) ,
+ (0.269841, 0.424139, 0.424139) ,
+ (0.285714, 0.436436, 0.436436) ,
+ (0.301587, 0.448395, 0.448395) ,
+ (0.31746, 0.460044, 0.460044) ,
+ (0.333333, 0.471405, 0.471405) ,
+ (0.349206, 0.482498, 0.482498) ,
+ (0.365079, 0.493342, 0.493342) ,
+ (0.380952, 0.517549, 0.517549) ,
+ (0.396825, 0.540674, 0.540674) ,
+ (0.412698, 0.562849, 0.562849) ,
+ (0.428571, 0.584183, 0.584183) ,
+ (0.444444, 0.604765, 0.604765) ,
+ (0.460317, 0.624669, 0.624669) ,
+ (0.47619, 0.643958, 0.643958) ,
+ (0.492063, 0.662687, 0.662687) ,
+ (0.507937, 0.6809, 0.6809) ,
+ (0.52381, 0.698638, 0.698638) ,
+ (0.539683, 0.715937, 0.715937) ,
+ (0.555556, 0.732828, 0.732828) ,
+ (0.571429, 0.749338, 0.749338) ,
+ (0.587302, 0.765493, 0.765493) ,
+ (0.603175, 0.781313, 0.781313) ,
+ (0.619048, 0.796819, 0.796819) ,
+ (0.634921, 0.812029, 0.812029) ,
+ (0.650794, 0.82696, 0.82696) ,
+ (0.666667, 0.841625, 0.841625) ,
+ (0.68254, 0.85604, 0.85604) ,
+ (0.698413, 0.870216, 0.870216) ,
+ (0.714286, 0.884164, 0.884164) ,
+ (0.730159, 0.897896, 0.897896) ,
+ (0.746032, 0.911421, 0.911421) ,
+ (0.761905, 0.917208, 0.917208) ,
+ (0.777778, 0.922958, 0.922958) ,
+ (0.793651, 0.928673, 0.928673) ,
+ (0.809524, 0.934353, 0.934353) ,
+ (0.825397, 0.939999, 0.939999) ,
+ (0.84127, 0.945611, 0.945611) ,
+ (0.857143, 0.95119, 0.95119) ,
+ (0.873016, 0.956736, 0.956736) ,
+ (0.888889, 0.96225, 0.96225) ,
+ (0.904762, 0.967733, 0.967733) ,
+ (0.920635, 0.973185, 0.973185) ,
+ (0.936508, 0.978607, 0.978607) ,
+ (0.952381, 0.983999, 0.983999) ,
+ (0.968254, 0.989361, 0.989361) ,
+ (0.984127, 0.994695, 0.994695) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // blue
+ (0.0, 0.0, 0.0) ,
+ (0.015873, 0.102869, 0.102869) ,
+ (0.031746, 0.145479, 0.145479) ,
+ (0.047619, 0.178174, 0.178174) ,
+ (0.063492, 0.205738, 0.205738) ,
+ (0.079365, 0.230022, 0.230022) ,
+ (0.095238, 0.251976, 0.251976) ,
+ (0.111111, 0.272166, 0.272166) ,
+ (0.126984, 0.290957, 0.290957) ,
+ (0.142857, 0.308607, 0.308607) ,
+ (0.15873, 0.3253, 0.3253) ,
+ (0.174603, 0.341178, 0.341178) ,
+ (0.190476, 0.356348, 0.356348) ,
+ (0.206349, 0.370899, 0.370899) ,
+ (0.222222, 0.3849, 0.3849) ,
+ (0.238095, 0.39841, 0.39841) ,
+ (0.253968, 0.411476, 0.411476) ,
+ (0.269841, 0.424139, 0.424139) ,
+ (0.285714, 0.436436, 0.436436) ,
+ (0.301587, 0.448395, 0.448395) ,
+ (0.31746, 0.460044, 0.460044) ,
+ (0.333333, 0.471405, 0.471405) ,
+ (0.349206, 0.482498, 0.482498) ,
+ (0.365079, 0.493342, 0.493342) ,
+ (0.380952, 0.503953, 0.503953) ,
+ (0.396825, 0.514344, 0.514344) ,
+ (0.412698, 0.524531, 0.524531) ,
+ (0.428571, 0.534522, 0.534522) ,
+ (0.444444, 0.544331, 0.544331) ,
+ (0.460317, 0.553966, 0.553966) ,
+ (0.47619, 0.563436, 0.563436) ,
+ (0.492063, 0.57275, 0.57275) ,
+ (0.507937, 0.581914, 0.581914) ,
+ (0.52381, 0.590937, 0.590937) ,
+ (0.539683, 0.599824, 0.599824) ,
+ (0.555556, 0.608581, 0.608581) ,
+ (0.571429, 0.617213, 0.617213) ,
+ (0.587302, 0.625727, 0.625727) ,
+ (0.603175, 0.634126, 0.634126) ,
+ (0.619048, 0.642416, 0.642416) ,
+ (0.634921, 0.6506, 0.6506) ,
+ (0.650794, 0.658682, 0.658682) ,
+ (0.666667, 0.666667, 0.666667) ,
+ (0.68254, 0.674556, 0.674556) ,
+ (0.698413, 0.682355, 0.682355) ,
+ (0.714286, 0.690066, 0.690066) ,
+ (0.730159, 0.697691, 0.697691) ,
+ (0.746032, 0.705234, 0.705234) ,
+ (0.761905, 0.727166, 0.727166) ,
+ (0.777778, 0.748455, 0.748455) ,
+ (0.793651, 0.769156, 0.769156) ,
+ (0.809524, 0.789314, 0.789314) ,
+ (0.825397, 0.808969, 0.808969) ,
+ (0.84127, 0.828159, 0.828159) ,
+ (0.857143, 0.846913, 0.846913) ,
+ (0.873016, 0.865261, 0.865261) ,
+ (0.888889, 0.883229, 0.883229) ,
+ (0.904762, 0.900837, 0.900837) ,
+ (0.920635, 0.918109, 0.918109) ,
+ (0.936508, 0.935061, 0.935061) ,
+ (0.952381, 0.951711, 0.951711) ,
+ (0.968254, 0.968075, 0.968075) ,
+ (0.984127, 0.984167, 0.984167) ,
+ (1.0, 1.0, 1.0)
+ }
+);
+
+
+list_data seismic = list_data(new pen[] {
+ rgb (0.0, 0.0, 0.3) ,
+ rgb (0.0, 0.0, 1.0) ,
+ rgb (1.0, 1.0, 1.0) ,
+ rgb (1.0, 0.0, 0.0) ,
+ rgb (0.5, 0.0, 0.0)
+});
+
+
+seg_data spring = seg_data(
+ new triple[] { // red
+ (0.0, 1.0, 1.0) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // blue
+ (0.0, 1.0, 1.0) ,
+ (1.0, 0.0, 0.0)
+ }
+);
+
+
+seg_data summer = seg_data(
+ new triple[] { // red
+ (0.0, 0.0, 0.0) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // green
+ (0.0, 0.5, 0.5) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // blue
+ (0.0, 0.4, 0.4) ,
+ (1.0, 0.4, 0.4)
+ }
+);
+
+
+list_data tab10 = list_data(new pen[] {
+ rgb (0.12156862745098039, 0.4666666666666667, 0.7058823529411765) ,
+ rgb (1.0, 0.4980392156862745, 0.054901960784313725) ,
+ rgb (0.17254901960784313, 0.6274509803921569, 0.17254901960784313) ,
+ rgb (0.8392156862745098, 0.15294117647058825, 0.1568627450980392) ,
+ rgb (0.5803921568627451, 0.403921568627451, 0.7411764705882353) ,
+ rgb (0.5490196078431373, 0.33725490196078434, 0.29411764705882354) ,
+ rgb (0.8901960784313725, 0.4666666666666667, 0.7607843137254902) ,
+ rgb (0.4980392156862745, 0.4980392156862745, 0.4980392156862745) ,
+ rgb (0.7372549019607844, 0.7411764705882353, 0.13333333333333333) ,
+ rgb (0.09019607843137255, 0.7450980392156863, 0.8117647058823529)
+});
+
+
+list_data tab20 = list_data(new pen[] {
+ rgb (0.12156862745098039, 0.4666666666666667, 0.7058823529411765) ,
+ rgb (0.6823529411764706, 0.7803921568627451, 0.9098039215686274) ,
+ rgb (1.0, 0.4980392156862745, 0.054901960784313725) ,
+ rgb (1.0, 0.7333333333333333, 0.47058823529411764) ,
+ rgb (0.17254901960784313, 0.6274509803921569, 0.17254901960784313) ,
+ rgb (0.596078431372549, 0.8745098039215686, 0.5411764705882353) ,
+ rgb (0.8392156862745098, 0.15294117647058825, 0.1568627450980392) ,
+ rgb (1.0, 0.596078431372549, 0.5882352941176471) ,
+ rgb (0.5803921568627451, 0.403921568627451, 0.7411764705882353) ,
+ rgb (0.7725490196078432, 0.6901960784313725, 0.8352941176470589) ,
+ rgb (0.5490196078431373, 0.33725490196078434, 0.29411764705882354) ,
+ rgb (0.7686274509803922, 0.611764705882353, 0.5803921568627451) ,
+ rgb (0.8901960784313725, 0.4666666666666667, 0.7607843137254902) ,
+ rgb (0.9686274509803922, 0.7137254901960784, 0.8235294117647058) ,
+ rgb (0.4980392156862745, 0.4980392156862745, 0.4980392156862745) ,
+ rgb (0.7803921568627451, 0.7803921568627451, 0.7803921568627451) ,
+ rgb (0.7372549019607844, 0.7411764705882353, 0.13333333333333333) ,
+ rgb (0.8588235294117647, 0.8588235294117647, 0.5529411764705883) ,
+ rgb (0.09019607843137255, 0.7450980392156863, 0.8117647058823529) ,
+ rgb (0.6196078431372549, 0.8549019607843137, 0.8980392156862745)
+});
+
+
+list_data tab20b = list_data(new pen[] {
+ rgb (0.2235294117647059, 0.23137254901960785, 0.4745098039215686) ,
+ rgb (0.3215686274509804, 0.32941176470588235, 0.6392156862745098) ,
+ rgb (0.4196078431372549, 0.43137254901960786, 0.8117647058823529) ,
+ rgb (0.611764705882353, 0.6196078431372549, 0.8705882352941177) ,
+ rgb (0.38823529411764707, 0.4745098039215686, 0.2235294117647059) ,
+ rgb (0.5490196078431373, 0.6352941176470588, 0.3215686274509804) ,
+ rgb (0.7098039215686275, 0.8117647058823529, 0.4196078431372549) ,
+ rgb (0.807843137254902, 0.8588235294117647, 0.611764705882353) ,
+ rgb (0.5490196078431373, 0.42745098039215684, 0.19215686274509805) ,
+ rgb (0.7411764705882353, 0.6196078431372549, 0.2235294117647059) ,
+ rgb (0.9058823529411765, 0.7294117647058823, 0.3215686274509804) ,
+ rgb (0.9058823529411765, 0.796078431372549, 0.5803921568627451) ,
+ rgb (0.5176470588235295, 0.23529411764705882, 0.2235294117647059) ,
+ rgb (0.6784313725490196, 0.28627450980392155, 0.2901960784313726) ,
+ rgb (0.8392156862745098, 0.3803921568627451, 0.4196078431372549) ,
+ rgb (0.9058823529411765, 0.5882352941176471, 0.611764705882353) ,
+ rgb (0.4823529411764706, 0.2549019607843137, 0.45098039215686275) ,
+ rgb (0.6470588235294118, 0.3176470588235294, 0.5803921568627451) ,
+ rgb (0.807843137254902, 0.42745098039215684, 0.7411764705882353) ,
+ rgb (0.8705882352941177, 0.6196078431372549, 0.8392156862745098)
+});
+
+
+list_data tab20c = list_data(new pen[] {
+ rgb (0.19215686274509805, 0.5098039215686274, 0.7411764705882353) ,
+ rgb (0.4196078431372549, 0.6823529411764706, 0.8392156862745098) ,
+ rgb (0.6196078431372549, 0.792156862745098, 0.8823529411764706) ,
+ rgb (0.7764705882352941, 0.8588235294117647, 0.9372549019607843) ,
+ rgb (0.9019607843137255, 0.3333333333333333, 0.050980392156862744) ,
+ rgb (0.9921568627450981, 0.5529411764705883, 0.23529411764705882) ,
+ rgb (0.9921568627450981, 0.6823529411764706, 0.4196078431372549) ,
+ rgb (0.9921568627450981, 0.8156862745098039, 0.6352941176470588) ,
+ rgb (0.19215686274509805, 0.6392156862745098, 0.32941176470588235) ,
+ rgb (0.4549019607843137, 0.7686274509803922, 0.4627450980392157) ,
+ rgb (0.6313725490196078, 0.8509803921568627, 0.6078431372549019) ,
+ rgb (0.7803921568627451, 0.9137254901960784, 0.7529411764705882) ,
+ rgb (0.4588235294117647, 0.4196078431372549, 0.6941176470588235) ,
+ rgb (0.6196078431372549, 0.6039215686274509, 0.7843137254901961) ,
+ rgb (0.7372549019607844, 0.7411764705882353, 0.8627450980392157) ,
+ rgb (0.8549019607843137, 0.8549019607843137, 0.9215686274509803) ,
+ rgb (0.38823529411764707, 0.38823529411764707, 0.38823529411764707) ,
+ rgb (0.5882352941176471, 0.5882352941176471, 0.5882352941176471) ,
+ rgb (0.7411764705882353, 0.7411764705882353, 0.7411764705882353) ,
+ rgb (0.8509803921568627, 0.8509803921568627, 0.8509803921568627)
+});
+
+
+seg_data winter = seg_data(
+ new triple[] { // red
+ (0.0, 0.0, 0.0) ,
+ (1.0, 0.0, 0.0)
+ },
+ new triple[] { // green
+ (0.0, 0.0, 0.0) ,
+ (1.0, 1.0, 1.0)
+ },
+ new triple[] { // blue
+ (0.0, 1.0, 1.0) ,
+ (1.0, 0.5, 0.5)
+ }
+);
+
+
+seg_data wistia = seg_data(
+ new triple[] { // red
+ (0.0, 0.8941176470588236, 0.8941176470588236) ,
+ (0.25, 1.0, 1.0) ,
+ (0.5, 1.0, 1.0) ,
+ (0.75, 1.0, 1.0) ,
+ (1.0, 0.9882352941176471, 0.9882352941176471)
+ },
+ new triple[] { // green
+ (0.0, 1.0, 1.0) ,
+ (0.25, 0.9098039215686274, 0.9098039215686274) ,
+ (0.5, 0.7411764705882353, 0.7411764705882353) ,
+ (0.75, 0.6274509803921569, 0.6274509803921569) ,
+ (1.0, 0.4980392156862745, 0.4980392156862745)
+ },
+ new triple[] { // blue
+ (0.0, 0.47843137254901963, 0.47843137254901963) ,
+ (0.25, 0.10196078431372549, 0.10196078431372549) ,
+ (0.5, 0.0, 0.0) ,
+ (0.75, 0.0, 0.0) ,
+ (1.0, 0.0, 0.0)
+ }
+);
+
+
+list_data cividis = list_data(new pen[] {
+ rgb (0.0, 0.135112, 0.304751) ,
+ rgb (0.0, 0.138068, 0.311105) ,
+ rgb (0.0, 0.141013, 0.317579) ,
+ rgb (0.0, 0.143951, 0.323982) ,
+ rgb (0.0, 0.146877, 0.330479) ,
+ rgb (0.0, 0.149791, 0.337065) ,
+ rgb (0.0, 0.152673, 0.343704) ,
+ rgb (0.0, 0.155377, 0.3505) ,
+ rgb (0.0, 0.157932, 0.357521) ,
+ rgb (0.0, 0.160495, 0.364534) ,
+ rgb (0.0, 0.163058, 0.371608) ,
+ rgb (0.0, 0.165621, 0.378769) ,
+ rgb (0.0, 0.168204, 0.385902) ,
+ rgb (0.0, 0.1708, 0.3931) ,
+ rgb (0.0, 0.17342, 0.400353) ,
+ rgb (0.0, 0.176082, 0.407577) ,
+ rgb (0.0, 0.178802, 0.414764) ,
+ rgb (0.0, 0.18161, 0.421859) ,
+ rgb (0.0, 0.18455, 0.428802) ,
+ rgb (0.0, 0.186915, 0.435532) ,
+ rgb (0.0, 0.188769, 0.439563) ,
+ rgb (0.0, 0.19095, 0.441085) ,
+ rgb (0.0, 0.193366, 0.441561) ,
+ rgb (0.003602, 0.195911, 0.441564) ,
+ rgb (0.017852, 0.198528, 0.441248) ,
+ rgb (0.03211, 0.201199, 0.440785) ,
+ rgb (0.046205, 0.203903, 0.440196) ,
+ rgb (0.058378, 0.206629, 0.439531) ,
+ rgb (0.068968, 0.209372, 0.438863) ,
+ rgb (0.078624, 0.212122, 0.438105) ,
+ rgb (0.087465, 0.214879, 0.437342) ,
+ rgb (0.095645, 0.217643, 0.436593) ,
+ rgb (0.103401, 0.220406, 0.43579) ,
+ rgb (0.110658, 0.22317, 0.435067) ,
+ rgb (0.117612, 0.225935, 0.434308) ,
+ rgb (0.124291, 0.228697, 0.433547) ,
+ rgb (0.130669, 0.231458, 0.43284) ,
+ rgb (0.13683, 0.234216, 0.432148) ,
+ rgb (0.142852, 0.236972, 0.431404) ,
+ rgb (0.148638, 0.239724, 0.430752) ,
+ rgb (0.154261, 0.242475, 0.43012) ,
+ rgb (0.159733, 0.245221, 0.429528) ,
+ rgb (0.165113, 0.247965, 0.428908) ,
+ rgb (0.170362, 0.250707, 0.428325) ,
+ rgb (0.17549, 0.253444, 0.42779) ,
+ rgb (0.180503, 0.25618, 0.427299) ,
+ rgb (0.185453, 0.258914, 0.426788) ,
+ rgb (0.190303, 0.261644, 0.426329) ,
+ rgb (0.195057, 0.264372, 0.425924) ,
+ rgb (0.199764, 0.267099, 0.425497) ,
+ rgb (0.204385, 0.269823, 0.425126) ,
+ rgb (0.208926, 0.272546, 0.424809) ,
+ rgb (0.213431, 0.275266, 0.42448) ,
+ rgb (0.217863, 0.277985, 0.424206) ,
+ rgb (0.222264, 0.280702, 0.423914) ,
+ rgb (0.226598, 0.283419, 0.423678) ,
+ rgb (0.230871, 0.286134, 0.423498) ,
+ rgb (0.23512, 0.288848, 0.423304) ,
+ rgb (0.239312, 0.291562, 0.423167) ,
+ rgb (0.243485, 0.294274, 0.423014) ,
+ rgb (0.247605, 0.296986, 0.422917) ,
+ rgb (0.251675, 0.299698, 0.422873) ,
+ rgb (0.255731, 0.302409, 0.422814) ,
+ rgb (0.25974, 0.30512, 0.42281) ,
+ rgb (0.263738, 0.307831, 0.422789) ,
+ rgb (0.267693, 0.310542, 0.422821) ,
+ rgb (0.271639, 0.313253, 0.422837) ,
+ rgb (0.275513, 0.315965, 0.422979) ,
+ rgb (0.279411, 0.318677, 0.423031) ,
+ rgb (0.28324, 0.32139, 0.423211) ,
+ rgb (0.287065, 0.324103, 0.423373) ,
+ rgb (0.290884, 0.326816, 0.423517) ,
+ rgb (0.294669, 0.329531, 0.423716) ,
+ rgb (0.298421, 0.332247, 0.423973) ,
+ rgb (0.302169, 0.334963, 0.424213) ,
+ rgb (0.305886, 0.337681, 0.424512) ,
+ rgb (0.309601, 0.340399, 0.42479) ,
+ rgb (0.313287, 0.34312, 0.42512) ,
+ rgb (0.316941, 0.345842, 0.425512) ,
+ rgb (0.320595, 0.348565, 0.425889) ,
+ rgb (0.32425, 0.351289, 0.42625) ,
+ rgb (0.327875, 0.354016, 0.42667) ,
+ rgb (0.331474, 0.356744, 0.427144) ,
+ rgb (0.335073, 0.359474, 0.427605) ,
+ rgb (0.338673, 0.362206, 0.428053) ,
+ rgb (0.342246, 0.364939, 0.428559) ,
+ rgb (0.345793, 0.367676, 0.429127) ,
+ rgb (0.349341, 0.370414, 0.429685) ,
+ rgb (0.352892, 0.373153, 0.430226) ,
+ rgb (0.356418, 0.375896, 0.430823) ,
+ rgb (0.359916, 0.378641, 0.431501) ,
+ rgb (0.363446, 0.381388, 0.432075) ,
+ rgb (0.366923, 0.384139, 0.432796) ,
+ rgb (0.37043, 0.38689, 0.433428) ,
+ rgb (0.373884, 0.389646, 0.434209) ,
+ rgb (0.377371, 0.392404, 0.43489) ,
+ rgb (0.38083, 0.395164, 0.435653) ,
+ rgb (0.384268, 0.397928, 0.436475) ,
+ rgb (0.387705, 0.400694, 0.437305) ,
+ rgb (0.391151, 0.403464, 0.438096) ,
+ rgb (0.394568, 0.406236, 0.438986) ,
+ rgb (0.397991, 0.409011, 0.439848) ,
+ rgb (0.401418, 0.41179, 0.440708) ,
+ rgb (0.40482, 0.414572, 0.441642) ,
+ rgb (0.408226, 0.417357, 0.44257) ,
+ rgb (0.411607, 0.420145, 0.443577) ,
+ rgb (0.414992, 0.422937, 0.444578) ,
+ rgb (0.418383, 0.425733, 0.44556) ,
+ rgb (0.421748, 0.428531, 0.44664) ,
+ rgb (0.42512, 0.431334, 0.447692) ,
+ rgb (0.428462, 0.43414, 0.448864) ,
+ rgb (0.431817, 0.43695, 0.449982) ,
+ rgb (0.435168, 0.439763, 0.451134) ,
+ rgb (0.438504, 0.44258, 0.452341) ,
+ rgb (0.44181, 0.445402, 0.453659) ,
+ rgb (0.445148, 0.448226, 0.454885) ,
+ rgb (0.448447, 0.451053, 0.456264) ,
+ rgb (0.451759, 0.453887, 0.457582) ,
+ rgb (0.455072, 0.456718, 0.458976) ,
+ rgb (0.458366, 0.459552, 0.460457) ,
+ rgb (0.461616, 0.462405, 0.461969) ,
+ rgb (0.464947, 0.465241, 0.463395) ,
+ rgb (0.468254, 0.468083, 0.464908) ,
+ rgb (0.471501, 0.47096, 0.466357) ,
+ rgb (0.474812, 0.473832, 0.467681) ,
+ rgb (0.478186, 0.476699, 0.468845) ,
+ rgb (0.481622, 0.479573, 0.469767) ,
+ rgb (0.485141, 0.482451, 0.470384) ,
+ rgb (0.488697, 0.485318, 0.471008) ,
+ rgb (0.492278, 0.488198, 0.471453) ,
+ rgb (0.495913, 0.491076, 0.471751) ,
+ rgb (0.499552, 0.49396, 0.472032) ,
+ rgb (0.503185, 0.496851, 0.472305) ,
+ rgb (0.506866, 0.499743, 0.472432) ,
+ rgb (0.51054, 0.502643, 0.47255) ,
+ rgb (0.514226, 0.505546, 0.47264) ,
+ rgb (0.51792, 0.508454, 0.472707) ,
+ rgb (0.521643, 0.511367, 0.472639) ,
+ rgb (0.525348, 0.514285, 0.47266) ,
+ rgb (0.529086, 0.517207, 0.472543) ,
+ rgb (0.532829, 0.520135, 0.472401) ,
+ rgb (0.536553, 0.523067, 0.472352) ,
+ rgb (0.540307, 0.526005, 0.472163) ,
+ rgb (0.544069, 0.528948, 0.471947) ,
+ rgb (0.54784, 0.531895, 0.471704) ,
+ rgb (0.551612, 0.534849, 0.471439) ,
+ rgb (0.555393, 0.537807, 0.471147) ,
+ rgb (0.559181, 0.540771, 0.470829) ,
+ rgb (0.562972, 0.543741, 0.470488) ,
+ rgb (0.566802, 0.546715, 0.469988) ,
+ rgb (0.570607, 0.549695, 0.469593) ,
+ rgb (0.574417, 0.552682, 0.469172) ,
+ rgb (0.578236, 0.555673, 0.468724) ,
+ rgb (0.582087, 0.55867, 0.468118) ,
+ rgb (0.585916, 0.561674, 0.467618) ,
+ rgb (0.589753, 0.564682, 0.46709) ,
+ rgb (0.593622, 0.567697, 0.466401) ,
+ rgb (0.597469, 0.570718, 0.465821) ,
+ rgb (0.601354, 0.573743, 0.465074) ,
+ rgb (0.605211, 0.576777, 0.464441) ,
+ rgb (0.609105, 0.579816, 0.463638) ,
+ rgb (0.612977, 0.582861, 0.46295) ,
+ rgb (0.616852, 0.585913, 0.462237) ,
+ rgb (0.620765, 0.58897, 0.461351) ,
+ rgb (0.624654, 0.592034, 0.460583) ,
+ rgb (0.628576, 0.595104, 0.459641) ,
+ rgb (0.632506, 0.59818, 0.458668) ,
+ rgb (0.636412, 0.601264, 0.457818) ,
+ rgb (0.640352, 0.604354, 0.456791) ,
+ rgb (0.64427, 0.60745, 0.455886) ,
+ rgb (0.648222, 0.610553, 0.454801) ,
+ rgb (0.652178, 0.613664, 0.453689) ,
+ rgb (0.656114, 0.61678, 0.452702) ,
+ rgb (0.660082, 0.619904, 0.451534) ,
+ rgb (0.664055, 0.623034, 0.450338) ,
+ rgb (0.668008, 0.626171, 0.44927) ,
+ rgb (0.671991, 0.629316, 0.448018) ,
+ rgb (0.675981, 0.632468, 0.446736) ,
+ rgb (0.679979, 0.635626, 0.445424) ,
+ rgb (0.68395, 0.638793, 0.444251) ,
+ rgb (0.687957, 0.641966, 0.442886) ,
+ rgb (0.691971, 0.645145, 0.441491) ,
+ rgb (0.695985, 0.648334, 0.440072) ,
+ rgb (0.700008, 0.651529, 0.438624) ,
+ rgb (0.704037, 0.654731, 0.437147) ,
+ rgb (0.708067, 0.657942, 0.435647) ,
+ rgb (0.712105, 0.66116, 0.434117) ,
+ rgb (0.716177, 0.664384, 0.432386) ,
+ rgb (0.720222, 0.667618, 0.430805) ,
+ rgb (0.724274, 0.670859, 0.429194) ,
+ rgb (0.728334, 0.674107, 0.427554) ,
+ rgb (0.732422, 0.677364, 0.425717) ,
+ rgb (0.736488, 0.680629, 0.424028) ,
+ rgb (0.740589, 0.6839, 0.422131) ,
+ rgb (0.744664, 0.687181, 0.420393) ,
+ rgb (0.748772, 0.69047, 0.418448) ,
+ rgb (0.752886, 0.693766, 0.416472) ,
+ rgb (0.756975, 0.697071, 0.414659) ,
+ rgb (0.761096, 0.700384, 0.412638) ,
+ rgb (0.765223, 0.703705, 0.410587) ,
+ rgb (0.769353, 0.707035, 0.408516) ,
+ rgb (0.773486, 0.710373, 0.406422) ,
+ rgb (0.777651, 0.713719, 0.404112) ,
+ rgb (0.781795, 0.717074, 0.401966) ,
+ rgb (0.785965, 0.720438, 0.399613) ,
+ rgb (0.790116, 0.72381, 0.397423) ,
+ rgb (0.794298, 0.72719, 0.395016) ,
+ rgb (0.79848, 0.73058, 0.392597) ,
+ rgb (0.802667, 0.733978, 0.390153) ,
+ rgb (0.806859, 0.737385, 0.387684) ,
+ rgb (0.811054, 0.740801, 0.385198) ,
+ rgb (0.815274, 0.744226, 0.382504) ,
+ rgb (0.819499, 0.747659, 0.379785) ,
+ rgb (0.823729, 0.751101, 0.377043) ,
+ rgb (0.827959, 0.754553, 0.374292) ,
+ rgb (0.832192, 0.758014, 0.371529) ,
+ rgb (0.836429, 0.761483, 0.368747) ,
+ rgb (0.840693, 0.764962, 0.365746) ,
+ rgb (0.844957, 0.76845, 0.362741) ,
+ rgb (0.849223, 0.771947, 0.359729) ,
+ rgb (0.853515, 0.775454, 0.3565) ,
+ rgb (0.857809, 0.778969, 0.353259) ,
+ rgb (0.862105, 0.782494, 0.350011) ,
+ rgb (0.866421, 0.786028, 0.346571) ,
+ rgb (0.870717, 0.789572, 0.343333) ,
+ rgb (0.875057, 0.793125, 0.339685) ,
+ rgb (0.879378, 0.796687, 0.336241) ,
+ rgb (0.88372, 0.800258, 0.332599) ,
+ rgb (0.888081, 0.803839, 0.32877) ,
+ rgb (0.89244, 0.80743, 0.324968) ,
+ rgb (0.896818, 0.81103, 0.320982) ,
+ rgb (0.901195, 0.814639, 0.317021) ,
+ rgb (0.905589, 0.818257, 0.312889) ,
+ rgb (0.91, 0.821885, 0.308594) ,
+ rgb (0.914407, 0.825522, 0.304348) ,
+ rgb (0.918828, 0.829168, 0.29996) ,
+ rgb (0.923279, 0.832822, 0.295244) ,
+ rgb (0.927724, 0.836486, 0.290611) ,
+ rgb (0.93218, 0.840159, 0.28588) ,
+ rgb (0.93666, 0.843841, 0.280876) ,
+ rgb (0.941147, 0.84753, 0.275815) ,
+ rgb (0.945654, 0.851228, 0.270532) ,
+ rgb (0.950178, 0.854933, 0.265085) ,
+ rgb (0.954725, 0.858646, 0.259365) ,
+ rgb (0.959284, 0.862365, 0.253563) ,
+ rgb (0.963872, 0.866089, 0.247445) ,
+ rgb (0.968469, 0.869819, 0.24131) ,
+ rgb (0.973114, 0.87355, 0.234677) ,
+ rgb (0.97778, 0.877281, 0.227954) ,
+ rgb (0.982497, 0.881008, 0.220878) ,
+ rgb (0.987293, 0.884718, 0.213336) ,
+ rgb (0.992218, 0.888385, 0.205468) ,
+ rgb (0.994847, 0.892954, 0.203445) ,
+ rgb (0.995249, 0.898384, 0.207561) ,
+ rgb (0.995503, 0.903866, 0.21237) ,
+ rgb (0.995737, 0.909344, 0.217772)
+});
+
+
+list_data inferno = list_data(new pen[] {
+ rgb (0.001462, 0.000466, 0.013866) ,
+ rgb (0.002267, 0.00127, 0.01857) ,
+ rgb (0.003299, 0.002249, 0.024239) ,
+ rgb (0.004547, 0.003392, 0.030909) ,
+ rgb (0.006006, 0.004692, 0.038558) ,
+ rgb (0.007676, 0.006136, 0.046836) ,
+ rgb (0.009561, 0.007713, 0.055143) ,
+ rgb (0.011663, 0.009417, 0.06346) ,
+ rgb (0.013995, 0.011225, 0.071862) ,
+ rgb (0.016561, 0.013136, 0.080282) ,
+ rgb (0.019373, 0.015133, 0.088767) ,
+ rgb (0.022447, 0.017199, 0.097327) ,
+ rgb (0.025793, 0.019331, 0.10593) ,
+ rgb (0.029432, 0.021503, 0.114621) ,
+ rgb (0.033385, 0.023702, 0.123397) ,
+ rgb (0.037668, 0.025921, 0.132232) ,
+ rgb (0.042253, 0.028139, 0.141141) ,
+ rgb (0.046915, 0.030324, 0.150164) ,
+ rgb (0.051644, 0.032474, 0.159254) ,
+ rgb (0.056449, 0.034569, 0.168414) ,
+ rgb (0.06134, 0.03659, 0.177642) ,
+ rgb (0.066331, 0.038504, 0.186962) ,
+ rgb (0.071429, 0.040294, 0.196354) ,
+ rgb (0.076637, 0.041905, 0.205799) ,
+ rgb (0.081962, 0.043328, 0.215289) ,
+ rgb (0.087411, 0.044556, 0.224813) ,
+ rgb (0.09299, 0.045583, 0.234358) ,
+ rgb (0.098702, 0.046402, 0.243904) ,
+ rgb (0.104551, 0.047008, 0.25343) ,
+ rgb (0.110536, 0.047399, 0.262912) ,
+ rgb (0.116656, 0.047574, 0.272321) ,
+ rgb (0.122908, 0.047536, 0.281624) ,
+ rgb (0.129285, 0.047293, 0.290788) ,
+ rgb (0.135778, 0.046856, 0.299776) ,
+ rgb (0.142378, 0.046242, 0.308553) ,
+ rgb (0.149073, 0.045468, 0.317085) ,
+ rgb (0.15585, 0.044559, 0.325338) ,
+ rgb (0.162689, 0.043554, 0.333277) ,
+ rgb (0.169575, 0.042489, 0.340874) ,
+ rgb (0.176493, 0.041402, 0.348111) ,
+ rgb (0.183429, 0.040329, 0.354971) ,
+ rgb (0.190367, 0.039309, 0.361447) ,
+ rgb (0.197297, 0.0384, 0.367535) ,
+ rgb (0.204209, 0.037632, 0.373238) ,
+ rgb (0.211095, 0.03703, 0.378563) ,
+ rgb (0.217949, 0.036615, 0.383522) ,
+ rgb (0.224763, 0.036405, 0.388129) ,
+ rgb (0.231538, 0.036405, 0.3924) ,
+ rgb (0.238273, 0.036621, 0.396353) ,
+ rgb (0.244967, 0.037055, 0.400007) ,
+ rgb (0.25162, 0.037705, 0.403378) ,
+ rgb (0.258234, 0.038571, 0.406485) ,
+ rgb (0.26481, 0.039647, 0.409345) ,
+ rgb (0.271347, 0.040922, 0.411976) ,
+ rgb (0.27785, 0.042353, 0.414392) ,
+ rgb (0.284321, 0.043933, 0.416608) ,
+ rgb (0.290763, 0.045644, 0.418637) ,
+ rgb (0.297178, 0.04747, 0.420491) ,
+ rgb (0.303568, 0.049396, 0.422182) ,
+ rgb (0.309935, 0.051407, 0.423721) ,
+ rgb (0.316282, 0.05349, 0.425116) ,
+ rgb (0.32261, 0.055634, 0.426377) ,
+ rgb (0.328921, 0.057827, 0.427511) ,
+ rgb (0.335217, 0.06006, 0.428524) ,
+ rgb (0.3415, 0.062325, 0.429425) ,
+ rgb (0.347771, 0.064616, 0.430217) ,
+ rgb (0.354032, 0.066925, 0.430906) ,
+ rgb (0.360284, 0.069247, 0.431497) ,
+ rgb (0.366529, 0.071579, 0.431994) ,
+ rgb (0.372768, 0.073915, 0.4324) ,
+ rgb (0.379001, 0.076253, 0.432719) ,
+ rgb (0.385228, 0.078591, 0.432955) ,
+ rgb (0.391453, 0.080927, 0.433109) ,
+ rgb (0.397674, 0.083257, 0.433183) ,
+ rgb (0.403894, 0.08558, 0.433179) ,
+ rgb (0.410113, 0.087896, 0.433098) ,
+ rgb (0.416331, 0.090203, 0.432943) ,
+ rgb (0.422549, 0.092501, 0.432714) ,
+ rgb (0.428768, 0.09479, 0.432412) ,
+ rgb (0.434987, 0.097069, 0.432039) ,
+ rgb (0.441207, 0.099338, 0.431594) ,
+ rgb (0.447428, 0.101597, 0.43108) ,
+ rgb (0.453651, 0.103848, 0.430498) ,
+ rgb (0.459875, 0.106089, 0.429846) ,
+ rgb (0.4661, 0.108322, 0.429125) ,
+ rgb (0.472328, 0.110547, 0.428334) ,
+ rgb (0.478558, 0.112764, 0.427475) ,
+ rgb (0.484789, 0.114974, 0.426548) ,
+ rgb (0.491022, 0.117179, 0.425552) ,
+ rgb (0.497257, 0.119379, 0.424488) ,
+ rgb (0.503493, 0.121575, 0.423356) ,
+ rgb (0.50973, 0.123769, 0.422156) ,
+ rgb (0.515967, 0.12596, 0.420887) ,
+ rgb (0.522206, 0.12815, 0.419549) ,
+ rgb (0.528444, 0.130341, 0.418142) ,
+ rgb (0.534683, 0.132534, 0.416667) ,
+ rgb (0.54092, 0.134729, 0.415123) ,
+ rgb (0.547157, 0.136929, 0.413511) ,
+ rgb (0.553392, 0.139134, 0.411829) ,
+ rgb (0.559624, 0.141346, 0.410078) ,
+ rgb (0.565854, 0.143567, 0.408258) ,
+ rgb (0.572081, 0.145797, 0.406369) ,
+ rgb (0.578304, 0.148039, 0.404411) ,
+ rgb (0.584521, 0.150294, 0.402385) ,
+ rgb (0.590734, 0.152563, 0.40029) ,
+ rgb (0.59694, 0.154848, 0.398125) ,
+ rgb (0.603139, 0.157151, 0.395891) ,
+ rgb (0.60933, 0.159474, 0.393589) ,
+ rgb (0.615513, 0.161817, 0.391219) ,
+ rgb (0.621685, 0.164184, 0.388781) ,
+ rgb (0.627847, 0.166575, 0.386276) ,
+ rgb (0.633998, 0.168992, 0.383704) ,
+ rgb (0.640135, 0.171438, 0.381065) ,
+ rgb (0.64626, 0.173914, 0.378359) ,
+ rgb (0.652369, 0.176421, 0.375586) ,
+ rgb (0.658463, 0.178962, 0.372748) ,
+ rgb (0.66454, 0.181539, 0.369846) ,
+ rgb (0.670599, 0.184153, 0.366879) ,
+ rgb (0.676638, 0.186807, 0.363849) ,
+ rgb (0.682656, 0.189501, 0.360757) ,
+ rgb (0.688653, 0.192239, 0.357603) ,
+ rgb (0.694627, 0.195021, 0.354388) ,
+ rgb (0.700576, 0.197851, 0.351113) ,
+ rgb (0.7065, 0.200728, 0.347777) ,
+ rgb (0.712396, 0.203656, 0.344383) ,
+ rgb (0.718264, 0.206636, 0.340931) ,
+ rgb (0.724103, 0.20967, 0.337424) ,
+ rgb (0.729909, 0.212759, 0.333861) ,
+ rgb (0.735683, 0.215906, 0.330245) ,
+ rgb (0.741423, 0.219112, 0.326576) ,
+ rgb (0.747127, 0.222378, 0.322856) ,
+ rgb (0.752794, 0.225706, 0.319085) ,
+ rgb (0.758422, 0.229097, 0.315266) ,
+ rgb (0.76401, 0.232554, 0.311399) ,
+ rgb (0.769556, 0.236077, 0.307485) ,
+ rgb (0.775059, 0.239667, 0.303526) ,
+ rgb (0.780517, 0.243327, 0.299523) ,
+ rgb (0.785929, 0.247056, 0.295477) ,
+ rgb (0.791293, 0.250856, 0.29139) ,
+ rgb (0.796607, 0.254728, 0.287264) ,
+ rgb (0.801871, 0.258674, 0.283099) ,
+ rgb (0.807082, 0.262692, 0.278898) ,
+ rgb (0.812239, 0.266786, 0.274661) ,
+ rgb (0.817341, 0.270954, 0.27039) ,
+ rgb (0.822386, 0.275197, 0.266085) ,
+ rgb (0.827372, 0.279517, 0.26175) ,
+ rgb (0.832299, 0.283913, 0.257383) ,
+ rgb (0.837165, 0.288385, 0.252988) ,
+ rgb (0.841969, 0.292933, 0.248564) ,
+ rgb (0.846709, 0.297559, 0.244113) ,
+ rgb (0.851384, 0.30226, 0.239636) ,
+ rgb (0.855992, 0.307038, 0.235133) ,
+ rgb (0.860533, 0.311892, 0.230606) ,
+ rgb (0.865006, 0.316822, 0.226055) ,
+ rgb (0.869409, 0.321827, 0.221482) ,
+ rgb (0.873741, 0.326906, 0.216886) ,
+ rgb (0.878001, 0.33206, 0.212268) ,
+ rgb (0.882188, 0.337287, 0.207628) ,
+ rgb (0.886302, 0.342586, 0.202968) ,
+ rgb (0.890341, 0.347957, 0.198286) ,
+ rgb (0.894305, 0.353399, 0.193584) ,
+ rgb (0.898192, 0.358911, 0.18886) ,
+ rgb (0.902003, 0.364492, 0.184116) ,
+ rgb (0.905735, 0.37014, 0.17935) ,
+ rgb (0.90939, 0.375856, 0.174563) ,
+ rgb (0.912966, 0.381636, 0.169755) ,
+ rgb (0.916462, 0.387481, 0.164924) ,
+ rgb (0.919879, 0.393389, 0.16007) ,
+ rgb (0.923215, 0.399359, 0.155193) ,
+ rgb (0.92647, 0.405389, 0.150292) ,
+ rgb (0.929644, 0.411479, 0.145367) ,
+ rgb (0.932737, 0.417627, 0.140417) ,
+ rgb (0.935747, 0.423831, 0.13544) ,
+ rgb (0.938675, 0.430091, 0.130438) ,
+ rgb (0.941521, 0.436405, 0.125409) ,
+ rgb (0.944285, 0.442772, 0.120354) ,
+ rgb (0.946965, 0.449191, 0.115272) ,
+ rgb (0.949562, 0.45566, 0.110164) ,
+ rgb (0.952075, 0.462178, 0.105031) ,
+ rgb (0.954506, 0.468744, 0.099874) ,
+ rgb (0.956852, 0.475356, 0.094695) ,
+ rgb (0.959114, 0.482014, 0.089499) ,
+ rgb (0.961293, 0.488716, 0.084289) ,
+ rgb (0.963387, 0.495462, 0.079073) ,
+ rgb (0.965397, 0.502249, 0.073859) ,
+ rgb (0.967322, 0.509078, 0.068659) ,
+ rgb (0.969163, 0.515946, 0.063488) ,
+ rgb (0.970919, 0.522853, 0.058367) ,
+ rgb (0.97259, 0.529798, 0.053324) ,
+ rgb (0.974176, 0.53678, 0.048392) ,
+ rgb (0.975677, 0.543798, 0.043618) ,
+ rgb (0.977092, 0.55085, 0.03905) ,
+ rgb (0.978422, 0.557937, 0.034931) ,
+ rgb (0.979666, 0.565057, 0.031409) ,
+ rgb (0.980824, 0.572209, 0.028508) ,
+ rgb (0.981895, 0.579392, 0.02625) ,
+ rgb (0.982881, 0.586606, 0.024661) ,
+ rgb (0.983779, 0.593849, 0.02377) ,
+ rgb (0.984591, 0.601122, 0.023606) ,
+ rgb (0.985315, 0.608422, 0.024202) ,
+ rgb (0.985952, 0.61575, 0.025592) ,
+ rgb (0.986502, 0.623105, 0.027814) ,
+ rgb (0.986964, 0.630485, 0.030908) ,
+ rgb (0.987337, 0.63789, 0.034916) ,
+ rgb (0.987622, 0.64532, 0.039886) ,
+ rgb (0.987819, 0.652773, 0.045581) ,
+ rgb (0.987926, 0.66025, 0.05175) ,
+ rgb (0.987945, 0.667748, 0.058329) ,
+ rgb (0.987874, 0.675267, 0.065257) ,
+ rgb (0.987714, 0.682807, 0.072489) ,
+ rgb (0.987464, 0.690366, 0.07999) ,
+ rgb (0.987124, 0.697944, 0.087731) ,
+ rgb (0.986694, 0.70554, 0.095694) ,
+ rgb (0.986175, 0.713153, 0.103863) ,
+ rgb (0.985566, 0.720782, 0.112229) ,
+ rgb (0.984865, 0.728427, 0.120785) ,
+ rgb (0.984075, 0.736087, 0.129527) ,
+ rgb (0.983196, 0.743758, 0.138453) ,
+ rgb (0.982228, 0.751442, 0.147565) ,
+ rgb (0.981173, 0.759135, 0.156863) ,
+ rgb (0.980032, 0.766837, 0.166353) ,
+ rgb (0.978806, 0.774545, 0.176037) ,
+ rgb (0.977497, 0.782258, 0.185923) ,
+ rgb (0.976108, 0.789974, 0.196018) ,
+ rgb (0.974638, 0.797692, 0.206332) ,
+ rgb (0.973088, 0.805409, 0.216877) ,
+ rgb (0.971468, 0.813122, 0.227658) ,
+ rgb (0.969783, 0.820825, 0.238686) ,
+ rgb (0.968041, 0.828515, 0.249972) ,
+ rgb (0.966243, 0.836191, 0.261534) ,
+ rgb (0.964394, 0.843848, 0.273391) ,
+ rgb (0.962517, 0.851476, 0.285546) ,
+ rgb (0.960626, 0.859069, 0.29801) ,
+ rgb (0.95872, 0.866624, 0.31082) ,
+ rgb (0.956834, 0.874129, 0.323974) ,
+ rgb (0.954997, 0.881569, 0.337475) ,
+ rgb (0.953215, 0.888942, 0.351369) ,
+ rgb (0.951546, 0.896226, 0.365627) ,
+ rgb (0.950018, 0.903409, 0.380271) ,
+ rgb (0.948683, 0.910473, 0.395289) ,
+ rgb (0.947594, 0.917399, 0.410665) ,
+ rgb (0.946809, 0.924168, 0.426373) ,
+ rgb (0.946392, 0.930761, 0.442367) ,
+ rgb (0.946403, 0.937159, 0.458592) ,
+ rgb (0.946903, 0.943348, 0.47497) ,
+ rgb (0.947937, 0.949318, 0.491426) ,
+ rgb (0.949545, 0.955063, 0.50786) ,
+ rgb (0.95174, 0.960587, 0.524203) ,
+ rgb (0.954529, 0.965896, 0.540361) ,
+ rgb (0.957896, 0.971003, 0.556275) ,
+ rgb (0.961812, 0.975924, 0.571925) ,
+ rgb (0.966249, 0.980678, 0.587206) ,
+ rgb (0.971162, 0.985282, 0.602154) ,
+ rgb (0.976511, 0.989753, 0.61676) ,
+ rgb (0.982257, 0.994109, 0.631017) ,
+ rgb (0.988362, 0.998364, 0.644924)
+});
+
+
+list_data magma = list_data(new pen[] {
+ rgb (0.001462, 0.000466, 0.013866) ,
+ rgb (0.002258, 0.001295, 0.018331) ,
+ rgb (0.003279, 0.002305, 0.023708) ,
+ rgb (0.004512, 0.00349, 0.029965) ,
+ rgb (0.00595, 0.004843, 0.03713) ,
+ rgb (0.007588, 0.006356, 0.044973) ,
+ rgb (0.009426, 0.008022, 0.052844) ,
+ rgb (0.011465, 0.009828, 0.06075) ,
+ rgb (0.013708, 0.011771, 0.068667) ,
+ rgb (0.016156, 0.01384, 0.076603) ,
+ rgb (0.018815, 0.016026, 0.084584) ,
+ rgb (0.021692, 0.01832, 0.09261) ,
+ rgb (0.024792, 0.020715, 0.100676) ,
+ rgb (0.028123, 0.023201, 0.108787) ,
+ rgb (0.031696, 0.025765, 0.116965) ,
+ rgb (0.03552, 0.028397, 0.125209) ,
+ rgb (0.039608, 0.03109, 0.133515) ,
+ rgb (0.04383, 0.03383, 0.141886) ,
+ rgb (0.048062, 0.036607, 0.150327) ,
+ rgb (0.05232, 0.039407, 0.158841) ,
+ rgb (0.056615, 0.04216, 0.167446) ,
+ rgb (0.060949, 0.044794, 0.176129) ,
+ rgb (0.06533, 0.047318, 0.184892) ,
+ rgb (0.069764, 0.049726, 0.193735) ,
+ rgb (0.074257, 0.052017, 0.20266) ,
+ rgb (0.078815, 0.054184, 0.211667) ,
+ rgb (0.083446, 0.056225, 0.220755) ,
+ rgb (0.088155, 0.058133, 0.229922) ,
+ rgb (0.092949, 0.059904, 0.239164) ,
+ rgb (0.097833, 0.061531, 0.248477) ,
+ rgb (0.102815, 0.06301, 0.257854) ,
+ rgb (0.107899, 0.064335, 0.267289) ,
+ rgb (0.113094, 0.065492, 0.276784) ,
+ rgb (0.118405, 0.066479, 0.286321) ,
+ rgb (0.123833, 0.067295, 0.295879) ,
+ rgb (0.12938, 0.067935, 0.305443) ,
+ rgb (0.135053, 0.068391, 0.315) ,
+ rgb (0.140858, 0.068654, 0.324538) ,
+ rgb (0.146785, 0.068738, 0.334011) ,
+ rgb (0.152839, 0.068637, 0.343404) ,
+ rgb (0.159018, 0.068354, 0.352688) ,
+ rgb (0.165308, 0.067911, 0.361816) ,
+ rgb (0.171713, 0.067305, 0.370771) ,
+ rgb (0.178212, 0.066576, 0.379497) ,
+ rgb (0.184801, 0.065732, 0.387973) ,
+ rgb (0.19146, 0.064818, 0.396152) ,
+ rgb (0.198177, 0.063862, 0.404009) ,
+ rgb (0.204935, 0.062907, 0.411514) ,
+ rgb (0.211718, 0.061992, 0.418647) ,
+ rgb (0.218512, 0.061158, 0.425392) ,
+ rgb (0.225302, 0.060445, 0.431742) ,
+ rgb (0.232077, 0.059889, 0.437695) ,
+ rgb (0.238826, 0.059517, 0.443256) ,
+ rgb (0.245543, 0.059352, 0.448436) ,
+ rgb (0.25222, 0.059415, 0.453248) ,
+ rgb (0.258857, 0.059706, 0.45771) ,
+ rgb (0.265447, 0.060237, 0.46184) ,
+ rgb (0.271994, 0.060994, 0.46566) ,
+ rgb (0.278493, 0.061978, 0.46919) ,
+ rgb (0.284951, 0.063168, 0.472451) ,
+ rgb (0.291366, 0.064553, 0.475462) ,
+ rgb (0.29774, 0.066117, 0.478243) ,
+ rgb (0.304081, 0.067835, 0.480812) ,
+ rgb (0.310382, 0.069702, 0.483186) ,
+ rgb (0.316654, 0.07169, 0.48538) ,
+ rgb (0.322899, 0.073782, 0.487408) ,
+ rgb (0.329114, 0.075972, 0.489287) ,
+ rgb (0.335308, 0.078236, 0.491024) ,
+ rgb (0.341482, 0.080564, 0.492631) ,
+ rgb (0.347636, 0.082946, 0.494121) ,
+ rgb (0.353773, 0.085373, 0.495501) ,
+ rgb (0.359898, 0.087831, 0.496778) ,
+ rgb (0.366012, 0.090314, 0.49796) ,
+ rgb (0.372116, 0.092816, 0.499053) ,
+ rgb (0.378211, 0.095332, 0.500067) ,
+ rgb (0.384299, 0.097855, 0.501002) ,
+ rgb (0.390384, 0.100379, 0.501864) ,
+ rgb (0.396467, 0.102902, 0.502658) ,
+ rgb (0.402548, 0.10542, 0.503386) ,
+ rgb (0.408629, 0.10793, 0.504052) ,
+ rgb (0.414709, 0.110431, 0.504662) ,
+ rgb (0.420791, 0.11292, 0.505215) ,
+ rgb (0.426877, 0.115395, 0.505714) ,
+ rgb (0.432967, 0.117855, 0.50616) ,
+ rgb (0.439062, 0.120298, 0.506555) ,
+ rgb (0.445163, 0.122724, 0.506901) ,
+ rgb (0.451271, 0.125132, 0.507198) ,
+ rgb (0.457386, 0.127522, 0.507448) ,
+ rgb (0.463508, 0.129893, 0.507652) ,
+ rgb (0.46964, 0.132245, 0.507809) ,
+ rgb (0.47578, 0.134577, 0.507921) ,
+ rgb (0.481929, 0.136891, 0.507989) ,
+ rgb (0.488088, 0.139186, 0.508011) ,
+ rgb (0.494258, 0.141462, 0.507988) ,
+ rgb (0.500438, 0.143719, 0.50792) ,
+ rgb (0.506629, 0.145958, 0.507806) ,
+ rgb (0.512831, 0.148179, 0.507648) ,
+ rgb (0.519045, 0.150383, 0.507443) ,
+ rgb (0.52527, 0.152569, 0.507192) ,
+ rgb (0.531507, 0.154739, 0.506895) ,
+ rgb (0.537755, 0.156894, 0.506551) ,
+ rgb (0.544015, 0.159033, 0.506159) ,
+ rgb (0.550287, 0.161158, 0.505719) ,
+ rgb (0.556571, 0.163269, 0.50523) ,
+ rgb (0.562866, 0.165368, 0.504692) ,
+ rgb (0.569172, 0.167454, 0.504105) ,
+ rgb (0.57549, 0.16953, 0.503466) ,
+ rgb (0.581819, 0.171596, 0.502777) ,
+ rgb (0.588158, 0.173652, 0.502035) ,
+ rgb (0.594508, 0.175701, 0.501241) ,
+ rgb (0.600868, 0.177743, 0.500394) ,
+ rgb (0.607238, 0.179779, 0.499492) ,
+ rgb (0.613617, 0.181811, 0.498536) ,
+ rgb (0.620005, 0.18384, 0.497524) ,
+ rgb (0.626401, 0.185867, 0.496456) ,
+ rgb (0.632805, 0.187893, 0.495332) ,
+ rgb (0.639216, 0.189921, 0.49415) ,
+ rgb (0.645633, 0.191952, 0.49291) ,
+ rgb (0.652056, 0.193986, 0.491611) ,
+ rgb (0.658483, 0.196027, 0.490253) ,
+ rgb (0.664915, 0.198075, 0.488836) ,
+ rgb (0.671349, 0.200133, 0.487358) ,
+ rgb (0.677786, 0.202203, 0.485819) ,
+ rgb (0.684224, 0.204286, 0.484219) ,
+ rgb (0.690661, 0.206384, 0.482558) ,
+ rgb (0.697098, 0.208501, 0.480835) ,
+ rgb (0.703532, 0.210638, 0.479049) ,
+ rgb (0.709962, 0.212797, 0.477201) ,
+ rgb (0.716387, 0.214982, 0.47529) ,
+ rgb (0.722805, 0.217194, 0.473316) ,
+ rgb (0.729216, 0.219437, 0.471279) ,
+ rgb (0.735616, 0.221713, 0.46918) ,
+ rgb (0.742004, 0.224025, 0.467018) ,
+ rgb (0.748378, 0.226377, 0.464794) ,
+ rgb (0.754737, 0.228772, 0.462509) ,
+ rgb (0.761077, 0.231214, 0.460162) ,
+ rgb (0.767398, 0.233705, 0.457755) ,
+ rgb (0.773695, 0.236249, 0.455289) ,
+ rgb (0.779968, 0.238851, 0.452765) ,
+ rgb (0.786212, 0.241514, 0.450184) ,
+ rgb (0.792427, 0.244242, 0.447543) ,
+ rgb (0.798608, 0.24704, 0.444848) ,
+ rgb (0.804752, 0.249911, 0.442102) ,
+ rgb (0.810855, 0.252861, 0.439305) ,
+ rgb (0.816914, 0.255895, 0.436461) ,
+ rgb (0.822926, 0.259016, 0.433573) ,
+ rgb (0.828886, 0.262229, 0.430644) ,
+ rgb (0.834791, 0.26554, 0.427671) ,
+ rgb (0.840636, 0.268953, 0.424666) ,
+ rgb (0.846416, 0.272473, 0.421631) ,
+ rgb (0.852126, 0.276106, 0.418573) ,
+ rgb (0.857763, 0.279857, 0.415496) ,
+ rgb (0.86332, 0.283729, 0.412403) ,
+ rgb (0.868793, 0.287728, 0.409303) ,
+ rgb (0.874176, 0.291859, 0.406205) ,
+ rgb (0.879464, 0.296125, 0.403118) ,
+ rgb (0.884651, 0.30053, 0.400047) ,
+ rgb (0.889731, 0.305079, 0.397002) ,
+ rgb (0.8947, 0.309773, 0.393995) ,
+ rgb (0.899552, 0.314616, 0.391037) ,
+ rgb (0.904281, 0.31961, 0.388137) ,
+ rgb (0.908884, 0.324755, 0.385308) ,
+ rgb (0.913354, 0.330052, 0.382563) ,
+ rgb (0.917689, 0.3355, 0.379915) ,
+ rgb (0.921884, 0.341098, 0.377376) ,
+ rgb (0.925937, 0.346844, 0.374959) ,
+ rgb (0.929845, 0.352734, 0.372677) ,
+ rgb (0.933606, 0.358764, 0.370541) ,
+ rgb (0.937221, 0.364929, 0.368567) ,
+ rgb (0.940687, 0.371224, 0.366762) ,
+ rgb (0.944006, 0.377643, 0.365136) ,
+ rgb (0.94718, 0.384178, 0.363701) ,
+ rgb (0.95021, 0.39082, 0.362468) ,
+ rgb (0.953099, 0.397563, 0.361438) ,
+ rgb (0.955849, 0.4044, 0.360619) ,
+ rgb (0.958464, 0.411324, 0.360014) ,
+ rgb (0.960949, 0.418323, 0.35963) ,
+ rgb (0.96331, 0.42539, 0.359469) ,
+ rgb (0.965549, 0.432519, 0.359529) ,
+ rgb (0.967671, 0.439703, 0.35981) ,
+ rgb (0.96968, 0.446936, 0.360311) ,
+ rgb (0.971582, 0.45421, 0.36103) ,
+ rgb (0.973381, 0.46152, 0.361965) ,
+ rgb (0.975082, 0.468861, 0.363111) ,
+ rgb (0.97669, 0.476226, 0.364466) ,
+ rgb (0.97821, 0.483612, 0.366025) ,
+ rgb (0.979645, 0.491014, 0.367783) ,
+ rgb (0.981, 0.498428, 0.369734) ,
+ rgb (0.982279, 0.505851, 0.371874) ,
+ rgb (0.983485, 0.51328, 0.374198) ,
+ rgb (0.984622, 0.520713, 0.376698) ,
+ rgb (0.985693, 0.528148, 0.379371) ,
+ rgb (0.9867, 0.535582, 0.38221) ,
+ rgb (0.987646, 0.543015, 0.38521) ,
+ rgb (0.988533, 0.550446, 0.388365) ,
+ rgb (0.989363, 0.557873, 0.391671) ,
+ rgb (0.990138, 0.565296, 0.395122) ,
+ rgb (0.990871, 0.572706, 0.398714) ,
+ rgb (0.991558, 0.580107, 0.402441) ,
+ rgb (0.992196, 0.587502, 0.406299) ,
+ rgb (0.992785, 0.594891, 0.410283) ,
+ rgb (0.993326, 0.602275, 0.41439) ,
+ rgb (0.993834, 0.609644, 0.418613) ,
+ rgb (0.994309, 0.616999, 0.42295) ,
+ rgb (0.994738, 0.62435, 0.427397) ,
+ rgb (0.995122, 0.631696, 0.431951) ,
+ rgb (0.99548, 0.639027, 0.436607) ,
+ rgb (0.99581, 0.646344, 0.441361) ,
+ rgb (0.996096, 0.653659, 0.446213) ,
+ rgb (0.996341, 0.660969, 0.45116) ,
+ rgb (0.99658, 0.668256, 0.456192) ,
+ rgb (0.996775, 0.675541, 0.461314) ,
+ rgb (0.996925, 0.682828, 0.466526) ,
+ rgb (0.997077, 0.690088, 0.471811) ,
+ rgb (0.997186, 0.697349, 0.477182) ,
+ rgb (0.997254, 0.704611, 0.482635) ,
+ rgb (0.997325, 0.711848, 0.488154) ,
+ rgb (0.997351, 0.719089, 0.493755) ,
+ rgb (0.997351, 0.726324, 0.499428) ,
+ rgb (0.997341, 0.733545, 0.505167) ,
+ rgb (0.997285, 0.740772, 0.510983) ,
+ rgb (0.997228, 0.747981, 0.516859) ,
+ rgb (0.997138, 0.75519, 0.522806) ,
+ rgb (0.997019, 0.762398, 0.528821) ,
+ rgb (0.996898, 0.769591, 0.534892) ,
+ rgb (0.996727, 0.776795, 0.541039) ,
+ rgb (0.996571, 0.783977, 0.547233) ,
+ rgb (0.996369, 0.791167, 0.553499) ,
+ rgb (0.996162, 0.798348, 0.55982) ,
+ rgb (0.995932, 0.805527, 0.566202) ,
+ rgb (0.99568, 0.812706, 0.572645) ,
+ rgb (0.995424, 0.819875, 0.57914) ,
+ rgb (0.995131, 0.827052, 0.585701) ,
+ rgb (0.994851, 0.834213, 0.592307) ,
+ rgb (0.994524, 0.841387, 0.598983) ,
+ rgb (0.994222, 0.84854, 0.605696) ,
+ rgb (0.993866, 0.855711, 0.612482) ,
+ rgb (0.993545, 0.862859, 0.619299) ,
+ rgb (0.99317, 0.870024, 0.626189) ,
+ rgb (0.992831, 0.877168, 0.633109) ,
+ rgb (0.99244, 0.88433, 0.640099) ,
+ rgb (0.992089, 0.89147, 0.647116) ,
+ rgb (0.991688, 0.898627, 0.654202) ,
+ rgb (0.991332, 0.905763, 0.661309) ,
+ rgb (0.99093, 0.912915, 0.668481) ,
+ rgb (0.99057, 0.920049, 0.675675) ,
+ rgb (0.990175, 0.927196, 0.682926) ,
+ rgb (0.989815, 0.934329, 0.690198) ,
+ rgb (0.989434, 0.94147, 0.697519) ,
+ rgb (0.989077, 0.948604, 0.704863) ,
+ rgb (0.988717, 0.955742, 0.712242) ,
+ rgb (0.988367, 0.962878, 0.719649) ,
+ rgb (0.988033, 0.970012, 0.727077) ,
+ rgb (0.987691, 0.977154, 0.734536) ,
+ rgb (0.987387, 0.984288, 0.742002) ,
+ rgb (0.987053, 0.991438, 0.749504)
+});
+
+
+list_data plasma = list_data(new pen[] {
+ rgb (0.050383, 0.029803, 0.527975) ,
+ rgb (0.063536, 0.028426, 0.533124) ,
+ rgb (0.075353, 0.027206, 0.538007) ,
+ rgb (0.086222, 0.026125, 0.542658) ,
+ rgb (0.096379, 0.025165, 0.547103) ,
+ rgb (0.10598, 0.024309, 0.551368) ,
+ rgb (0.115124, 0.023556, 0.555468) ,
+ rgb (0.123903, 0.022878, 0.559423) ,
+ rgb (0.132381, 0.022258, 0.56325) ,
+ rgb (0.140603, 0.021687, 0.566959) ,
+ rgb (0.148607, 0.021154, 0.570562) ,
+ rgb (0.156421, 0.020651, 0.574065) ,
+ rgb (0.16407, 0.020171, 0.577478) ,
+ rgb (0.171574, 0.019706, 0.580806) ,
+ rgb (0.17895, 0.019252, 0.584054) ,
+ rgb (0.186213, 0.018803, 0.587228) ,
+ rgb (0.193374, 0.018354, 0.59033) ,
+ rgb (0.200445, 0.017902, 0.593364) ,
+ rgb (0.207435, 0.017442, 0.596333) ,
+ rgb (0.21435, 0.016973, 0.599239) ,
+ rgb (0.221197, 0.016497, 0.602083) ,
+ rgb (0.227983, 0.016007, 0.604867) ,
+ rgb (0.234715, 0.015502, 0.607592) ,
+ rgb (0.241396, 0.014979, 0.610259) ,
+ rgb (0.248032, 0.014439, 0.612868) ,
+ rgb (0.254627, 0.013882, 0.615419) ,
+ rgb (0.261183, 0.013308, 0.617911) ,
+ rgb (0.267703, 0.012716, 0.620346) ,
+ rgb (0.274191, 0.012109, 0.622722) ,
+ rgb (0.280648, 0.011488, 0.625038) ,
+ rgb (0.287076, 0.010855, 0.627295) ,
+ rgb (0.293478, 0.010213, 0.62949) ,
+ rgb (0.299855, 0.009561, 0.631624) ,
+ rgb (0.30621, 0.008902, 0.633694) ,
+ rgb (0.312543, 0.008239, 0.6357) ,
+ rgb (0.318856, 0.007576, 0.63764) ,
+ rgb (0.32515, 0.006915, 0.639512) ,
+ rgb (0.331426, 0.006261, 0.641316) ,
+ rgb (0.337683, 0.005618, 0.643049) ,
+ rgb (0.343925, 0.004991, 0.64471) ,
+ rgb (0.35015, 0.004382, 0.646298) ,
+ rgb (0.356359, 0.003798, 0.64781) ,
+ rgb (0.362553, 0.003243, 0.649245) ,
+ rgb (0.368733, 0.002724, 0.650601) ,
+ rgb (0.374897, 0.002245, 0.651876) ,
+ rgb (0.381047, 0.001814, 0.653068) ,
+ rgb (0.387183, 0.001434, 0.654177) ,
+ rgb (0.393304, 0.001114, 0.655199) ,
+ rgb (0.399411, 0.000859, 0.656133) ,
+ rgb (0.405503, 0.000678, 0.656977) ,
+ rgb (0.41158, 0.000577, 0.65773) ,
+ rgb (0.417642, 0.000564, 0.65839) ,
+ rgb (0.423689, 0.000646, 0.658956) ,
+ rgb (0.429719, 0.000831, 0.659425) ,
+ rgb (0.435734, 0.001127, 0.659797) ,
+ rgb (0.441732, 0.00154, 0.660069) ,
+ rgb (0.447714, 0.00208, 0.66024) ,
+ rgb (0.453677, 0.002755, 0.66031) ,
+ rgb (0.459623, 0.003574, 0.660277) ,
+ rgb (0.46555, 0.004545, 0.660139) ,
+ rgb (0.471457, 0.005678, 0.659897) ,
+ rgb (0.477344, 0.00698, 0.659549) ,
+ rgb (0.48321, 0.00846, 0.659095) ,
+ rgb (0.489055, 0.010127, 0.658534) ,
+ rgb (0.494877, 0.01199, 0.657865) ,
+ rgb (0.500678, 0.014055, 0.657088) ,
+ rgb (0.506454, 0.016333, 0.656202) ,
+ rgb (0.512206, 0.018833, 0.655209) ,
+ rgb (0.517933, 0.021563, 0.654109) ,
+ rgb (0.523633, 0.024532, 0.652901) ,
+ rgb (0.529306, 0.027747, 0.651586) ,
+ rgb (0.534952, 0.031217, 0.650165) ,
+ rgb (0.54057, 0.03495, 0.64864) ,
+ rgb (0.546157, 0.038954, 0.64701) ,
+ rgb (0.551715, 0.043136, 0.645277) ,
+ rgb (0.557243, 0.047331, 0.643443) ,
+ rgb (0.562738, 0.051545, 0.641509) ,
+ rgb (0.568201, 0.055778, 0.639477) ,
+ rgb (0.573632, 0.060028, 0.637349) ,
+ rgb (0.579029, 0.064296, 0.635126) ,
+ rgb (0.584391, 0.068579, 0.632812) ,
+ rgb (0.589719, 0.072878, 0.630408) ,
+ rgb (0.595011, 0.07719, 0.627917) ,
+ rgb (0.600266, 0.081516, 0.625342) ,
+ rgb (0.605485, 0.085854, 0.622686) ,
+ rgb (0.610667, 0.090204, 0.619951) ,
+ rgb (0.615812, 0.094564, 0.61714) ,
+ rgb (0.620919, 0.098934, 0.614257) ,
+ rgb (0.625987, 0.103312, 0.611305) ,
+ rgb (0.631017, 0.107699, 0.608287) ,
+ rgb (0.636008, 0.112092, 0.605205) ,
+ rgb (0.640959, 0.116492, 0.602065) ,
+ rgb (0.645872, 0.120898, 0.598867) ,
+ rgb (0.650746, 0.125309, 0.595617) ,
+ rgb (0.65558, 0.129725, 0.592317) ,
+ rgb (0.660374, 0.134144, 0.588971) ,
+ rgb (0.665129, 0.138566, 0.585582) ,
+ rgb (0.669845, 0.142992, 0.582154) ,
+ rgb (0.674522, 0.147419, 0.578688) ,
+ rgb (0.67916, 0.151848, 0.575189) ,
+ rgb (0.683758, 0.156278, 0.57166) ,
+ rgb (0.688318, 0.160709, 0.568103) ,
+ rgb (0.69284, 0.165141, 0.564522) ,
+ rgb (0.697324, 0.169573, 0.560919) ,
+ rgb (0.701769, 0.174005, 0.557296) ,
+ rgb (0.706178, 0.178437, 0.553657) ,
+ rgb (0.710549, 0.182868, 0.550004) ,
+ rgb (0.714883, 0.187299, 0.546338) ,
+ rgb (0.719181, 0.191729, 0.542663) ,
+ rgb (0.723444, 0.196158, 0.538981) ,
+ rgb (0.72767, 0.200586, 0.535293) ,
+ rgb (0.731862, 0.205013, 0.531601) ,
+ rgb (0.736019, 0.209439, 0.527908) ,
+ rgb (0.740143, 0.213864, 0.524216) ,
+ rgb (0.744232, 0.218288, 0.520524) ,
+ rgb (0.748289, 0.222711, 0.516834) ,
+ rgb (0.752312, 0.227133, 0.513149) ,
+ rgb (0.756304, 0.231555, 0.509468) ,
+ rgb (0.760264, 0.235976, 0.505794) ,
+ rgb (0.764193, 0.240396, 0.502126) ,
+ rgb (0.76809, 0.244817, 0.498465) ,
+ rgb (0.771958, 0.249237, 0.494813) ,
+ rgb (0.775796, 0.253658, 0.491171) ,
+ rgb (0.779604, 0.258078, 0.487539) ,
+ rgb (0.783383, 0.2625, 0.483918) ,
+ rgb (0.787133, 0.266922, 0.480307) ,
+ rgb (0.790855, 0.271345, 0.476706) ,
+ rgb (0.794549, 0.27577, 0.473117) ,
+ rgb (0.798216, 0.280197, 0.469538) ,
+ rgb (0.801855, 0.284626, 0.465971) ,
+ rgb (0.805467, 0.289057, 0.462415) ,
+ rgb (0.809052, 0.293491, 0.45887) ,
+ rgb (0.812612, 0.297928, 0.455338) ,
+ rgb (0.816144, 0.302368, 0.451816) ,
+ rgb (0.819651, 0.306812, 0.448306) ,
+ rgb (0.823132, 0.311261, 0.444806) ,
+ rgb (0.826588, 0.315714, 0.441316) ,
+ rgb (0.830018, 0.320172, 0.437836) ,
+ rgb (0.833422, 0.324635, 0.434366) ,
+ rgb (0.836801, 0.329105, 0.430905) ,
+ rgb (0.840155, 0.33358, 0.427455) ,
+ rgb (0.843484, 0.338062, 0.424013) ,
+ rgb (0.846788, 0.342551, 0.420579) ,
+ rgb (0.850066, 0.347048, 0.417153) ,
+ rgb (0.853319, 0.351553, 0.413734) ,
+ rgb (0.856547, 0.356066, 0.410322) ,
+ rgb (0.85975, 0.360588, 0.406917) ,
+ rgb (0.862927, 0.365119, 0.403519) ,
+ rgb (0.866078, 0.36966, 0.400126) ,
+ rgb (0.869203, 0.374212, 0.396738) ,
+ rgb (0.872303, 0.378774, 0.393355) ,
+ rgb (0.875376, 0.383347, 0.389976) ,
+ rgb (0.878423, 0.387932, 0.3866) ,
+ rgb (0.881443, 0.392529, 0.383229) ,
+ rgb (0.884436, 0.397139, 0.37986) ,
+ rgb (0.887402, 0.401762, 0.376494) ,
+ rgb (0.89034, 0.406398, 0.37313) ,
+ rgb (0.89325, 0.411048, 0.369768) ,
+ rgb (0.896131, 0.415712, 0.366407) ,
+ rgb (0.898984, 0.420392, 0.363047) ,
+ rgb (0.901807, 0.425087, 0.359688) ,
+ rgb (0.904601, 0.429797, 0.356329) ,
+ rgb (0.907365, 0.434524, 0.35297) ,
+ rgb (0.910098, 0.439268, 0.34961) ,
+ rgb (0.9128, 0.444029, 0.346251) ,
+ rgb (0.915471, 0.448807, 0.34289) ,
+ rgb (0.918109, 0.453603, 0.339529) ,
+ rgb (0.920714, 0.458417, 0.336166) ,
+ rgb (0.923287, 0.463251, 0.332801) ,
+ rgb (0.925825, 0.468103, 0.329435) ,
+ rgb (0.928329, 0.472975, 0.326067) ,
+ rgb (0.930798, 0.477867, 0.322697) ,
+ rgb (0.933232, 0.48278, 0.319325) ,
+ rgb (0.93563, 0.487712, 0.315952) ,
+ rgb (0.93799, 0.492667, 0.312575) ,
+ rgb (0.940313, 0.497642, 0.309197) ,
+ rgb (0.942598, 0.502639, 0.305816) ,
+ rgb (0.944844, 0.507658, 0.302433) ,
+ rgb (0.947051, 0.512699, 0.299049) ,
+ rgb (0.949217, 0.517763, 0.295662) ,
+ rgb (0.951344, 0.52285, 0.292275) ,
+ rgb (0.953428, 0.52796, 0.288883) ,
+ rgb (0.95547, 0.533093, 0.28549) ,
+ rgb (0.957469, 0.53825, 0.282096) ,
+ rgb (0.959424, 0.543431, 0.278701) ,
+ rgb (0.961336, 0.548636, 0.275305) ,
+ rgb (0.963203, 0.553865, 0.271909) ,
+ rgb (0.965024, 0.559118, 0.268513) ,
+ rgb (0.966798, 0.564396, 0.265118) ,
+ rgb (0.968526, 0.5697, 0.261721) ,
+ rgb (0.970205, 0.575028, 0.258325) ,
+ rgb (0.971835, 0.580382, 0.254931) ,
+ rgb (0.973416, 0.585761, 0.25154) ,
+ rgb (0.974947, 0.591165, 0.248151) ,
+ rgb (0.976428, 0.596595, 0.244767) ,
+ rgb (0.977856, 0.602051, 0.241387) ,
+ rgb (0.979233, 0.607532, 0.238013) ,
+ rgb (0.980556, 0.613039, 0.234646) ,
+ rgb (0.981826, 0.618572, 0.231287) ,
+ rgb (0.983041, 0.624131, 0.227937) ,
+ rgb (0.984199, 0.629718, 0.224595) ,
+ rgb (0.985301, 0.63533, 0.221265) ,
+ rgb (0.986345, 0.640969, 0.217948) ,
+ rgb (0.987332, 0.646633, 0.214648) ,
+ rgb (0.98826, 0.652325, 0.211364) ,
+ rgb (0.989128, 0.658043, 0.2081) ,
+ rgb (0.989935, 0.663787, 0.204859) ,
+ rgb (0.990681, 0.669558, 0.201642) ,
+ rgb (0.991365, 0.675355, 0.198453) ,
+ rgb (0.991985, 0.681179, 0.195295) ,
+ rgb (0.992541, 0.68703, 0.19217) ,
+ rgb (0.993032, 0.692907, 0.189084) ,
+ rgb (0.993456, 0.69881, 0.186041) ,
+ rgb (0.993814, 0.704741, 0.183043) ,
+ rgb (0.994103, 0.710698, 0.180097) ,
+ rgb (0.994324, 0.716681, 0.177208) ,
+ rgb (0.994474, 0.722691, 0.174381) ,
+ rgb (0.994553, 0.728728, 0.171622) ,
+ rgb (0.994561, 0.734791, 0.168938) ,
+ rgb (0.994495, 0.74088, 0.166335) ,
+ rgb (0.994355, 0.746995, 0.163821) ,
+ rgb (0.994141, 0.753137, 0.161404) ,
+ rgb (0.993851, 0.759304, 0.159092) ,
+ rgb (0.993482, 0.765499, 0.156891) ,
+ rgb (0.993033, 0.77172, 0.154808) ,
+ rgb (0.992505, 0.777967, 0.152855) ,
+ rgb (0.991897, 0.784239, 0.151042) ,
+ rgb (0.991209, 0.790537, 0.149377) ,
+ rgb (0.990439, 0.796859, 0.14787) ,
+ rgb (0.989587, 0.803205, 0.146529) ,
+ rgb (0.988648, 0.809579, 0.145357) ,
+ rgb (0.987621, 0.815978, 0.144363) ,
+ rgb (0.986509, 0.822401, 0.143557) ,
+ rgb (0.985314, 0.828846, 0.142945) ,
+ rgb (0.984031, 0.835315, 0.142528) ,
+ rgb (0.982653, 0.841812, 0.142303) ,
+ rgb (0.98119, 0.848329, 0.142279) ,
+ rgb (0.979644, 0.854866, 0.142453) ,
+ rgb (0.977995, 0.861432, 0.142808) ,
+ rgb (0.976265, 0.868016, 0.143351) ,
+ rgb (0.974443, 0.874622, 0.144061) ,
+ rgb (0.97253, 0.88125, 0.144923) ,
+ rgb (0.970533, 0.887896, 0.145919) ,
+ rgb (0.968443, 0.894564, 0.147014) ,
+ rgb (0.966271, 0.901249, 0.14818) ,
+ rgb (0.964021, 0.90795, 0.14937) ,
+ rgb (0.961681, 0.914672, 0.15052) ,
+ rgb (0.959276, 0.921407, 0.151566) ,
+ rgb (0.956808, 0.928152, 0.152409) ,
+ rgb (0.954287, 0.934908, 0.152921) ,
+ rgb (0.951726, 0.941671, 0.152925) ,
+ rgb (0.949151, 0.948435, 0.152178) ,
+ rgb (0.946602, 0.95519, 0.150328) ,
+ rgb (0.944152, 0.961916, 0.146861) ,
+ rgb (0.941896, 0.96859, 0.140956) ,
+ rgb (0.940015, 0.975158, 0.131326)
+});
+
+
+list_data twilight = list_data(new pen[] {
+ rgb (0.8857501584075443, 0.8500092494306783, 0.8879736506427196) ,
+ rgb (0.8837852019553906, 0.8507294054031063, 0.8872322209694989) ,
+ rgb (0.8817223105928579, 0.8512759407765347, 0.8863805692551482) ,
+ rgb (0.8795410528270573, 0.8516567540749572, 0.8854143767924102) ,
+ rgb (0.8772488085896548, 0.8518702833887027, 0.8843412038131143) ,
+ rgb (0.8748534750857597, 0.8519152612302319, 0.8831692696761383) ,
+ rgb (0.8723313408512408, 0.8518016547808089, 0.8818970435500162) ,
+ rgb (0.8697047485350982, 0.8515240300479789, 0.8805388339000336) ,
+ rgb (0.8669601550533358, 0.8510896085314068, 0.8790976697717334) ,
+ rgb (0.86408985081464, 0.8505039116750779, 0.8775792578489263) ,
+ rgb (0.8611024543689985, 0.8497675485700126, 0.8759924292343957) ,
+ rgb (0.8579825924567037, 0.8488893481028184, 0.8743403855344628) ,
+ rgb (0.8547259318925698, 0.8478748812467282, 0.8726282980930582) ,
+ rgb (0.8513371457085719, 0.8467273579611647, 0.8708608165735044) ,
+ rgb (0.8478071070257792, 0.8454546229209523, 0.8690403678369444) ,
+ rgb (0.8441261828674842, 0.8440648271103739, 0.8671697332269007) ,
+ rgb (0.8403042080595778, 0.8425605950855084, 0.865250882410458) ,
+ rgb (0.8363403180919118, 0.8409479651895194, 0.8632852800107016) ,
+ rgb (0.8322270571293441, 0.8392349062775448, 0.8612756350042788) ,
+ rgb (0.8279689431601354, 0.837426007513952, 0.8592239945130679) ,
+ rgb (0.8235742968025285, 0.8355248776479544, 0.8571319132851495) ,
+ rgb (0.8190465467793753, 0.8335364929949034, 0.855002062870101) ,
+ rgb (0.8143898212114309, 0.8314655869419785, 0.8528375906214702) ,
+ rgb (0.8095999819094809, 0.8293189667350546, 0.8506444160105037) ,
+ rgb (0.8046916442981458, 0.8270983878056066, 0.8484244929697402) ,
+ rgb (0.79967075421268, 0.8248078181208093, 0.8461821002957853) ,
+ rgb (0.7945430508923111, 0.8224511622630462, 0.8439218478682798) ,
+ rgb (0.7893144556460892, 0.8200321318870201, 0.8416486380471222) ,
+ rgb (0.7839910104276492, 0.8175542640053343, 0.8393674746403673) ,
+ rgb (0.7785789200822759, 0.8150208937874255, 0.8370834463093898) ,
+ rgb (0.7730841659017094, 0.8124352473546601, 0.8348017295057968) ,
+ rgb (0.7675110850441786, 0.8098007598713145, 0.8325281663805967) ,
+ rgb (0.7618690793798029, 0.8071194938764749, 0.830266486168872) ,
+ rgb (0.7561644358438198, 0.8043940873347794, 0.8280213899472) ,
+ rgb (0.750403467654067, 0.8016269900896532, 0.8257973785108242) ,
+ rgb (0.7445924777189017, 0.7988204771958325, 0.8235986758615652) ,
+ rgb (0.7387377170049494, 0.7959766573503101, 0.8214292278043301) ,
+ rgb (0.7328454364552346, 0.7930974646884407, 0.8192926338423038) ,
+ rgb (0.726921775128297, 0.7901846863592763, 0.8171921746672638) ,
+ rgb (0.7209728066553678, 0.7872399592345264, 0.8151307392087926) ,
+ rgb (0.7150040307625213, 0.7842648709158119, 0.8131111655994991) ,
+ rgb (0.709020781345393, 0.7812608871607091, 0.8111359185511793) ,
+ rgb (0.7030297722540817, 0.7782290497335813, 0.8092061884805697) ,
+ rgb (0.6970365443886174, 0.7751705000806606, 0.8073233538006345) ,
+ rgb (0.691046410093091, 0.7720862946067809, 0.8054884169067907) ,
+ rgb (0.6850644615439593, 0.7689774029354699, 0.8037020626717691) ,
+ rgb (0.6790955449988215, 0.765844721313959, 0.8019646617300199) ,
+ rgb (0.6731442255942621, 0.7626890873389048, 0.8002762854580953) ,
+ rgb (0.6672147980375281, 0.7595112803730375, 0.7986367465453776) ,
+ rgb (0.6613112930078745, 0.7563120270871903, 0.7970456043491897) ,
+ rgb (0.6554369232645472, 0.7530920875676843, 0.7955027112903105) ,
+ rgb (0.6495957300425348, 0.7498520122194177, 0.7940067402149911) ,
+ rgb (0.6437910831099849, 0.7465923800833657, 0.7925565320130605) ,
+ rgb (0.6380258682854598, 0.7433137671403319, 0.7911510045957317) ,
+ rgb (0.6323027138710603, 0.740016721601314, 0.7897889276264043) ,
+ rgb (0.6266240202260459, 0.7367017540369944, 0.7884690131633456) ,
+ rgb (0.6209919306481755, 0.733369347989232, 0.7871899462469658) ,
+ rgb (0.6154084641177048, 0.7300199523273969, 0.7859502270675048) ,
+ rgb (0.6098754317609306, 0.7266539875975829, 0.7847483573269471) ,
+ rgb (0.6043943420027486, 0.7232718614323369, 0.7835829559353559) ,
+ rgb (0.5989665814482068, 0.7198739489224673, 0.7824525989934664) ,
+ rgb (0.5935933569683722, 0.7164606049658685, 0.781355882376401) ,
+ rgb (0.588275797805555, 0.7130321464645814, 0.7802914140563652) ,
+ rgb (0.5830148703693241, 0.7095888767699747, 0.7792578182047659) ,
+ rgb (0.5778116438998202, 0.7061310615715398, 0.7782534512102552) ,
+ rgb (0.5726668948158774, 0.7026589535425779, 0.7772770268091199) ,
+ rgb (0.5675811785386197, 0.6991727930264627, 0.776327485342753) ,
+ rgb (0.5625551535721934, 0.6956727838162965, 0.7754035914230984) ,
+ rgb (0.5575894041960517, 0.6921591145825405, 0.7745041337932782) ,
+ rgb (0.5526845058934713, 0.6886319451516638, 0.7736279426902245) ,
+ rgb (0.5478409815301863, 0.6850914221850988, 0.7727738647344087) ,
+ rgb (0.5430593242401823, 0.6815376725306588, 0.7719407969783508) ,
+ rgb (0.5383401557517628, 0.677970811290954, 0.7711273443905772) ,
+ rgb (0.533683891477284, 0.6743909370521273, 0.7703325054879735) ,
+ rgb (0.529090861832473, 0.6707981230280622, 0.7695555229231313) ,
+ rgb (0.5245615147059358, 0.6671924299614223, 0.7687954171423095) ,
+ rgb (0.5200962739223556, 0.6635739143403039, 0.768051194033441) ,
+ rgb (0.5156955988596057, 0.65994260812898, 0.7673219148959617) ,
+ rgb (0.5113599254160193, 0.6562985398183186, 0.7666066378064533) ,
+ rgb (0.5070896957645166, 0.6526417240314645, 0.7659044566083585) ,
+ rgb (0.5028853540415561, 0.6489721673409526, 0.7652144671817491) ,
+ rgb (0.4987473366135607, 0.6452898684900934, 0.7645357873418008) ,
+ rgb (0.4946761847863938, 0.6415948411950443, 0.7638671900213091) ,
+ rgb (0.4906722493856122, 0.6378870485884708, 0.7632081276316384) ,
+ rgb (0.4867359599430568, 0.6341664625110051, 0.7625578008592404) ,
+ rgb (0.4828677867260272, 0.6304330455306234, 0.761915371498953) ,
+ rgb (0.47906816236197386, 0.6266867625186013, 0.7612800037566242) ,
+ rgb (0.47533752394906287, 0.6229275728383581, 0.7606508557181775) ,
+ rgb (0.4716762951887709, 0.6191554324288464, 0.7600270922788305) ,
+ rgb (0.46808490970531597, 0.6153702869579029, 0.7594078989109274) ,
+ rgb (0.4645637671630393, 0.6115720882286415, 0.7587924262302581) ,
+ rgb (0.4611132664702388, 0.607760777169989, 0.7581798643680714) ,
+ rgb (0.45773377230160567, 0.6039363004658646, 0.7575693690185916) ,
+ rgb (0.45442563977552913, 0.6000985950385866, 0.7569601366060649) ,
+ rgb (0.45118918687617743, 0.5962476205135354, 0.7563512064324664) ,
+ rgb (0.4480247093358917, 0.5923833145214658, 0.7557417647410792) ,
+ rgb (0.4449324685421538, 0.5885055998308617, 0.7551311041857901) ,
+ rgb (0.441912717666964, 0.5846144110017557, 0.7545183888441067) ,
+ rgb (0.43896563958048396, 0.5807096924109849, 0.7539027620828594) ,
+ rgb (0.4360913895835637, 0.5767913799818608, 0.7532834105961016) ,
+ rgb (0.43329008867358393, 0.5728594162560667, 0.7526594653256667) ,
+ rgb (0.4305617907305757, 0.5689137457245718, 0.752030080993127) ,
+ rgb (0.42790652284925834, 0.5649543060909209, 0.7513944352191484) ,
+ rgb (0.42532423665011354, 0.560981049599503, 0.7507516498900512) ,
+ rgb (0.4228148567577266, 0.5569939212699658, 0.7501008698822764) ,
+ rgb (0.42037822361396326, 0.5529928715810817, 0.7494412559451894) ,
+ rgb (0.4180141407923363, 0.5489778542188889, 0.7487719316700112) ,
+ rgb (0.4157223260454232, 0.544948827153504, 0.7480920445900052) ,
+ rgb (0.4135024574331473, 0.5409057477109848, 0.7474007329754309) ,
+ rgb (0.4113541469730457, 0.5368485776500593, 0.7466971285506578) ,
+ rgb (0.4092768899914751, 0.5327773017713032, 0.7459803063570782) ,
+ rgb (0.4072701869421907, 0.5286918801105741, 0.7452494263758127) ,
+ rgb (0.4053334378930318, 0.5245922817498312, 0.7445036583670813) ,
+ rgb (0.40346600333905397, 0.5204784765384003, 0.7437421522356709) ,
+ rgb (0.40166714010896104, 0.5163504496968876, 0.7429640345324835) ,
+ rgb (0.39993606933454834, 0.5122081814321852, 0.7421684457131799) ,
+ rgb (0.3982719152586337, 0.5080516653927614, 0.7413545091809972) ,
+ rgb (0.3966737490566561, 0.5038808905384797, 0.7405213858051674) ,
+ rgb (0.3951405880820763, 0.4996958532637776, 0.7396682021171571) ,
+ rgb (0.39367135736822567, 0.4954965577745118, 0.738794102296364) ,
+ rgb (0.39226494876209317, 0.4912830033289926, 0.7378982478447508) ,
+ rgb (0.390920175719949, 0.4870552025122304, 0.7369797713388125) ,
+ rgb (0.38963580160340855, 0.48281316715123496, 0.7360378254693274) ,
+ rgb (0.3884105330084243, 0.47855691131792805, 0.7350715764115726) ,
+ rgb (0.3872430145933025, 0.4742864593363539, 0.7340801678785439) ,
+ rgb (0.386131841788921, 0.4700018340988123, 0.7330627749243106) ,
+ rgb (0.3850755679365139, 0.46570306719930193, 0.732018540336905) ,
+ rgb (0.38407269378943537, 0.46139018782416635, 0.7309466543290268) ,
+ rgb (0.3831216808440275, 0.457063235814072, 0.7298462679135326) ,
+ rgb (0.38222094988570376, 0.45272225034283325, 0.7287165614400378) ,
+ rgb (0.3813688793045416, 0.4483672766927786, 0.7275567131714135) ,
+ rgb (0.3805638069656562, 0.4439983720863372, 0.7263658704513531) ,
+ rgb (0.3798040374484875, 0.4396155882122263, 0.7251432377876109) ,
+ rgb (0.3790878928311076, 0.43521897612544935, 0.7238879869132313) ,
+ rgb (0.378413635091359, 0.43080859411413064, 0.7225993199306104) ,
+ rgb (0.3777794975351373, 0.4263845142616835, 0.7212763999353023) ,
+ rgb (0.3771837184425123, 0.4219468022345483, 0.7199184152447577) ,
+ rgb (0.37662448930806297, 0.41749553747893614, 0.7185245473617611) ,
+ rgb (0.37610001286385814, 0.4130307995247706, 0.7170939691992023) ,
+ rgb (0.375608469194424, 0.40855267638072096, 0.7156258509158755) ,
+ rgb (0.37514802505380473, 0.4040612609993941, 0.7141193695725726) ,
+ rgb (0.3747168601930223, 0.3995566498711684, 0.7125736851650046) ,
+ rgb (0.3743131319931234, 0.3950389482828331, 0.7109879652237746) ,
+ rgb (0.3739349933047578, 0.3905082752937583, 0.7093613429347845) ,
+ rgb (0.3735806215098284, 0.3859647438605754, 0.7076929760731058) ,
+ rgb (0.37324816143326384, 0.38140848555753937, 0.7059820097480604) ,
+ rgb (0.3729357864666503, 0.3768396383521984, 0.7042275578058994) ,
+ rgb (0.37264166757849604, 0.3722583500483685, 0.7024287314570723) ,
+ rgb (0.37236397858465387, 0.36766477862108266, 0.7005846349652077) ,
+ rgb (0.3721008970244382, 0.3630590973698238, 0.6986943461507372) ,
+ rgb (0.3718506155898596, 0.3584414828587522, 0.6967569581025654) ,
+ rgb (0.3716113323440048, 0.3538121372967869, 0.6947714991938089) ,
+ rgb (0.37138124223736607, 0.34917126878479027, 0.6927370347192883) ,
+ rgb (0.37115856636209105, 0.3445191141023017, 0.6906525358646499) ,
+ rgb (0.3709415155133733, 0.33985591488818123, 0.6885170337950512) ,
+ rgb (0.3707283327942267, 0.33518193808489577, 0.6863294816960677) ,
+ rgb (0.37051738634484427, 0.3304974124430785, 0.6840888878885721) ,
+ rgb (0.37030682071842685, 0.32580269697872455, 0.6817941168448668) ,
+ rgb (0.37009487130772695, 0.3210981375964933, 0.6794440539905685) ,
+ rgb (0.3698798032902536, 0.31638410101153364, 0.6770375543809057) ,
+ rgb (0.36965987626565955, 0.3116609876295197, 0.6745734474341955) ,
+ rgb (0.3694333459127623, 0.3069292355186234, 0.6720505284912062) ,
+ rgb (0.36919847837592484, 0.3021893217650707, 0.6694675433161452) ,
+ rgb (0.3689535530659678, 0.29744175492366276, 0.6668232208982426) ,
+ rgb (0.3686968223189527, 0.292687098561501, 0.6641162529823691) ,
+ rgb (0.36842655638020444, 0.2879259643777846, 0.661345269109446) ,
+ rgb (0.3681410147989972, 0.2831590122118299, 0.6585088880697231) ,
+ rgb (0.3678384369653108, 0.2783869718129776, 0.655605668384537) ,
+ rgb (0.36751707094367697, 0.2736106331709098, 0.6526341171161864) ,
+ rgb (0.36717513650699446, 0.26883085667326956, 0.6495927229789225) ,
+ rgb (0.3668108554010799, 0.26404857724525643, 0.6464799165290824) ,
+ rgb (0.3664224325155063, 0.25926481158628106, 0.6432940914076554) ,
+ rgb (0.36600853966739794, 0.25448043878086224, 0.6400336180336859) ,
+ rgb (0.3655669837353898, 0.24969683475296395, 0.6366967518748858) ,
+ rgb (0.3650957984588681, 0.24491536803550484, 0.6332817352005559) ,
+ rgb (0.3645930889012501, 0.24013747024823828, 0.629786801550261) ,
+ rgb (0.3640569302208851, 0.23536470386204195, 0.6262101345195302) ,
+ rgb (0.36348537610385145, 0.2305987621839642, 0.6225498862239288) ,
+ rgb (0.3628764356004103, 0.2258414929328703, 0.6188041741082302) ,
+ rgb (0.36222809558295926, 0.22109488427338303, 0.6149711234609613) ,
+ rgb (0.36153829010998356, 0.21636111429594002, 0.6110488067964093) ,
+ rgb (0.36080493826624654, 0.21164251793458128, 0.6070353217206471) ,
+ rgb (0.36002681809096376, 0.20694122817889948, 0.6029284543191687) ,
+ rgb (0.35920088560930186, 0.20226037920758122, 0.5987265295935138) ,
+ rgb (0.3583248996661781, 0.197602942459778, 0.5944276851750107) ,
+ rgb (0.35739663292915563, 0.1929720819784246, 0.5900301125106313) ,
+ rgb (0.35641381143126327, 0.18837119869242164, 0.5855320765920552) ,
+ rgb (0.3553741530690672, 0.18380392577704466, 0.580931914318328) ,
+ rgb (0.3542753496066376, 0.17927413271618647, 0.5762280966066872) ,
+ rgb (0.35311574421123737, 0.17478570377561287, 0.5714187152355529) ,
+ rgb (0.3518924860887379, 0.17034320478524959, 0.5665028491121665) ,
+ rgb (0.3506030444193101, 0.1659512998472086, 0.5614796470399323) ,
+ rgb (0.34924513554955644, 0.16161477763045118, 0.5563483747416378) ,
+ rgb (0.3478165323877778, 0.1573386351115298, 0.5511085345270326) ,
+ rgb (0.3463150717579309, 0.15312802296627787, 0.5457599924248665) ,
+ rgb (0.34473901574536375, 0.1489882058982641, 0.5403024592040654) ,
+ rgb (0.34308600291572294, 0.14492465359918028, 0.534737042820671) ,
+ rgb (0.34135411074506483, 0.1409427920655632, 0.5290650094033675) ,
+ rgb (0.33954168752669694, 0.1370480189671817, 0.5232879753508524) ,
+ rgb (0.3376473209067111, 0.13324562282438077, 0.5174080757397947) ,
+ rgb (0.33566978565015315, 0.12954074251271822, 0.5114280721516895) ,
+ rgb (0.33360804901486, 0.1259381830100592, 0.505351647966549) ,
+ rgb (0.33146154891145124, 0.12244245263391232, 0.4991827458843107) ,
+ rgb (0.3292300520323141, 0.11905764321981127, 0.49292595612342666) ,
+ rgb (0.3269137124539796, 0.1157873496841953, 0.4865864649569746) ,
+ rgb (0.32451307931207785, 0.11263459791730848, 0.48017007211645196) ,
+ rgb (0.3220288227606932, 0.10960114111258401, 0.4736849472572688) ,
+ rgb (0.31946262395497965, 0.1066887988239266, 0.46713728801395243) ,
+ rgb (0.316816480890235, 0.10389861387653518, 0.46053414662739794) ,
+ rgb (0.3140927841475553, 0.10123077676403242, 0.45388335612058467) ,
+ rgb (0.31129434479712365, 0.0986847719340522, 0.4471931371516162) ,
+ rgb (0.30842444457210105, 0.09625938534057774, 0.44047194882050544) ,
+ rgb (0.30548675819945936, 0.09395276484082374, 0.4337284999936111) ,
+ rgb (0.3024853636457425, 0.0917611873973036, 0.42697404043749887) ,
+ rgb (0.2994248396021477, 0.08968225371675004, 0.42021619665853854) ,
+ rgb (0.2963100038890529, 0.08771325096046395, 0.41346259134143476) ,
+ rgb (0.2931459309698525, 0.08585065688962071, 0.40672178082365834) ,
+ rgb (0.2899379244517661, 0.08409078829085731, 0.40000214725256295) ,
+ rgb (0.28669151388283165, 0.08242987384848069, 0.39331182532243375) ,
+ rgb (0.28341239797185225, 0.08086415336549937, 0.38665868550105914) ,
+ rgb (0.2801063857697547, 0.07938999480226153, 0.38005028528138707) ,
+ rgb (0.2767793961581559, 0.07800394103378822, 0.37349382846504675) ,
+ rgb (0.2734373934245081, 0.07670280023749607, 0.36699616136347685) ,
+ rgb (0.2700863774911405, 0.07548367558427554, 0.36056376228111864) ,
+ rgb (0.26673233211995284, 0.0743440180285462, 0.3542027606624096) ,
+ rgb (0.26338121807151404, 0.07328165793989708, 0.34791888996380105) ,
+ rgb (0.26003895187439957, 0.0722947810433622, 0.3417175669546984) ,
+ rgb (0.256711916510839, 0.07138010624208224, 0.3356064898460009) ,
+ rgb (0.25340685873736807, 0.07053358292685183, 0.3295945757321303) ,
+ rgb (0.2501284530619938, 0.06975820642910699, 0.32368100685760637) ,
+ rgb (0.24688226237959, 0.06905363944920445, 0.31786993834254956) ,
+ rgb (0.24367372557466271, 0.06841985515092269, 0.3121652405088837) ,
+ rgb (0.2405081333229594, 0.0678571038148556, 0.3065705449367832) ,
+ rgb (0.23739062429054825, 0.06736588805055552, 0.3010892218406587) ,
+ rgb (0.23433055727563878, 0.0669355996616394, 0.295740099298676) ,
+ rgb (0.23132955273021344, 0.06657618693909059, 0.29051361067988485) ,
+ rgb (0.2283917709422868, 0.06628997924139618, 0.28541074411068496) ,
+ rgb (0.22552164337737857, 0.0660781731193956, 0.28043398847505197) ,
+ rgb (0.22272706739121817, 0.06593379067565194, 0.275597146520537) ,
+ rgb (0.22001251100779617, 0.0658579189189076, 0.2709027999432586) ,
+ rgb (0.21737845072382705, 0.06585966123356204, 0.2663420934966951) ,
+ rgb (0.21482843531473683, 0.06594038561377849, 0.26191675992376573) ,
+ rgb (0.21237411048541005, 0.06608502466175845, 0.2576516509356954) ,
+ rgb (0.21001214221188125, 0.06630857391894718, 0.2535289048041211) ,
+ rgb (0.2077442377448806, 0.06661453200418091, 0.24954644291943817) ,
+ rgb (0.20558051999470117, 0.06699046239786874, 0.24572497420147632) ,
+ rgb (0.20352007949514977, 0.06744417961242422, 0.24205576625191821) ,
+ rgb (0.2015613376412984, 0.06798327102620025, 0.23852974228695395) ,
+ rgb (0.19971571438603364, 0.06859271055370472, 0.23517094067076993) ,
+ rgb (0.19794834061899208, 0.06931406607166066, 0.23194647381302336) ,
+ rgb (0.1960826032659409, 0.07032122724242362, 0.22874673279569585) ,
+ rgb (0.19410351363791453, 0.07160830485689157, 0.22558727307410353) ,
+ rgb (0.19199449184606268, 0.0731828306492733, 0.22243385243433622) ,
+ rgb (0.18975853639094634, 0.07501986186214377, 0.2193005075652994) ,
+ rgb (0.18739228342697645, 0.07710209689958833, 0.21618875376309582) ,
+ rgb (0.18488035509396164, 0.07942573027972388, 0.21307651648984993) ,
+ rgb (0.18774482037046955, 0.07725158846803931, 0.21387448578597812) ,
+ rgb (0.19049578401722037, 0.07531127841678764, 0.2146562337112265) ,
+ rgb (0.1931548636579131, 0.07360681904011795, 0.21542362939081539) ,
+ rgb (0.19571853588267552, 0.07215778103960274, 0.21617499187076789) ,
+ rgb (0.19819343656336558, 0.07097462525273879, 0.21690975060032436) ,
+ rgb (0.20058760685133747, 0.07006457614998421, 0.21762721310371608) ,
+ rgb (0.20290365333558247, 0.06943524858045896, 0.21833167885096033) ,
+ rgb (0.20531725273301316, 0.06891959226639757, 0.21911516689288835) ,
+ rgb (0.20785704662965598, 0.06848439879702528, 0.22000133917653536) ,
+ rgb (0.21052882914958676, 0.06812195249816172, 0.22098759107715404) ,
+ rgb (0.2133313859647627, 0.06783014842602667, 0.2220704321302429) ,
+ rgb (0.21625279838647882, 0.06761633027051639, 0.22324568672294431) ,
+ rgb (0.21930503925136402, 0.06746578636294004, 0.22451023616807558) ,
+ rgb (0.22247308588973624, 0.06738821405309284, 0.22585960379408354) ,
+ rgb (0.2257539681670791, 0.06738213230014747, 0.22728984778098055) ,
+ rgb (0.2291562027859284, 0.06743473087115257, 0.22879681433956656) ,
+ rgb (0.23266299920501882, 0.06755710438847978, 0.23037617493752832) ,
+ rgb (0.23627495835774248, 0.06774359820987802, 0.23202360805926608) ,
+ rgb (0.23999586188690308, 0.06798502996477995, 0.23373434258507808) ,
+ rgb (0.2438114972024792, 0.06828985152901187, 0.23550427698321885) ,
+ rgb (0.247720929905011, 0.06865333790948652, 0.2373288009471749) ,
+ rgb (0.25172899728289466, 0.0690646308260355, 0.23920260612763083) ,
+ rgb (0.2558213554748177, 0.06953231029187984, 0.24112190491594204) ,
+ rgb (0.25999463887892144, 0.07005385560386188, 0.24308218808684579) ,
+ rgb (0.2642551220706094, 0.07061659562299544, 0.24507758869355967) ,
+ rgb (0.2685909594817286, 0.07122671627792246, 0.24710443563450618) ,
+ rgb (0.272997015188973, 0.07188355544616351, 0.2491584709323293) ,
+ rgb (0.277471508091428, 0.07258296989925478, 0.2512349399594277) ,
+ rgb (0.2820174629736694, 0.07331569321404097, 0.25332800295084507) ,
+ rgb (0.28662309235899847, 0.07408846082680887, 0.2554347867371703) ,
+ rgb (0.29128515387578635, 0.0748990498474667, 0.25755101595750435) ,
+ rgb (0.2960004726065818, 0.07574533600095842, 0.25967245030364566) ,
+ rgb (0.3007727681291869, 0.07661782433616476, 0.2617929409781967) ,
+ rgb (0.30559226007249934, 0.07752196310753731, 0.2639100669211966) ,
+ rgb (0.31045520848595526, 0.07845687167618218, 0.2660200572779356) ,
+ rgb (0.3153587000920581, 0.07942099731524319, 0.2681190407694196) ,
+ rgb (0.3202998655799406, 0.08041299473755484, 0.2702032289303951) ,
+ rgb (0.3252788886040126, 0.08142839007654609, 0.27226772884656186) ,
+ rgb (0.3302917447118144, 0.08246763389003825, 0.27430929404579435) ,
+ rgb (0.3353335322445545, 0.08353243411900396, 0.2763253435679004) ,
+ rgb (0.34040164359597463, 0.08462223619170267, 0.27831254595259397) ,
+ rgb (0.345493557138718, 0.08573665496512634, 0.28026769921081435) ,
+ rgb (0.3506067824603248, 0.08687555176033529, 0.28218770540182386) ,
+ rgb (0.35573889947341125, 0.08803897435024335, 0.2840695897279818) ,
+ rgb (0.36088752387578377, 0.0892271943627452, 0.28591050458531014) ,
+ rgb (0.36605031412464006, 0.0904406854276979, 0.2877077458811747) ,
+ rgb (0.3712250843130934, 0.09167999748026273, 0.2894586539763317) ,
+ rgb (0.3764103053221462, 0.09294519809377791, 0.2911602415731392) ,
+ rgb (0.38160247377467543, 0.09423873126371218, 0.2928110750626949) ,
+ rgb (0.3867993907954417, 0.09556181960083443, 0.29440901248173756) ,
+ rgb (0.39199887556812907, 0.09691583650296684, 0.2959521200550908) ,
+ rgb (0.39719876876325577, 0.09830232096827862, 0.2974385647628578) ,
+ rgb (0.40239692379737496, 0.09972293031495055, 0.2988667436973397) ,
+ rgb (0.4075912039268871, 0.10117945586419633, 0.300235195077286) ,
+ rgb (0.41277985630360303, 0.1026734006932461, 0.3015422643746897) ,
+ rgb (0.41796105205173684, 0.10420644885760968, 0.3027865203963184) ,
+ rgb (0.42313214269556043, 0.10578120994917611, 0.3039675809469457) ,
+ rgb (0.4282910131578975, 0.1073997763055258, 0.30508479060294547) ,
+ rgb (0.4334355841041439, 0.1090642347484701, 0.3061376792828915) ,
+ rgb (0.4385637818793154, 0.11077667828375456, 0.30712600062348083) ,
+ rgb (0.44367358645071275, 0.11253912421257944, 0.3080497309546545) ,
+ rgb (0.4487629917317482, 0.1143535557462255, 0.30890905921943196) ,
+ rgb (0.4538300508699989, 0.11622183788331528, 0.3097044124984492) ,
+ rgb (0.45887288947308297, 0.11814571137706886, 0.3104363697903881) ,
+ rgb (0.46389102840284874, 0.12012561256850712, 0.31110343446582983) ,
+ rgb (0.46888111384598413, 0.12216445576414045, 0.31170911458932665) ,
+ rgb (0.473841437035254, 0.12426354237989065, 0.31225470169927194) ,
+ rgb (0.47877034239726296, 0.12642401401409453, 0.3127417273582196) ,
+ rgb (0.48366628618847957, 0.1286467902201389, 0.31317188565991266) ,
+ rgb (0.48852847371852987, 0.13093210934893723, 0.31354553695453014) ,
+ rgb (0.49335504375145617, 0.13328091630401023, 0.31386561956734976) ,
+ rgb (0.4981443546207415, 0.13569380302451714, 0.314135190862664) ,
+ rgb (0.5028952497497061, 0.13817086581280427, 0.3143566215383367) ,
+ rgb (0.5076068118105369, 0.14071192654913128, 0.3145320012008257) ,
+ rgb (0.5122783510532176, 0.14331656120063752, 0.3146630922831542) ,
+ rgb (0.5169084880054446, 0.14598463068714407, 0.3147540759228004) ,
+ rgb (0.5214965286322996, 0.14871544765633712, 0.3148076795453443) ,
+ rgb (0.5260418962547748, 0.15150818660835483, 0.31482653406646727) ,
+ rgb (0.5305442048985645, 0.15436183633886777, 0.3148129978918713) ,
+ rgb (0.5350027976174474, 0.15727540775107324, 0.3147708520739653) ,
+ rgb (0.5394173664919906, 0.16024769309971934, 0.31470295028655965) ,
+ rgb (0.5437877131360856, 0.16327738551419116, 0.31461204226295625) ,
+ rgb (0.5481137003346762, 0.1663630904279047, 0.3145010299091471) ,
+ rgb (0.5523952157271191, 0.16950338809328983, 0.3143729155461537) ,
+ rgb (0.5566322903496934, 0.17269677158182117, 0.31423043195101424) ,
+ rgb (0.5608249903911717, 0.17594170887918095, 0.31407639883970623) ,
+ rgb (0.564973435290177, 0.17923664950367169, 0.3139136046337036) ,
+ rgb (0.5690778478401143, 0.18258004462335425, 0.3137444095679653) ,
+ rgb (0.5731384575410787, 0.18597036007065024, 0.3135712686852) ,
+ rgb (0.5771555081299204, 0.18940601489760422, 0.3133970433357208) ,
+ rgb (0.5811293276158656, 0.19288548904692518, 0.3132239939418394) ,
+ rgb (0.5850602439646688, 0.19640737049066315, 0.3130540116373273) ,
+ rgb (0.5889486193554471, 0.19997020971775276, 0.31288922211590126) ,
+ rgb (0.5927948053652026, 0.20357251410079796, 0.3127323483930494) ,
+ rgb (0.5965991810912237, 0.207212956082026, 0.3125852303112123) ,
+ rgb (0.6003621301041158, 0.21089030138947745, 0.3124493441041469) ,
+ rgb (0.6040840169673274, 0.21460331490206347, 0.31232652641170694) ,
+ rgb (0.6077652399481865, 0.21835070166659282, 0.312219032918702) ,
+ rgb (0.6114062072731884, 0.22213124697023234, 0.3121288139643524) ,
+ rgb (0.6150072323639137, 0.22594402043981826, 0.3120568068576574) ,
+ rgb (0.6185686525887719, 0.2297879924917992, 0.3120046383872893) ,
+ rgb (0.6220907982108261, 0.2336621873300741, 0.3119738327362739) ,
+ rgb (0.6255741650043496, 0.23756535071152696, 0.3119669831491227) ,
+ rgb (0.6290189201698587, 0.24149689191922535, 0.3119844719564572) ,
+ rgb (0.6324253485421027, 0.24545598775548677, 0.3120276597462445) ,
+ rgb (0.6357937104834237, 0.24944185818822678, 0.3120979395330059) ,
+ rgb (0.6391243387840212, 0.2534536546198314, 0.3121968961206398) ,
+ rgb (0.642417577481186, 0.257490519876798, 0.31232631707560987) ,
+ rgb (0.6456734938264543, 0.2615520316161528, 0.31248673753935263) ,
+ rgb (0.6488923016945825, 0.2656375533620908, 0.3126794181957019) ,
+ rgb (0.652074172902773, 0.269746505252367, 0.3129056060581917) ,
+ rgb (0.6552193260932713, 0.2738782665241015, 0.3131666792687211) ,
+ rgb (0.6583280801134499, 0.2780321095766563, 0.3134643447952643) ,
+ rgb (0.6614003753260178, 0.28220778870555907, 0.3137991292649849) ,
+ rgb (0.6644363246987884, 0.2864048361425618, 0.31417223403606975) ,
+ rgb (0.6674360376636913, 0.29062280081258873, 0.31458483752056837) ,
+ rgb (0.670399595476762, 0.29486126309253047, 0.3150381395687221) ,
+ rgb (0.6733272556481733, 0.29911962764489264, 0.3155337232398221) ,
+ rgb (0.6762189792440975, 0.30339762792450425, 0.3160724937230589) ,
+ rgb (0.6790747402815734, 0.30769497879760166, 0.31665545668946665) ,
+ rgb (0.6818945715094452, 0.31201133280550686, 0.3172838048924495) ,
+ rgb (0.6846785094249453, 0.3163463482122221, 0.31795870784057567) ,
+ rgb (0.6874265643516962, 0.32069970535138104, 0.3186813762227769) ,
+ rgb (0.6901389321505248, 0.32507091815606004, 0.319453323328983) ,
+ rgb (0.6928154484676493, 0.32945984647042675, 0.3202754315314667) ,
+ rgb (0.6954560834689112, 0.33386622163232865, 0.3211488430698579) ,
+ rgb (0.6980608153581771, 0.3382897632604862, 0.3220747885521809) ,
+ rgb (0.700629624772421, 0.34273019305341756, 0.32305449047765694) ,
+ rgb (0.7031624945881415, 0.34718723719598, 0.32408913679491225) ,
+ rgb (0.7056595112261009, 0.3516605297812094, 0.32518014084085567) ,
+ rgb (0.7081205956842048, 0.356149855233803, 0.32632861885644465) ,
+ rgb (0.7105456546582587, 0.36065500290840113, 0.3275357416278876) ,
+ rgb (0.7129346683977347, 0.36517570519856757, 0.3288027427038317) ,
+ rgb (0.7152876061484729, 0.3697117022522345, 0.3301308728723546) ,
+ rgb (0.7176044490813385, 0.3742627271068619, 0.3315213862095893) ,
+ rgb (0.7198852149054985, 0.37882848839337313, 0.332975552002454) ,
+ rgb (0.7221299918421461, 0.3834086450896306, 0.33449469983585844) ,
+ rgb (0.7243386564778159, 0.38800301593162145, 0.3360799596569183) ,
+ rgb (0.7265112290022755, 0.3926113126792577, 0.3377325942005665) ,
+ rgb (0.7286477385671655, 0.39723324476747235, 0.33945384341064017) ,
+ rgb (0.7307482075484517, 0.401868526884681, 0.3412449533046818) ,
+ rgb (0.7328127050626875, 0.4065168468778026, 0.3431071517341082) ,
+ rgb (0.7348413359856494, 0.4111778700451951, 0.3450416947080907) ,
+ rgb (0.7368342217358587, 0.4158512585029011, 0.347049785207584) ,
+ rgb (0.7387914002459927, 0.4205367299231533, 0.34913260148542435) ,
+ rgb (0.7407130161950609, 0.4252339389526239, 0.35129130890802607) ,
+ rgb (0.7425992159973317, 0.42994254036133867, 0.3535270924537459) ,
+ rgb (0.7444501867657067, 0.4346621718461711, 0.35584108091122535) ,
+ rgb (0.7462661578916344, 0.439392450449735, 0.3582343914230064) ,
+ rgb (0.7480473927555956, 0.44413297780351974, 0.36070813602540136) ,
+ rgb (0.7497942054717047, 0.4488833348154881, 0.3632633755836028) ,
+ rgb (0.7515068504589166, 0.45364314496866825, 0.36590112443835765) ,
+ rgb (0.7531856636904657, 0.45841199172949604, 0.3686223664223477) ,
+ rgb (0.7548310506695954, 0.46318942799460555, 0.3714280448394211) ,
+ rgb (0.7564434157714071, 0.4679750143794846, 0.37431909037543515) ,
+ rgb (0.7580232553845584, 0.4727682731566229, 0.3772963553109668) ,
+ rgb (0.7595711110534006, 0.4775687122205708, 0.380360657784311) ,
+ rgb (0.7610876378057071, 0.48237579130289127, 0.3835127572385229) ,
+ rgb (0.7625733355405261, 0.48718906673415824, 0.38675335037837993) ,
+ rgb (0.7640288560928866, 0.49200802533379656, 0.39008308392311997) ,
+ rgb (0.7654549259333051, 0.4968321290972723, 0.3935025400011538) ,
+ rgb (0.7668522895064389, 0.5016608471009063, 0.39701221751773474) ,
+ rgb (0.768221765997353, 0.5064936237128791, 0.40061257089416885) ,
+ rgb (0.7695642334401418, 0.5113298901696085, 0.4043039806968248) ,
+ rgb (0.7708809196230247, 0.516168926434691, 0.40808667584648967) ,
+ rgb (0.7721725722960555, 0.5210102658711383, 0.4119608998712287) ,
+ rgb (0.7734402182988989, 0.5258533209345156, 0.41592679539764366) ,
+ rgb (0.774684947460632, 0.5306974938477673, 0.4199844035696376) ,
+ rgb (0.775907907306857, 0.5355421788246119, 0.42413367909988375) ,
+ rgb (0.7771103295521099, 0.5403867491056124, 0.4283745037125848) ,
+ rgb (0.7782934580763312, 0.545230594884266, 0.432706647838971) ,
+ rgb (0.7794586273150664, 0.5500730841397727, 0.4371297985644476) ,
+ rgb (0.7806077474948377, 0.5549133574489061, 0.4416433242636464) ,
+ rgb (0.7817418047898184, 0.5597509805259486, 0.44624687186865436) ,
+ rgb (0.7828622526444091, 0.5645853311116688, 0.45093985823706345) ,
+ rgb (0.7839706083641448, 0.5694157832671042, 0.4557215474289206) ,
+ rgb (0.7850684501960684, 0.5742417003617839, 0.46059116206904965) ,
+ rgb (0.7861573713233296, 0.5790624629815756, 0.465547782819184) ,
+ rgb (0.7872390410818835, 0.5838774374455721, 0.47059039582133383) ,
+ rgb (0.7883151404562396, 0.5886860017356244, 0.4757179187907608) ,
+ rgb (0.7893873776625194, 0.5934875421745599, 0.48092913815357724) ,
+ rgb (0.7904577684772788, 0.5982813427706246, 0.48622257801969754) ,
+ rgb (0.7915283284347561, 0.603066705931472, 0.49159667021646397) ,
+ rgb (0.7926003430423745, 0.6078432208703702, 0.4970502062153201) ,
+ rgb (0.7936755969866496, 0.6126102933407219, 0.5025816129126943) ,
+ rgb (0.7947558597265404, 0.617367344002207, 0.5081892121310299) ,
+ rgb (0.7958429237958377, 0.6221137880845115, 0.5138712409190979) ,
+ rgb (0.7969385471995161, 0.626849056792967, 0.5196258425240281) ,
+ rgb (0.7980444781513664, 0.6315725822508955, 0.5254510814483478) ,
+ rgb (0.7991624518501963, 0.6362837937202919, 0.5313449594256143) ,
+ rgb (0.8002941538975398, 0.6409821330674986, 0.5373053518514104) ,
+ rgb (0.8014412429256005, 0.6456670345921877, 0.5433300863249918) ,
+ rgb (0.8026053114611295, 0.6503379374810385, 0.5494169158460365) ,
+ rgb (0.8037879253107763, 0.6549942654947263, 0.5555635086708381) ,
+ rgb (0.804990547908103, 0.6596354502756416, 0.5617674511054698) ,
+ rgb (0.8062146052692706, 0.6642608958528229, 0.5680262917864979) ,
+ rgb (0.8074614045096935, 0.6688700095398864, 0.5743374637345958) ,
+ rgb (0.8087321917008969, 0.6734621670219452, 0.5806983480557674) ,
+ rgb (0.8100280946652069, 0.6780367267397182, 0.5871062690808275) ,
+ rgb (0.8113501401176333, 0.6825930154624339, 0.5935584890905076) ,
+ rgb (0.8126992203988149, 0.6871303371461888, 0.600052148204351) ,
+ rgb (0.8140761104699334, 0.6916479479148213, 0.6065843782630862) ,
+ rgb (0.8154814662727948, 0.6961450550830809, 0.6131522120932265) ,
+ rgb (0.8169157577505589, 0.7006208301478398, 0.6197526063725792) ,
+ rgb (0.8183793116449822, 0.705074381896351, 0.626382454789333) ,
+ rgb (0.8198723065045529, 0.7095047497878748, 0.6330385704006711) ,
+ rgb (0.8213947205565636, 0.7139109141951604, 0.6397176669767276) ,
+ rgb (0.8229463511042843, 0.7182917733129006, 0.6464164243818421) ,
+ rgb (0.8245268129450285, 0.7226461431208888, 0.653131379154226) ,
+ rgb (0.8261354971058026, 0.7269727551823826, 0.659859001562165) ,
+ rgb (0.8277716072353446, 0.7312702332407809, 0.6665957020468297) ,
+ rgb (0.8294340781648147, 0.7355371221572935, 0.6733377200930191) ,
+ rgb (0.8311216352909631, 0.7397718464763862, 0.6800812520363146) ,
+ rgb (0.8328327718577798, 0.7439727181745988, 0.6868223587464855) ,
+ rgb (0.8345656905566583, 0.7481379479992134, 0.6935569764986385) ,
+ rgb (0.8363189884473793, 0.7522654895287526, 0.7002799902886496) ,
+ rgb (0.8380912347613196, 0.7563531486080863, 0.7069856139021298) ,
+ rgb (0.8398783988412087, 0.7603990719977968, 0.7136714781112923) ,
+ rgb (0.8416775076684515, 0.7644010120098295, 0.7203329938728462) ,
+ rgb (0.843485292229337, 0.7683566039987018, 0.7269653699897204) ,
+ rgb (0.8452981073195511, 0.7722633860104472, 0.7335636824054149) ,
+ rgb (0.847111955079651, 0.7761188023604716, 0.7401227576280706) ,
+ rgb (0.8489224556311764, 0.7799202140765015, 0.7466371929366437) ,
+ rgb (0.8507269702317879, 0.7836645734238389, 0.7530974636118285) ,
+ rgb (0.8525190720770844, 0.7873493613354844, 0.7594994148789691) ,
+ rgb (0.8542921961147046, 0.7909719677709199, 0.765838014779141) ,
+ rgb (0.856040223147254, 0.7945296360155061, 0.7721061003767414) ,
+ rgb (0.857756629435049, 0.7980196314271393, 0.778295716672475) ,
+ rgb (0.8594346370300241, 0.8014392309950078, 0.7843978875138392) ,
+ rgb (0.8610711702756552, 0.8047851790981223, 0.7903952966373629) ,
+ rgb (0.8626560105112757, 0.8080552380426153, 0.796282666437655) ,
+ rgb (0.8641834372394103, 0.8112464422465354, 0.8020461269686395) ,
+ rgb (0.8656493432560532, 0.8143554406751491, 0.8076697232416455) ,
+ rgb (0.867053149070485, 0.8173780404191124, 0.813134196269114) ,
+ rgb (0.8683995469581863, 0.8203087551218152, 0.8184163896312899) ,
+ rgb (0.8696913150261381, 0.8231415885956916, 0.8235047668317317) ,
+ rgb (0.8709384671729751, 0.8258685788943851, 0.8283849726114961) ,
+ rgb (0.8721533197845432, 0.8284805282370967, 0.8330486712880828) ,
+ rgb (0.8733517136091627, 0.8309671525127262, 0.8374885100119709) ,
+ rgb (0.8745379332026019, 0.8333197294864546, 0.8417192535806901) ,
+ rgb (0.875714587099614, 0.8355302318472394, 0.8457553751902708) ,
+ rgb (0.8768784845161469, 0.8375923807118654, 0.8496137354915025) ,
+ rgb (0.8780229843664901, 0.8395016561854007, 0.8533064535245892) ,
+ rgb (0.8791324424079277, 0.8412555488447591, 0.8568557229103964) ,
+ rgb (0.8801929331569581, 0.8428522482477862, 0.8602739992715663) ,
+ rgb (0.8811916987134195, 0.8442906671771735, 0.8635659516866988) ,
+ rgb (0.8821154248940161, 0.8455700725455935, 0.8667376504623333) ,
+ rgb (0.8829516859544853, 0.8466897027569927, 0.8697961704819097) ,
+ rgb (0.8836912714589804, 0.8476489176151927, 0.8727414710144156) ,
+ rgb (0.8843271305411354, 0.8484474157205542, 0.8755678522824297) ,
+ rgb (0.8848513815990857, 0.849084264228938, 0.8782823528537247) ,
+ rgb (0.8852589797263047, 0.8495589281098921, 0.8808841479402484) ,
+ rgb (0.8855471481195238, 0.8498717428363158, 0.8833620612117095) ,
+ rgb (0.8857115512284565, 0.8500218611585632, 0.8857253899008712)
+});
+
+
+list_data twilight_shifted = list_data(new pen[] {
+ rgb (0.18739228342697645, 0.07710209689958833, 0.21618875376309582) ,
+ rgb (0.18975853639094634, 0.07501986186214377, 0.2193005075652994) ,
+ rgb (0.19199449184606268, 0.0731828306492733, 0.22243385243433622) ,
+ rgb (0.19410351363791453, 0.07160830485689157, 0.22558727307410353) ,
+ rgb (0.1960826032659409, 0.07032122724242362, 0.22874673279569585) ,
+ rgb (0.19794834061899208, 0.06931406607166066, 0.23194647381302336) ,
+ rgb (0.19971571438603364, 0.06859271055370472, 0.23517094067076993) ,
+ rgb (0.2015613376412984, 0.06798327102620025, 0.23852974228695395) ,
+ rgb (0.20352007949514977, 0.06744417961242422, 0.24205576625191821) ,
+ rgb (0.20558051999470117, 0.06699046239786874, 0.24572497420147632) ,
+ rgb (0.2077442377448806, 0.06661453200418091, 0.24954644291943817) ,
+ rgb (0.21001214221188125, 0.06630857391894718, 0.2535289048041211) ,
+ rgb (0.21237411048541005, 0.06608502466175845, 0.2576516509356954) ,
+ rgb (0.21482843531473683, 0.06594038561377849, 0.26191675992376573) ,
+ rgb (0.21737845072382705, 0.06585966123356204, 0.2663420934966951) ,
+ rgb (0.22001251100779617, 0.0658579189189076, 0.2709027999432586) ,
+ rgb (0.22272706739121817, 0.06593379067565194, 0.275597146520537) ,
+ rgb (0.22552164337737857, 0.0660781731193956, 0.28043398847505197) ,
+ rgb (0.2283917709422868, 0.06628997924139618, 0.28541074411068496) ,
+ rgb (0.23132955273021344, 0.06657618693909059, 0.29051361067988485) ,
+ rgb (0.23433055727563878, 0.0669355996616394, 0.295740099298676) ,
+ rgb (0.23739062429054825, 0.06736588805055552, 0.3010892218406587) ,
+ rgb (0.2405081333229594, 0.0678571038148556, 0.3065705449367832) ,
+ rgb (0.24367372557466271, 0.06841985515092269, 0.3121652405088837) ,
+ rgb (0.24688226237959, 0.06905363944920445, 0.31786993834254956) ,
+ rgb (0.2501284530619938, 0.06975820642910699, 0.32368100685760637) ,
+ rgb (0.25340685873736807, 0.07053358292685183, 0.3295945757321303) ,
+ rgb (0.256711916510839, 0.07138010624208224, 0.3356064898460009) ,
+ rgb (0.26003895187439957, 0.0722947810433622, 0.3417175669546984) ,
+ rgb (0.26338121807151404, 0.07328165793989708, 0.34791888996380105) ,
+ rgb (0.26673233211995284, 0.0743440180285462, 0.3542027606624096) ,
+ rgb (0.2700863774911405, 0.07548367558427554, 0.36056376228111864) ,
+ rgb (0.2734373934245081, 0.07670280023749607, 0.36699616136347685) ,
+ rgb (0.2767793961581559, 0.07800394103378822, 0.37349382846504675) ,
+ rgb (0.2801063857697547, 0.07938999480226153, 0.38005028528138707) ,
+ rgb (0.28341239797185225, 0.08086415336549937, 0.38665868550105914) ,
+ rgb (0.28669151388283165, 0.08242987384848069, 0.39331182532243375) ,
+ rgb (0.2899379244517661, 0.08409078829085731, 0.40000214725256295) ,
+ rgb (0.2931459309698525, 0.08585065688962071, 0.40672178082365834) ,
+ rgb (0.2963100038890529, 0.08771325096046395, 0.41346259134143476) ,
+ rgb (0.2994248396021477, 0.08968225371675004, 0.42021619665853854) ,
+ rgb (0.3024853636457425, 0.0917611873973036, 0.42697404043749887) ,
+ rgb (0.30548675819945936, 0.09395276484082374, 0.4337284999936111) ,
+ rgb (0.30842444457210105, 0.09625938534057774, 0.44047194882050544) ,
+ rgb (0.31129434479712365, 0.0986847719340522, 0.4471931371516162) ,
+ rgb (0.3140927841475553, 0.10123077676403242, 0.45388335612058467) ,
+ rgb (0.316816480890235, 0.10389861387653518, 0.46053414662739794) ,
+ rgb (0.31946262395497965, 0.1066887988239266, 0.46713728801395243) ,
+ rgb (0.3220288227606932, 0.10960114111258401, 0.4736849472572688) ,
+ rgb (0.32451307931207785, 0.11263459791730848, 0.48017007211645196) ,
+ rgb (0.3269137124539796, 0.1157873496841953, 0.4865864649569746) ,
+ rgb (0.3292300520323141, 0.11905764321981127, 0.49292595612342666) ,
+ rgb (0.33146154891145124, 0.12244245263391232, 0.4991827458843107) ,
+ rgb (0.33360804901486, 0.1259381830100592, 0.505351647966549) ,
+ rgb (0.33566978565015315, 0.12954074251271822, 0.5114280721516895) ,
+ rgb (0.3376473209067111, 0.13324562282438077, 0.5174080757397947) ,
+ rgb (0.33954168752669694, 0.1370480189671817, 0.5232879753508524) ,
+ rgb (0.34135411074506483, 0.1409427920655632, 0.5290650094033675) ,
+ rgb (0.34308600291572294, 0.14492465359918028, 0.534737042820671) ,
+ rgb (0.34473901574536375, 0.1489882058982641, 0.5403024592040654) ,
+ rgb (0.3463150717579309, 0.15312802296627787, 0.5457599924248665) ,
+ rgb (0.3478165323877778, 0.1573386351115298, 0.5511085345270326) ,
+ rgb (0.34924513554955644, 0.16161477763045118, 0.5563483747416378) ,
+ rgb (0.3506030444193101, 0.1659512998472086, 0.5614796470399323) ,
+ rgb (0.3518924860887379, 0.17034320478524959, 0.5665028491121665) ,
+ rgb (0.35311574421123737, 0.17478570377561287, 0.5714187152355529) ,
+ rgb (0.3542753496066376, 0.17927413271618647, 0.5762280966066872) ,
+ rgb (0.3553741530690672, 0.18380392577704466, 0.580931914318328) ,
+ rgb (0.35641381143126327, 0.18837119869242164, 0.5855320765920552) ,
+ rgb (0.35739663292915563, 0.1929720819784246, 0.5900301125106313) ,
+ rgb (0.3583248996661781, 0.197602942459778, 0.5944276851750107) ,
+ rgb (0.35920088560930186, 0.20226037920758122, 0.5987265295935138) ,
+ rgb (0.36002681809096376, 0.20694122817889948, 0.6029284543191687) ,
+ rgb (0.36080493826624654, 0.21164251793458128, 0.6070353217206471) ,
+ rgb (0.36153829010998356, 0.21636111429594002, 0.6110488067964093) ,
+ rgb (0.36222809558295926, 0.22109488427338303, 0.6149711234609613) ,
+ rgb (0.3628764356004103, 0.2258414929328703, 0.6188041741082302) ,
+ rgb (0.36348537610385145, 0.2305987621839642, 0.6225498862239288) ,
+ rgb (0.3640569302208851, 0.23536470386204195, 0.6262101345195302) ,
+ rgb (0.3645930889012501, 0.24013747024823828, 0.629786801550261) ,
+ rgb (0.3650957984588681, 0.24491536803550484, 0.6332817352005559) ,
+ rgb (0.3655669837353898, 0.24969683475296395, 0.6366967518748858) ,
+ rgb (0.36600853966739794, 0.25448043878086224, 0.6400336180336859) ,
+ rgb (0.3664224325155063, 0.25926481158628106, 0.6432940914076554) ,
+ rgb (0.3668108554010799, 0.26404857724525643, 0.6464799165290824) ,
+ rgb (0.36717513650699446, 0.26883085667326956, 0.6495927229789225) ,
+ rgb (0.36751707094367697, 0.2736106331709098, 0.6526341171161864) ,
+ rgb (0.3678384369653108, 0.2783869718129776, 0.655605668384537) ,
+ rgb (0.3681410147989972, 0.2831590122118299, 0.6585088880697231) ,
+ rgb (0.36842655638020444, 0.2879259643777846, 0.661345269109446) ,
+ rgb (0.3686968223189527, 0.292687098561501, 0.6641162529823691) ,
+ rgb (0.3689535530659678, 0.29744175492366276, 0.6668232208982426) ,
+ rgb (0.36919847837592484, 0.3021893217650707, 0.6694675433161452) ,
+ rgb (0.3694333459127623, 0.3069292355186234, 0.6720505284912062) ,
+ rgb (0.36965987626565955, 0.3116609876295197, 0.6745734474341955) ,
+ rgb (0.3698798032902536, 0.31638410101153364, 0.6770375543809057) ,
+ rgb (0.37009487130772695, 0.3210981375964933, 0.6794440539905685) ,
+ rgb (0.37030682071842685, 0.32580269697872455, 0.6817941168448668) ,
+ rgb (0.37051738634484427, 0.3304974124430785, 0.6840888878885721) ,
+ rgb (0.3707283327942267, 0.33518193808489577, 0.6863294816960677) ,
+ rgb (0.3709415155133733, 0.33985591488818123, 0.6885170337950512) ,
+ rgb (0.37115856636209105, 0.3445191141023017, 0.6906525358646499) ,
+ rgb (0.37138124223736607, 0.34917126878479027, 0.6927370347192883) ,
+ rgb (0.3716113323440048, 0.3538121372967869, 0.6947714991938089) ,
+ rgb (0.3718506155898596, 0.3584414828587522, 0.6967569581025654) ,
+ rgb (0.3721008970244382, 0.3630590973698238, 0.6986943461507372) ,
+ rgb (0.37236397858465387, 0.36766477862108266, 0.7005846349652077) ,
+ rgb (0.37264166757849604, 0.3722583500483685, 0.7024287314570723) ,
+ rgb (0.3729357864666503, 0.3768396383521984, 0.7042275578058994) ,
+ rgb (0.37324816143326384, 0.38140848555753937, 0.7059820097480604) ,
+ rgb (0.3735806215098284, 0.3859647438605754, 0.7076929760731058) ,
+ rgb (0.3739349933047578, 0.3905082752937583, 0.7093613429347845) ,
+ rgb (0.3743131319931234, 0.3950389482828331, 0.7109879652237746) ,
+ rgb (0.3747168601930223, 0.3995566498711684, 0.7125736851650046) ,
+ rgb (0.37514802505380473, 0.4040612609993941, 0.7141193695725726) ,
+ rgb (0.375608469194424, 0.40855267638072096, 0.7156258509158755) ,
+ rgb (0.37610001286385814, 0.4130307995247706, 0.7170939691992023) ,
+ rgb (0.37662448930806297, 0.41749553747893614, 0.7185245473617611) ,
+ rgb (0.3771837184425123, 0.4219468022345483, 0.7199184152447577) ,
+ rgb (0.3777794975351373, 0.4263845142616835, 0.7212763999353023) ,
+ rgb (0.378413635091359, 0.43080859411413064, 0.7225993199306104) ,
+ rgb (0.3790878928311076, 0.43521897612544935, 0.7238879869132313) ,
+ rgb (0.3798040374484875, 0.4396155882122263, 0.7251432377876109) ,
+ rgb (0.3805638069656562, 0.4439983720863372, 0.7263658704513531) ,
+ rgb (0.3813688793045416, 0.4483672766927786, 0.7275567131714135) ,
+ rgb (0.38222094988570376, 0.45272225034283325, 0.7287165614400378) ,
+ rgb (0.3831216808440275, 0.457063235814072, 0.7298462679135326) ,
+ rgb (0.38407269378943537, 0.46139018782416635, 0.7309466543290268) ,
+ rgb (0.3850755679365139, 0.46570306719930193, 0.732018540336905) ,
+ rgb (0.386131841788921, 0.4700018340988123, 0.7330627749243106) ,
+ rgb (0.3872430145933025, 0.4742864593363539, 0.7340801678785439) ,
+ rgb (0.3884105330084243, 0.47855691131792805, 0.7350715764115726) ,
+ rgb (0.38963580160340855, 0.48281316715123496, 0.7360378254693274) ,
+ rgb (0.390920175719949, 0.4870552025122304, 0.7369797713388125) ,
+ rgb (0.39226494876209317, 0.4912830033289926, 0.7378982478447508) ,
+ rgb (0.39367135736822567, 0.4954965577745118, 0.738794102296364) ,
+ rgb (0.3951405880820763, 0.4996958532637776, 0.7396682021171571) ,
+ rgb (0.3966737490566561, 0.5038808905384797, 0.7405213858051674) ,
+ rgb (0.3982719152586337, 0.5080516653927614, 0.7413545091809972) ,
+ rgb (0.39993606933454834, 0.5122081814321852, 0.7421684457131799) ,
+ rgb (0.40166714010896104, 0.5163504496968876, 0.7429640345324835) ,
+ rgb (0.40346600333905397, 0.5204784765384003, 0.7437421522356709) ,
+ rgb (0.4053334378930318, 0.5245922817498312, 0.7445036583670813) ,
+ rgb (0.4072701869421907, 0.5286918801105741, 0.7452494263758127) ,
+ rgb (0.4092768899914751, 0.5327773017713032, 0.7459803063570782) ,
+ rgb (0.4113541469730457, 0.5368485776500593, 0.7466971285506578) ,
+ rgb (0.4135024574331473, 0.5409057477109848, 0.7474007329754309) ,
+ rgb (0.4157223260454232, 0.544948827153504, 0.7480920445900052) ,
+ rgb (0.4180141407923363, 0.5489778542188889, 0.7487719316700112) ,
+ rgb (0.42037822361396326, 0.5529928715810817, 0.7494412559451894) ,
+ rgb (0.4228148567577266, 0.5569939212699658, 0.7501008698822764) ,
+ rgb (0.42532423665011354, 0.560981049599503, 0.7507516498900512) ,
+ rgb (0.42790652284925834, 0.5649543060909209, 0.7513944352191484) ,
+ rgb (0.4305617907305757, 0.5689137457245718, 0.752030080993127) ,
+ rgb (0.43329008867358393, 0.5728594162560667, 0.7526594653256667) ,
+ rgb (0.4360913895835637, 0.5767913799818608, 0.7532834105961016) ,
+ rgb (0.43896563958048396, 0.5807096924109849, 0.7539027620828594) ,
+ rgb (0.441912717666964, 0.5846144110017557, 0.7545183888441067) ,
+ rgb (0.4449324685421538, 0.5885055998308617, 0.7551311041857901) ,
+ rgb (0.4480247093358917, 0.5923833145214658, 0.7557417647410792) ,
+ rgb (0.45118918687617743, 0.5962476205135354, 0.7563512064324664) ,
+ rgb (0.45442563977552913, 0.6000985950385866, 0.7569601366060649) ,
+ rgb (0.45773377230160567, 0.6039363004658646, 0.7575693690185916) ,
+ rgb (0.4611132664702388, 0.607760777169989, 0.7581798643680714) ,
+ rgb (0.4645637671630393, 0.6115720882286415, 0.7587924262302581) ,
+ rgb (0.46808490970531597, 0.6153702869579029, 0.7594078989109274) ,
+ rgb (0.4716762951887709, 0.6191554324288464, 0.7600270922788305) ,
+ rgb (0.47533752394906287, 0.6229275728383581, 0.7606508557181775) ,
+ rgb (0.47906816236197386, 0.6266867625186013, 0.7612800037566242) ,
+ rgb (0.4828677867260272, 0.6304330455306234, 0.761915371498953) ,
+ rgb (0.4867359599430568, 0.6341664625110051, 0.7625578008592404) ,
+ rgb (0.4906722493856122, 0.6378870485884708, 0.7632081276316384) ,
+ rgb (0.4946761847863938, 0.6415948411950443, 0.7638671900213091) ,
+ rgb (0.4987473366135607, 0.6452898684900934, 0.7645357873418008) ,
+ rgb (0.5028853540415561, 0.6489721673409526, 0.7652144671817491) ,
+ rgb (0.5070896957645166, 0.6526417240314645, 0.7659044566083585) ,
+ rgb (0.5113599254160193, 0.6562985398183186, 0.7666066378064533) ,
+ rgb (0.5156955988596057, 0.65994260812898, 0.7673219148959617) ,
+ rgb (0.5200962739223556, 0.6635739143403039, 0.768051194033441) ,
+ rgb (0.5245615147059358, 0.6671924299614223, 0.7687954171423095) ,
+ rgb (0.529090861832473, 0.6707981230280622, 0.7695555229231313) ,
+ rgb (0.533683891477284, 0.6743909370521273, 0.7703325054879735) ,
+ rgb (0.5383401557517628, 0.677970811290954, 0.7711273443905772) ,
+ rgb (0.5430593242401823, 0.6815376725306588, 0.7719407969783508) ,
+ rgb (0.5478409815301863, 0.6850914221850988, 0.7727738647344087) ,
+ rgb (0.5526845058934713, 0.6886319451516638, 0.7736279426902245) ,
+ rgb (0.5575894041960517, 0.6921591145825405, 0.7745041337932782) ,
+ rgb (0.5625551535721934, 0.6956727838162965, 0.7754035914230984) ,
+ rgb (0.5675811785386197, 0.6991727930264627, 0.776327485342753) ,
+ rgb (0.5726668948158774, 0.7026589535425779, 0.7772770268091199) ,
+ rgb (0.5778116438998202, 0.7061310615715398, 0.7782534512102552) ,
+ rgb (0.5830148703693241, 0.7095888767699747, 0.7792578182047659) ,
+ rgb (0.588275797805555, 0.7130321464645814, 0.7802914140563652) ,
+ rgb (0.5935933569683722, 0.7164606049658685, 0.781355882376401) ,
+ rgb (0.5989665814482068, 0.7198739489224673, 0.7824525989934664) ,
+ rgb (0.6043943420027486, 0.7232718614323369, 0.7835829559353559) ,
+ rgb (0.6098754317609306, 0.7266539875975829, 0.7847483573269471) ,
+ rgb (0.6154084641177048, 0.7300199523273969, 0.7859502270675048) ,
+ rgb (0.6209919306481755, 0.733369347989232, 0.7871899462469658) ,
+ rgb (0.6266240202260459, 0.7367017540369944, 0.7884690131633456) ,
+ rgb (0.6323027138710603, 0.740016721601314, 0.7897889276264043) ,
+ rgb (0.6380258682854598, 0.7433137671403319, 0.7911510045957317) ,
+ rgb (0.6437910831099849, 0.7465923800833657, 0.7925565320130605) ,
+ rgb (0.6495957300425348, 0.7498520122194177, 0.7940067402149911) ,
+ rgb (0.6554369232645472, 0.7530920875676843, 0.7955027112903105) ,
+ rgb (0.6613112930078745, 0.7563120270871903, 0.7970456043491897) ,
+ rgb (0.6672147980375281, 0.7595112803730375, 0.7986367465453776) ,
+ rgb (0.6731442255942621, 0.7626890873389048, 0.8002762854580953) ,
+ rgb (0.6790955449988215, 0.765844721313959, 0.8019646617300199) ,
+ rgb (0.6850644615439593, 0.7689774029354699, 0.8037020626717691) ,
+ rgb (0.691046410093091, 0.7720862946067809, 0.8054884169067907) ,
+ rgb (0.6970365443886174, 0.7751705000806606, 0.8073233538006345) ,
+ rgb (0.7030297722540817, 0.7782290497335813, 0.8092061884805697) ,
+ rgb (0.709020781345393, 0.7812608871607091, 0.8111359185511793) ,
+ rgb (0.7150040307625213, 0.7842648709158119, 0.8131111655994991) ,
+ rgb (0.7209728066553678, 0.7872399592345264, 0.8151307392087926) ,
+ rgb (0.726921775128297, 0.7901846863592763, 0.8171921746672638) ,
+ rgb (0.7328454364552346, 0.7930974646884407, 0.8192926338423038) ,
+ rgb (0.7387377170049494, 0.7959766573503101, 0.8214292278043301) ,
+ rgb (0.7445924777189017, 0.7988204771958325, 0.8235986758615652) ,
+ rgb (0.750403467654067, 0.8016269900896532, 0.8257973785108242) ,
+ rgb (0.7561644358438198, 0.8043940873347794, 0.8280213899472) ,
+ rgb (0.7618690793798029, 0.8071194938764749, 0.830266486168872) ,
+ rgb (0.7675110850441786, 0.8098007598713145, 0.8325281663805967) ,
+ rgb (0.7730841659017094, 0.8124352473546601, 0.8348017295057968) ,
+ rgb (0.7785789200822759, 0.8150208937874255, 0.8370834463093898) ,
+ rgb (0.7839910104276492, 0.8175542640053343, 0.8393674746403673) ,
+ rgb (0.7893144556460892, 0.8200321318870201, 0.8416486380471222) ,
+ rgb (0.7945430508923111, 0.8224511622630462, 0.8439218478682798) ,
+ rgb (0.79967075421268, 0.8248078181208093, 0.8461821002957853) ,
+ rgb (0.8046916442981458, 0.8270983878056066, 0.8484244929697402) ,
+ rgb (0.8095999819094809, 0.8293189667350546, 0.8506444160105037) ,
+ rgb (0.8143898212114309, 0.8314655869419785, 0.8528375906214702) ,
+ rgb (0.8190465467793753, 0.8335364929949034, 0.855002062870101) ,
+ rgb (0.8235742968025285, 0.8355248776479544, 0.8571319132851495) ,
+ rgb (0.8279689431601354, 0.837426007513952, 0.8592239945130679) ,
+ rgb (0.8322270571293441, 0.8392349062775448, 0.8612756350042788) ,
+ rgb (0.8363403180919118, 0.8409479651895194, 0.8632852800107016) ,
+ rgb (0.8403042080595778, 0.8425605950855084, 0.865250882410458) ,
+ rgb (0.8441261828674842, 0.8440648271103739, 0.8671697332269007) ,
+ rgb (0.8478071070257792, 0.8454546229209523, 0.8690403678369444) ,
+ rgb (0.8513371457085719, 0.8467273579611647, 0.8708608165735044) ,
+ rgb (0.8547259318925698, 0.8478748812467282, 0.8726282980930582) ,
+ rgb (0.8579825924567037, 0.8488893481028184, 0.8743403855344628) ,
+ rgb (0.8611024543689985, 0.8497675485700126, 0.8759924292343957) ,
+ rgb (0.86408985081464, 0.8505039116750779, 0.8775792578489263) ,
+ rgb (0.8669601550533358, 0.8510896085314068, 0.8790976697717334) ,
+ rgb (0.8697047485350982, 0.8515240300479789, 0.8805388339000336) ,
+ rgb (0.8723313408512408, 0.8518016547808089, 0.8818970435500162) ,
+ rgb (0.8748534750857597, 0.8519152612302319, 0.8831692696761383) ,
+ rgb (0.8772488085896548, 0.8518702833887027, 0.8843412038131143) ,
+ rgb (0.8795410528270573, 0.8516567540749572, 0.8854143767924102) ,
+ rgb (0.8817223105928579, 0.8512759407765347, 0.8863805692551482) ,
+ rgb (0.8837852019553906, 0.8507294054031063, 0.8872322209694989) ,
+ rgb (0.8857501584075443, 0.8500092494306783, 0.8879736506427196) ,
+ rgb (0.8857115512284565, 0.8500218611585632, 0.8857253899008712) ,
+ rgb (0.8855471481195238, 0.8498717428363158, 0.8833620612117095) ,
+ rgb (0.8852589797263047, 0.8495589281098921, 0.8808841479402484) ,
+ rgb (0.8848513815990857, 0.849084264228938, 0.8782823528537247) ,
+ rgb (0.8843271305411354, 0.8484474157205542, 0.8755678522824297) ,
+ rgb (0.8836912714589804, 0.8476489176151927, 0.8727414710144156) ,
+ rgb (0.8829516859544853, 0.8466897027569927, 0.8697961704819097) ,
+ rgb (0.8821154248940161, 0.8455700725455935, 0.8667376504623333) ,
+ rgb (0.8811916987134195, 0.8442906671771735, 0.8635659516866988) ,
+ rgb (0.8801929331569581, 0.8428522482477862, 0.8602739992715663) ,
+ rgb (0.8791324424079277, 0.8412555488447591, 0.8568557229103964) ,
+ rgb (0.8780229843664901, 0.8395016561854007, 0.8533064535245892) ,
+ rgb (0.8768784845161469, 0.8375923807118654, 0.8496137354915025) ,
+ rgb (0.875714587099614, 0.8355302318472394, 0.8457553751902708) ,
+ rgb (0.8745379332026019, 0.8333197294864546, 0.8417192535806901) ,
+ rgb (0.8733517136091627, 0.8309671525127262, 0.8374885100119709) ,
+ rgb (0.8721533197845432, 0.8284805282370967, 0.8330486712880828) ,
+ rgb (0.8709384671729751, 0.8258685788943851, 0.8283849726114961) ,
+ rgb (0.8696913150261381, 0.8231415885956916, 0.8235047668317317) ,
+ rgb (0.8683995469581863, 0.8203087551218152, 0.8184163896312899) ,
+ rgb (0.867053149070485, 0.8173780404191124, 0.813134196269114) ,
+ rgb (0.8656493432560532, 0.8143554406751491, 0.8076697232416455) ,
+ rgb (0.8641834372394103, 0.8112464422465354, 0.8020461269686395) ,
+ rgb (0.8626560105112757, 0.8080552380426153, 0.796282666437655) ,
+ rgb (0.8610711702756552, 0.8047851790981223, 0.7903952966373629) ,
+ rgb (0.8594346370300241, 0.8014392309950078, 0.7843978875138392) ,
+ rgb (0.857756629435049, 0.7980196314271393, 0.778295716672475) ,
+ rgb (0.856040223147254, 0.7945296360155061, 0.7721061003767414) ,
+ rgb (0.8542921961147046, 0.7909719677709199, 0.765838014779141) ,
+ rgb (0.8525190720770844, 0.7873493613354844, 0.7594994148789691) ,
+ rgb (0.8507269702317879, 0.7836645734238389, 0.7530974636118285) ,
+ rgb (0.8489224556311764, 0.7799202140765015, 0.7466371929366437) ,
+ rgb (0.847111955079651, 0.7761188023604716, 0.7401227576280706) ,
+ rgb (0.8452981073195511, 0.7722633860104472, 0.7335636824054149) ,
+ rgb (0.843485292229337, 0.7683566039987018, 0.7269653699897204) ,
+ rgb (0.8416775076684515, 0.7644010120098295, 0.7203329938728462) ,
+ rgb (0.8398783988412087, 0.7603990719977968, 0.7136714781112923) ,
+ rgb (0.8380912347613196, 0.7563531486080863, 0.7069856139021298) ,
+ rgb (0.8363189884473793, 0.7522654895287526, 0.7002799902886496) ,
+ rgb (0.8345656905566583, 0.7481379479992134, 0.6935569764986385) ,
+ rgb (0.8328327718577798, 0.7439727181745988, 0.6868223587464855) ,
+ rgb (0.8311216352909631, 0.7397718464763862, 0.6800812520363146) ,
+ rgb (0.8294340781648147, 0.7355371221572935, 0.6733377200930191) ,
+ rgb (0.8277716072353446, 0.7312702332407809, 0.6665957020468297) ,
+ rgb (0.8261354971058026, 0.7269727551823826, 0.659859001562165) ,
+ rgb (0.8245268129450285, 0.7226461431208888, 0.653131379154226) ,
+ rgb (0.8229463511042843, 0.7182917733129006, 0.6464164243818421) ,
+ rgb (0.8213947205565636, 0.7139109141951604, 0.6397176669767276) ,
+ rgb (0.8198723065045529, 0.7095047497878748, 0.6330385704006711) ,
+ rgb (0.8183793116449822, 0.705074381896351, 0.626382454789333) ,
+ rgb (0.8169157577505589, 0.7006208301478398, 0.6197526063725792) ,
+ rgb (0.8154814662727948, 0.6961450550830809, 0.6131522120932265) ,
+ rgb (0.8140761104699334, 0.6916479479148213, 0.6065843782630862) ,
+ rgb (0.8126992203988149, 0.6871303371461888, 0.600052148204351) ,
+ rgb (0.8113501401176333, 0.6825930154624339, 0.5935584890905076) ,
+ rgb (0.8100280946652069, 0.6780367267397182, 0.5871062690808275) ,
+ rgb (0.8087321917008969, 0.6734621670219452, 0.5806983480557674) ,
+ rgb (0.8074614045096935, 0.6688700095398864, 0.5743374637345958) ,
+ rgb (0.8062146052692706, 0.6642608958528229, 0.5680262917864979) ,
+ rgb (0.804990547908103, 0.6596354502756416, 0.5617674511054698) ,
+ rgb (0.8037879253107763, 0.6549942654947263, 0.5555635086708381) ,
+ rgb (0.8026053114611295, 0.6503379374810385, 0.5494169158460365) ,
+ rgb (0.8014412429256005, 0.6456670345921877, 0.5433300863249918) ,
+ rgb (0.8002941538975398, 0.6409821330674986, 0.5373053518514104) ,
+ rgb (0.7991624518501963, 0.6362837937202919, 0.5313449594256143) ,
+ rgb (0.7980444781513664, 0.6315725822508955, 0.5254510814483478) ,
+ rgb (0.7969385471995161, 0.626849056792967, 0.5196258425240281) ,
+ rgb (0.7958429237958377, 0.6221137880845115, 0.5138712409190979) ,
+ rgb (0.7947558597265404, 0.617367344002207, 0.5081892121310299) ,
+ rgb (0.7936755969866496, 0.6126102933407219, 0.5025816129126943) ,
+ rgb (0.7926003430423745, 0.6078432208703702, 0.4970502062153201) ,
+ rgb (0.7915283284347561, 0.603066705931472, 0.49159667021646397) ,
+ rgb (0.7904577684772788, 0.5982813427706246, 0.48622257801969754) ,
+ rgb (0.7893873776625194, 0.5934875421745599, 0.48092913815357724) ,
+ rgb (0.7883151404562396, 0.5886860017356244, 0.4757179187907608) ,
+ rgb (0.7872390410818835, 0.5838774374455721, 0.47059039582133383) ,
+ rgb (0.7861573713233296, 0.5790624629815756, 0.465547782819184) ,
+ rgb (0.7850684501960684, 0.5742417003617839, 0.46059116206904965) ,
+ rgb (0.7839706083641448, 0.5694157832671042, 0.4557215474289206) ,
+ rgb (0.7828622526444091, 0.5645853311116688, 0.45093985823706345) ,
+ rgb (0.7817418047898184, 0.5597509805259486, 0.44624687186865436) ,
+ rgb (0.7806077474948377, 0.5549133574489061, 0.4416433242636464) ,
+ rgb (0.7794586273150664, 0.5500730841397727, 0.4371297985644476) ,
+ rgb (0.7782934580763312, 0.545230594884266, 0.432706647838971) ,
+ rgb (0.7771103295521099, 0.5403867491056124, 0.4283745037125848) ,
+ rgb (0.775907907306857, 0.5355421788246119, 0.42413367909988375) ,
+ rgb (0.774684947460632, 0.5306974938477673, 0.4199844035696376) ,
+ rgb (0.7734402182988989, 0.5258533209345156, 0.41592679539764366) ,
+ rgb (0.7721725722960555, 0.5210102658711383, 0.4119608998712287) ,
+ rgb (0.7708809196230247, 0.516168926434691, 0.40808667584648967) ,
+ rgb (0.7695642334401418, 0.5113298901696085, 0.4043039806968248) ,
+ rgb (0.768221765997353, 0.5064936237128791, 0.40061257089416885) ,
+ rgb (0.7668522895064389, 0.5016608471009063, 0.39701221751773474) ,
+ rgb (0.7654549259333051, 0.4968321290972723, 0.3935025400011538) ,
+ rgb (0.7640288560928866, 0.49200802533379656, 0.39008308392311997) ,
+ rgb (0.7625733355405261, 0.48718906673415824, 0.38675335037837993) ,
+ rgb (0.7610876378057071, 0.48237579130289127, 0.3835127572385229) ,
+ rgb (0.7595711110534006, 0.4775687122205708, 0.380360657784311) ,
+ rgb (0.7580232553845584, 0.4727682731566229, 0.3772963553109668) ,
+ rgb (0.7564434157714071, 0.4679750143794846, 0.37431909037543515) ,
+ rgb (0.7548310506695954, 0.46318942799460555, 0.3714280448394211) ,
+ rgb (0.7531856636904657, 0.45841199172949604, 0.3686223664223477) ,
+ rgb (0.7515068504589166, 0.45364314496866825, 0.36590112443835765) ,
+ rgb (0.7497942054717047, 0.4488833348154881, 0.3632633755836028) ,
+ rgb (0.7480473927555956, 0.44413297780351974, 0.36070813602540136) ,
+ rgb (0.7462661578916344, 0.439392450449735, 0.3582343914230064) ,
+ rgb (0.7444501867657067, 0.4346621718461711, 0.35584108091122535) ,
+ rgb (0.7425992159973317, 0.42994254036133867, 0.3535270924537459) ,
+ rgb (0.7407130161950609, 0.4252339389526239, 0.35129130890802607) ,
+ rgb (0.7387914002459927, 0.4205367299231533, 0.34913260148542435) ,
+ rgb (0.7368342217358587, 0.4158512585029011, 0.347049785207584) ,
+ rgb (0.7348413359856494, 0.4111778700451951, 0.3450416947080907) ,
+ rgb (0.7328127050626875, 0.4065168468778026, 0.3431071517341082) ,
+ rgb (0.7307482075484517, 0.401868526884681, 0.3412449533046818) ,
+ rgb (0.7286477385671655, 0.39723324476747235, 0.33945384341064017) ,
+ rgb (0.7265112290022755, 0.3926113126792577, 0.3377325942005665) ,
+ rgb (0.7243386564778159, 0.38800301593162145, 0.3360799596569183) ,
+ rgb (0.7221299918421461, 0.3834086450896306, 0.33449469983585844) ,
+ rgb (0.7198852149054985, 0.37882848839337313, 0.332975552002454) ,
+ rgb (0.7176044490813385, 0.3742627271068619, 0.3315213862095893) ,
+ rgb (0.7152876061484729, 0.3697117022522345, 0.3301308728723546) ,
+ rgb (0.7129346683977347, 0.36517570519856757, 0.3288027427038317) ,
+ rgb (0.7105456546582587, 0.36065500290840113, 0.3275357416278876) ,
+ rgb (0.7081205956842048, 0.356149855233803, 0.32632861885644465) ,
+ rgb (0.7056595112261009, 0.3516605297812094, 0.32518014084085567) ,
+ rgb (0.7031624945881415, 0.34718723719598, 0.32408913679491225) ,
+ rgb (0.700629624772421, 0.34273019305341756, 0.32305449047765694) ,
+ rgb (0.6980608153581771, 0.3382897632604862, 0.3220747885521809) ,
+ rgb (0.6954560834689112, 0.33386622163232865, 0.3211488430698579) ,
+ rgb (0.6928154484676493, 0.32945984647042675, 0.3202754315314667) ,
+ rgb (0.6901389321505248, 0.32507091815606004, 0.319453323328983) ,
+ rgb (0.6874265643516962, 0.32069970535138104, 0.3186813762227769) ,
+ rgb (0.6846785094249453, 0.3163463482122221, 0.31795870784057567) ,
+ rgb (0.6818945715094452, 0.31201133280550686, 0.3172838048924495) ,
+ rgb (0.6790747402815734, 0.30769497879760166, 0.31665545668946665) ,
+ rgb (0.6762189792440975, 0.30339762792450425, 0.3160724937230589) ,
+ rgb (0.6733272556481733, 0.29911962764489264, 0.3155337232398221) ,
+ rgb (0.670399595476762, 0.29486126309253047, 0.3150381395687221) ,
+ rgb (0.6674360376636913, 0.29062280081258873, 0.31458483752056837) ,
+ rgb (0.6644363246987884, 0.2864048361425618, 0.31417223403606975) ,
+ rgb (0.6614003753260178, 0.28220778870555907, 0.3137991292649849) ,
+ rgb (0.6583280801134499, 0.2780321095766563, 0.3134643447952643) ,
+ rgb (0.6552193260932713, 0.2738782665241015, 0.3131666792687211) ,
+ rgb (0.652074172902773, 0.269746505252367, 0.3129056060581917) ,
+ rgb (0.6488923016945825, 0.2656375533620908, 0.3126794181957019) ,
+ rgb (0.6456734938264543, 0.2615520316161528, 0.31248673753935263) ,
+ rgb (0.642417577481186, 0.257490519876798, 0.31232631707560987) ,
+ rgb (0.6391243387840212, 0.2534536546198314, 0.3121968961206398) ,
+ rgb (0.6357937104834237, 0.24944185818822678, 0.3120979395330059) ,
+ rgb (0.6324253485421027, 0.24545598775548677, 0.3120276597462445) ,
+ rgb (0.6290189201698587, 0.24149689191922535, 0.3119844719564572) ,
+ rgb (0.6255741650043496, 0.23756535071152696, 0.3119669831491227) ,
+ rgb (0.6220907982108261, 0.2336621873300741, 0.3119738327362739) ,
+ rgb (0.6185686525887719, 0.2297879924917992, 0.3120046383872893) ,
+ rgb (0.6150072323639137, 0.22594402043981826, 0.3120568068576574) ,
+ rgb (0.6114062072731884, 0.22213124697023234, 0.3121288139643524) ,
+ rgb (0.6077652399481865, 0.21835070166659282, 0.312219032918702) ,
+ rgb (0.6040840169673274, 0.21460331490206347, 0.31232652641170694) ,
+ rgb (0.6003621301041158, 0.21089030138947745, 0.3124493441041469) ,
+ rgb (0.5965991810912237, 0.207212956082026, 0.3125852303112123) ,
+ rgb (0.5927948053652026, 0.20357251410079796, 0.3127323483930494) ,
+ rgb (0.5889486193554471, 0.19997020971775276, 0.31288922211590126) ,
+ rgb (0.5850602439646688, 0.19640737049066315, 0.3130540116373273) ,
+ rgb (0.5811293276158656, 0.19288548904692518, 0.3132239939418394) ,
+ rgb (0.5771555081299204, 0.18940601489760422, 0.3133970433357208) ,
+ rgb (0.5731384575410787, 0.18597036007065024, 0.3135712686852) ,
+ rgb (0.5690778478401143, 0.18258004462335425, 0.3137444095679653) ,
+ rgb (0.564973435290177, 0.17923664950367169, 0.3139136046337036) ,
+ rgb (0.5608249903911717, 0.17594170887918095, 0.31407639883970623) ,
+ rgb (0.5566322903496934, 0.17269677158182117, 0.31423043195101424) ,
+ rgb (0.5523952157271191, 0.16950338809328983, 0.3143729155461537) ,
+ rgb (0.5481137003346762, 0.1663630904279047, 0.3145010299091471) ,
+ rgb (0.5437877131360856, 0.16327738551419116, 0.31461204226295625) ,
+ rgb (0.5394173664919906, 0.16024769309971934, 0.31470295028655965) ,
+ rgb (0.5350027976174474, 0.15727540775107324, 0.3147708520739653) ,
+ rgb (0.5305442048985645, 0.15436183633886777, 0.3148129978918713) ,
+ rgb (0.5260418962547748, 0.15150818660835483, 0.31482653406646727) ,
+ rgb (0.5214965286322996, 0.14871544765633712, 0.3148076795453443) ,
+ rgb (0.5169084880054446, 0.14598463068714407, 0.3147540759228004) ,
+ rgb (0.5122783510532176, 0.14331656120063752, 0.3146630922831542) ,
+ rgb (0.5076068118105369, 0.14071192654913128, 0.3145320012008257) ,
+ rgb (0.5028952497497061, 0.13817086581280427, 0.3143566215383367) ,
+ rgb (0.4981443546207415, 0.13569380302451714, 0.314135190862664) ,
+ rgb (0.49335504375145617, 0.13328091630401023, 0.31386561956734976) ,
+ rgb (0.48852847371852987, 0.13093210934893723, 0.31354553695453014) ,
+ rgb (0.48366628618847957, 0.1286467902201389, 0.31317188565991266) ,
+ rgb (0.47877034239726296, 0.12642401401409453, 0.3127417273582196) ,
+ rgb (0.473841437035254, 0.12426354237989065, 0.31225470169927194) ,
+ rgb (0.46888111384598413, 0.12216445576414045, 0.31170911458932665) ,
+ rgb (0.46389102840284874, 0.12012561256850712, 0.31110343446582983) ,
+ rgb (0.45887288947308297, 0.11814571137706886, 0.3104363697903881) ,
+ rgb (0.4538300508699989, 0.11622183788331528, 0.3097044124984492) ,
+ rgb (0.4487629917317482, 0.1143535557462255, 0.30890905921943196) ,
+ rgb (0.44367358645071275, 0.11253912421257944, 0.3080497309546545) ,
+ rgb (0.4385637818793154, 0.11077667828375456, 0.30712600062348083) ,
+ rgb (0.4334355841041439, 0.1090642347484701, 0.3061376792828915) ,
+ rgb (0.4282910131578975, 0.1073997763055258, 0.30508479060294547) ,
+ rgb (0.42313214269556043, 0.10578120994917611, 0.3039675809469457) ,
+ rgb (0.41796105205173684, 0.10420644885760968, 0.3027865203963184) ,
+ rgb (0.41277985630360303, 0.1026734006932461, 0.3015422643746897) ,
+ rgb (0.4075912039268871, 0.10117945586419633, 0.300235195077286) ,
+ rgb (0.40239692379737496, 0.09972293031495055, 0.2988667436973397) ,
+ rgb (0.39719876876325577, 0.09830232096827862, 0.2974385647628578) ,
+ rgb (0.39199887556812907, 0.09691583650296684, 0.2959521200550908) ,
+ rgb (0.3867993907954417, 0.09556181960083443, 0.29440901248173756) ,
+ rgb (0.38160247377467543, 0.09423873126371218, 0.2928110750626949) ,
+ rgb (0.3764103053221462, 0.09294519809377791, 0.2911602415731392) ,
+ rgb (0.3712250843130934, 0.09167999748026273, 0.2894586539763317) ,
+ rgb (0.36605031412464006, 0.0904406854276979, 0.2877077458811747) ,
+ rgb (0.36088752387578377, 0.0892271943627452, 0.28591050458531014) ,
+ rgb (0.35573889947341125, 0.08803897435024335, 0.2840695897279818) ,
+ rgb (0.3506067824603248, 0.08687555176033529, 0.28218770540182386) ,
+ rgb (0.345493557138718, 0.08573665496512634, 0.28026769921081435) ,
+ rgb (0.34040164359597463, 0.08462223619170267, 0.27831254595259397) ,
+ rgb (0.3353335322445545, 0.08353243411900396, 0.2763253435679004) ,
+ rgb (0.3302917447118144, 0.08246763389003825, 0.27430929404579435) ,
+ rgb (0.3252788886040126, 0.08142839007654609, 0.27226772884656186) ,
+ rgb (0.3202998655799406, 0.08041299473755484, 0.2702032289303951) ,
+ rgb (0.3153587000920581, 0.07942099731524319, 0.2681190407694196) ,
+ rgb (0.31045520848595526, 0.07845687167618218, 0.2660200572779356) ,
+ rgb (0.30559226007249934, 0.07752196310753731, 0.2639100669211966) ,
+ rgb (0.3007727681291869, 0.07661782433616476, 0.2617929409781967) ,
+ rgb (0.2960004726065818, 0.07574533600095842, 0.25967245030364566) ,
+ rgb (0.29128515387578635, 0.0748990498474667, 0.25755101595750435) ,
+ rgb (0.28662309235899847, 0.07408846082680887, 0.2554347867371703) ,
+ rgb (0.2820174629736694, 0.07331569321404097, 0.25332800295084507) ,
+ rgb (0.277471508091428, 0.07258296989925478, 0.2512349399594277) ,
+ rgb (0.272997015188973, 0.07188355544616351, 0.2491584709323293) ,
+ rgb (0.2685909594817286, 0.07122671627792246, 0.24710443563450618) ,
+ rgb (0.2642551220706094, 0.07061659562299544, 0.24507758869355967) ,
+ rgb (0.25999463887892144, 0.07005385560386188, 0.24308218808684579) ,
+ rgb (0.2558213554748177, 0.06953231029187984, 0.24112190491594204) ,
+ rgb (0.25172899728289466, 0.0690646308260355, 0.23920260612763083) ,
+ rgb (0.247720929905011, 0.06865333790948652, 0.2373288009471749) ,
+ rgb (0.2438114972024792, 0.06828985152901187, 0.23550427698321885) ,
+ rgb (0.23999586188690308, 0.06798502996477995, 0.23373434258507808) ,
+ rgb (0.23627495835774248, 0.06774359820987802, 0.23202360805926608) ,
+ rgb (0.23266299920501882, 0.06755710438847978, 0.23037617493752832) ,
+ rgb (0.2291562027859284, 0.06743473087115257, 0.22879681433956656) ,
+ rgb (0.2257539681670791, 0.06738213230014747, 0.22728984778098055) ,
+ rgb (0.22247308588973624, 0.06738821405309284, 0.22585960379408354) ,
+ rgb (0.21930503925136402, 0.06746578636294004, 0.22451023616807558) ,
+ rgb (0.21625279838647882, 0.06761633027051639, 0.22324568672294431) ,
+ rgb (0.2133313859647627, 0.06783014842602667, 0.2220704321302429) ,
+ rgb (0.21052882914958676, 0.06812195249816172, 0.22098759107715404) ,
+ rgb (0.20785704662965598, 0.06848439879702528, 0.22000133917653536) ,
+ rgb (0.20531725273301316, 0.06891959226639757, 0.21911516689288835) ,
+ rgb (0.20290365333558247, 0.06943524858045896, 0.21833167885096033) ,
+ rgb (0.20058760685133747, 0.07006457614998421, 0.21762721310371608) ,
+ rgb (0.19819343656336558, 0.07097462525273879, 0.21690975060032436) ,
+ rgb (0.19571853588267552, 0.07215778103960274, 0.21617499187076789) ,
+ rgb (0.1931548636579131, 0.07360681904011795, 0.21542362939081539) ,
+ rgb (0.19049578401722037, 0.07531127841678764, 0.2146562337112265) ,
+ rgb (0.18774482037046955, 0.07725158846803931, 0.21387448578597812) ,
+ rgb (0.18488035509396164, 0.07942573027972388, 0.21307651648984993)
+});
+
+
+list_data viridis = list_data(new pen[] {
+ rgb (0.267004, 0.004874, 0.329415) ,
+ rgb (0.26851, 0.009605, 0.335427) ,
+ rgb (0.269944, 0.014625, 0.341379) ,
+ rgb (0.271305, 0.019942, 0.347269) ,
+ rgb (0.272594, 0.025563, 0.353093) ,
+ rgb (0.273809, 0.031497, 0.358853) ,
+ rgb (0.274952, 0.037752, 0.364543) ,
+ rgb (0.276022, 0.044167, 0.370164) ,
+ rgb (0.277018, 0.050344, 0.375715) ,
+ rgb (0.277941, 0.056324, 0.381191) ,
+ rgb (0.278791, 0.062145, 0.386592) ,
+ rgb (0.279566, 0.067836, 0.391917) ,
+ rgb (0.280267, 0.073417, 0.397163) ,
+ rgb (0.280894, 0.078907, 0.402329) ,
+ rgb (0.281446, 0.08432, 0.407414) ,
+ rgb (0.281924, 0.089666, 0.412415) ,
+ rgb (0.282327, 0.094955, 0.417331) ,
+ rgb (0.282656, 0.100196, 0.42216) ,
+ rgb (0.28291, 0.105393, 0.426902) ,
+ rgb (0.283091, 0.110553, 0.431554) ,
+ rgb (0.283197, 0.11568, 0.436115) ,
+ rgb (0.283229, 0.120777, 0.440584) ,
+ rgb (0.283187, 0.125848, 0.44496) ,
+ rgb (0.283072, 0.130895, 0.449241) ,
+ rgb (0.282884, 0.13592, 0.453427) ,
+ rgb (0.282623, 0.140926, 0.457517) ,
+ rgb (0.28229, 0.145912, 0.46151) ,
+ rgb (0.281887, 0.150881, 0.465405) ,
+ rgb (0.281412, 0.155834, 0.469201) ,
+ rgb (0.280868, 0.160771, 0.472899) ,
+ rgb (0.280255, 0.165693, 0.476498) ,
+ rgb (0.279574, 0.170599, 0.479997) ,
+ rgb (0.278826, 0.17549, 0.483397) ,
+ rgb (0.278012, 0.180367, 0.486697) ,
+ rgb (0.277134, 0.185228, 0.489898) ,
+ rgb (0.276194, 0.190074, 0.493001) ,
+ rgb (0.275191, 0.194905, 0.496005) ,
+ rgb (0.274128, 0.199721, 0.498911) ,
+ rgb (0.273006, 0.20452, 0.501721) ,
+ rgb (0.271828, 0.209303, 0.504434) ,
+ rgb (0.270595, 0.214069, 0.507052) ,
+ rgb (0.269308, 0.218818, 0.509577) ,
+ rgb (0.267968, 0.223549, 0.512008) ,
+ rgb (0.26658, 0.228262, 0.514349) ,
+ rgb (0.265145, 0.232956, 0.516599) ,
+ rgb (0.263663, 0.237631, 0.518762) ,
+ rgb (0.262138, 0.242286, 0.520837) ,
+ rgb (0.260571, 0.246922, 0.522828) ,
+ rgb (0.258965, 0.251537, 0.524736) ,
+ rgb (0.257322, 0.25613, 0.526563) ,
+ rgb (0.255645, 0.260703, 0.528312) ,
+ rgb (0.253935, 0.265254, 0.529983) ,
+ rgb (0.252194, 0.269783, 0.531579) ,
+ rgb (0.250425, 0.27429, 0.533103) ,
+ rgb (0.248629, 0.278775, 0.534556) ,
+ rgb (0.246811, 0.283237, 0.535941) ,
+ rgb (0.244972, 0.287675, 0.53726) ,
+ rgb (0.243113, 0.292092, 0.538516) ,
+ rgb (0.241237, 0.296485, 0.539709) ,
+ rgb (0.239346, 0.300855, 0.540844) ,
+ rgb (0.237441, 0.305202, 0.541921) ,
+ rgb (0.235526, 0.309527, 0.542944) ,
+ rgb (0.233603, 0.313828, 0.543914) ,
+ rgb (0.231674, 0.318106, 0.544834) ,
+ rgb (0.229739, 0.322361, 0.545706) ,
+ rgb (0.227802, 0.326594, 0.546532) ,
+ rgb (0.225863, 0.330805, 0.547314) ,
+ rgb (0.223925, 0.334994, 0.548053) ,
+ rgb (0.221989, 0.339161, 0.548752) ,
+ rgb (0.220057, 0.343307, 0.549413) ,
+ rgb (0.21813, 0.347432, 0.550038) ,
+ rgb (0.21621, 0.351535, 0.550627) ,
+ rgb (0.214298, 0.355619, 0.551184) ,
+ rgb (0.212395, 0.359683, 0.55171) ,
+ rgb (0.210503, 0.363727, 0.552206) ,
+ rgb (0.208623, 0.367752, 0.552675) ,
+ rgb (0.206756, 0.371758, 0.553117) ,
+ rgb (0.204903, 0.375746, 0.553533) ,
+ rgb (0.203063, 0.379716, 0.553925) ,
+ rgb (0.201239, 0.38367, 0.554294) ,
+ rgb (0.19943, 0.387607, 0.554642) ,
+ rgb (0.197636, 0.391528, 0.554969) ,
+ rgb (0.19586, 0.395433, 0.555276) ,
+ rgb (0.1941, 0.399323, 0.555565) ,
+ rgb (0.192357, 0.403199, 0.555836) ,
+ rgb (0.190631, 0.407061, 0.556089) ,
+ rgb (0.188923, 0.41091, 0.556326) ,
+ rgb (0.187231, 0.414746, 0.556547) ,
+ rgb (0.185556, 0.41857, 0.556753) ,
+ rgb (0.183898, 0.422383, 0.556944) ,
+ rgb (0.182256, 0.426184, 0.55712) ,
+ rgb (0.180629, 0.429975, 0.557282) ,
+ rgb (0.179019, 0.433756, 0.55743) ,
+ rgb (0.177423, 0.437527, 0.557565) ,
+ rgb (0.175841, 0.44129, 0.557685) ,
+ rgb (0.174274, 0.445044, 0.557792) ,
+ rgb (0.172719, 0.448791, 0.557885) ,
+ rgb (0.171176, 0.45253, 0.557965) ,
+ rgb (0.169646, 0.456262, 0.55803) ,
+ rgb (0.168126, 0.459988, 0.558082) ,
+ rgb (0.166617, 0.463708, 0.558119) ,
+ rgb (0.165117, 0.467423, 0.558141) ,
+ rgb (0.163625, 0.471133, 0.558148) ,
+ rgb (0.162142, 0.474838, 0.55814) ,
+ rgb (0.160665, 0.47854, 0.558115) ,
+ rgb (0.159194, 0.482237, 0.558073) ,
+ rgb (0.157729, 0.485932, 0.558013) ,
+ rgb (0.15627, 0.489624, 0.557936) ,
+ rgb (0.154815, 0.493313, 0.55784) ,
+ rgb (0.153364, 0.497, 0.557724) ,
+ rgb (0.151918, 0.500685, 0.557587) ,
+ rgb (0.150476, 0.504369, 0.55743) ,
+ rgb (0.149039, 0.508051, 0.55725) ,
+ rgb (0.147607, 0.511733, 0.557049) ,
+ rgb (0.14618, 0.515413, 0.556823) ,
+ rgb (0.144759, 0.519093, 0.556572) ,
+ rgb (0.143343, 0.522773, 0.556295) ,
+ rgb (0.141935, 0.526453, 0.555991) ,
+ rgb (0.140536, 0.530132, 0.555659) ,
+ rgb (0.139147, 0.533812, 0.555298) ,
+ rgb (0.13777, 0.537492, 0.554906) ,
+ rgb (0.136408, 0.541173, 0.554483) ,
+ rgb (0.135066, 0.544853, 0.554029) ,
+ rgb (0.133743, 0.548535, 0.553541) ,
+ rgb (0.132444, 0.552216, 0.553018) ,
+ rgb (0.131172, 0.555899, 0.552459) ,
+ rgb (0.129933, 0.559582, 0.551864) ,
+ rgb (0.128729, 0.563265, 0.551229) ,
+ rgb (0.127568, 0.566949, 0.550556) ,
+ rgb (0.126453, 0.570633, 0.549841) ,
+ rgb (0.125394, 0.574318, 0.549086) ,
+ rgb (0.124395, 0.578002, 0.548287) ,
+ rgb (0.123463, 0.581687, 0.547445) ,
+ rgb (0.122606, 0.585371, 0.546557) ,
+ rgb (0.121831, 0.589055, 0.545623) ,
+ rgb (0.121148, 0.592739, 0.544641) ,
+ rgb (0.120565, 0.596422, 0.543611) ,
+ rgb (0.120092, 0.600104, 0.54253) ,
+ rgb (0.119738, 0.603785, 0.5414) ,
+ rgb (0.119512, 0.607464, 0.540218) ,
+ rgb (0.119423, 0.611141, 0.538982) ,
+ rgb (0.119483, 0.614817, 0.537692) ,
+ rgb (0.119699, 0.61849, 0.536347) ,
+ rgb (0.120081, 0.622161, 0.534946) ,
+ rgb (0.120638, 0.625828, 0.533488) ,
+ rgb (0.12138, 0.629492, 0.531973) ,
+ rgb (0.122312, 0.633153, 0.530398) ,
+ rgb (0.123444, 0.636809, 0.528763) ,
+ rgb (0.12478, 0.640461, 0.527068) ,
+ rgb (0.126326, 0.644107, 0.525311) ,
+ rgb (0.128087, 0.647749, 0.523491) ,
+ rgb (0.130067, 0.651384, 0.521608) ,
+ rgb (0.132268, 0.655014, 0.519661) ,
+ rgb (0.134692, 0.658636, 0.517649) ,
+ rgb (0.137339, 0.662252, 0.515571) ,
+ rgb (0.14021, 0.665859, 0.513427) ,
+ rgb (0.143303, 0.669459, 0.511215) ,
+ rgb (0.146616, 0.67305, 0.508936) ,
+ rgb (0.150148, 0.676631, 0.506589) ,
+ rgb (0.153894, 0.680203, 0.504172) ,
+ rgb (0.157851, 0.683765, 0.501686) ,
+ rgb (0.162016, 0.687316, 0.499129) ,
+ rgb (0.166383, 0.690856, 0.496502) ,
+ rgb (0.170948, 0.694384, 0.493803) ,
+ rgb (0.175707, 0.6979, 0.491033) ,
+ rgb (0.180653, 0.701402, 0.488189) ,
+ rgb (0.185783, 0.704891, 0.485273) ,
+ rgb (0.19109, 0.708366, 0.482284) ,
+ rgb (0.196571, 0.711827, 0.479221) ,
+ rgb (0.202219, 0.715272, 0.476084) ,
+ rgb (0.20803, 0.718701, 0.472873) ,
+ rgb (0.214, 0.722114, 0.469588) ,
+ rgb (0.220124, 0.725509, 0.466226) ,
+ rgb (0.226397, 0.728888, 0.462789) ,
+ rgb (0.232815, 0.732247, 0.459277) ,
+ rgb (0.239374, 0.735588, 0.455688) ,
+ rgb (0.24607, 0.73891, 0.452024) ,
+ rgb (0.252899, 0.742211, 0.448284) ,
+ rgb (0.259857, 0.745492, 0.444467) ,
+ rgb (0.266941, 0.748751, 0.440573) ,
+ rgb (0.274149, 0.751988, 0.436601) ,
+ rgb (0.281477, 0.755203, 0.432552) ,
+ rgb (0.288921, 0.758394, 0.428426) ,
+ rgb (0.296479, 0.761561, 0.424223) ,
+ rgb (0.304148, 0.764704, 0.419943) ,
+ rgb (0.311925, 0.767822, 0.415586) ,
+ rgb (0.319809, 0.770914, 0.411152) ,
+ rgb (0.327796, 0.77398, 0.40664) ,
+ rgb (0.335885, 0.777018, 0.402049) ,
+ rgb (0.344074, 0.780029, 0.397381) ,
+ rgb (0.35236, 0.783011, 0.392636) ,
+ rgb (0.360741, 0.785964, 0.387814) ,
+ rgb (0.369214, 0.788888, 0.382914) ,
+ rgb (0.377779, 0.791781, 0.377939) ,
+ rgb (0.386433, 0.794644, 0.372886) ,
+ rgb (0.395174, 0.797475, 0.367757) ,
+ rgb (0.404001, 0.800275, 0.362552) ,
+ rgb (0.412913, 0.803041, 0.357269) ,
+ rgb (0.421908, 0.805774, 0.35191) ,
+ rgb (0.430983, 0.808473, 0.346476) ,
+ rgb (0.440137, 0.811138, 0.340967) ,
+ rgb (0.449368, 0.813768, 0.335384) ,
+ rgb (0.458674, 0.816363, 0.329727) ,
+ rgb (0.468053, 0.818921, 0.323998) ,
+ rgb (0.477504, 0.821444, 0.318195) ,
+ rgb (0.487026, 0.823929, 0.312321) ,
+ rgb (0.496615, 0.826376, 0.306377) ,
+ rgb (0.506271, 0.828786, 0.300362) ,
+ rgb (0.515992, 0.831158, 0.294279) ,
+ rgb (0.525776, 0.833491, 0.288127) ,
+ rgb (0.535621, 0.835785, 0.281908) ,
+ rgb (0.545524, 0.838039, 0.275626) ,
+ rgb (0.555484, 0.840254, 0.269281) ,
+ rgb (0.565498, 0.84243, 0.262877) ,
+ rgb (0.575563, 0.844566, 0.256415) ,
+ rgb (0.585678, 0.846661, 0.249897) ,
+ rgb (0.595839, 0.848717, 0.243329) ,
+ rgb (0.606045, 0.850733, 0.236712) ,
+ rgb (0.616293, 0.852709, 0.230052) ,
+ rgb (0.626579, 0.854645, 0.223353) ,
+ rgb (0.636902, 0.856542, 0.21662) ,
+ rgb (0.647257, 0.8584, 0.209861) ,
+ rgb (0.657642, 0.860219, 0.203082) ,
+ rgb (0.668054, 0.861999, 0.196293) ,
+ rgb (0.678489, 0.863742, 0.189503) ,
+ rgb (0.688944, 0.865448, 0.182725) ,
+ rgb (0.699415, 0.867117, 0.175971) ,
+ rgb (0.709898, 0.868751, 0.169257) ,
+ rgb (0.720391, 0.87035, 0.162603) ,
+ rgb (0.730889, 0.871916, 0.156029) ,
+ rgb (0.741388, 0.873449, 0.149561) ,
+ rgb (0.751884, 0.874951, 0.143228) ,
+ rgb (0.762373, 0.876424, 0.137064) ,
+ rgb (0.772852, 0.877868, 0.131109) ,
+ rgb (0.783315, 0.879285, 0.125405) ,
+ rgb (0.79376, 0.880678, 0.120005) ,
+ rgb (0.804182, 0.882046, 0.114965) ,
+ rgb (0.814576, 0.883393, 0.110347) ,
+ rgb (0.82494, 0.88472, 0.106217) ,
+ rgb (0.83527, 0.886029, 0.102646) ,
+ rgb (0.845561, 0.887322, 0.099702) ,
+ rgb (0.85581, 0.888601, 0.097452) ,
+ rgb (0.866013, 0.889868, 0.095953) ,
+ rgb (0.876168, 0.891125, 0.09525) ,
+ rgb (0.886271, 0.892374, 0.095374) ,
+ rgb (0.89632, 0.893616, 0.096335) ,
+ rgb (0.906311, 0.894855, 0.098125) ,
+ rgb (0.916242, 0.896091, 0.100717) ,
+ rgb (0.926106, 0.89733, 0.104071) ,
+ rgb (0.935904, 0.89857, 0.108131) ,
+ rgb (0.945636, 0.899815, 0.112838) ,
+ rgb (0.9553, 0.901065, 0.118128) ,
+ rgb (0.964894, 0.902323, 0.123941) ,
+ rgb (0.974417, 0.90359, 0.130215) ,
+ rgb (0.983868, 0.904867, 0.136897) ,
+ rgb (0.993248, 0.906157, 0.143936)
+});
+
+
diff --git a/graphics/asymptote/base/contour.asy b/graphics/asymptote/base/contour.asy
new file mode 100644
index 0000000000..fbb4cd1c71
--- /dev/null
+++ b/graphics/asymptote/base/contour.asy
@@ -0,0 +1,682 @@
+// Contour routines written by Radoslav Marinov and John Bowman.
+
+import graph_settings;
+
+real eps=10000*realEpsilon;
+
+// 1
+// 6 +-------------------+ 5
+// | \ / |
+// | \ / |
+// | \ / |
+// | \ / |
+// 2 | X | 0
+// | / \ |
+// | / \ |
+// | / \ |
+// | / \ |
+// 7 +-------------------+ 4 or 8
+// 3
+
+private struct segment
+{
+ bool active;
+ pair a,b; // Endpoints; a is always an edge point if one exists.
+ int c; // Contour value.
+ int edge; // -1: interior, 0 to 3: edge,
+ // 4-8: single-vertex edge, 9: double-vertex edge.
+}
+
+// Case 1: line passes through two vertices of a triangle
+private segment case1(pair p0, pair p1, int edge)
+{
+ // Will cause a duplicate guide; luckily case1 is rare
+ segment rtrn;
+ rtrn.active=true;
+ rtrn.a=p0;
+ rtrn.b=p1;
+ rtrn.edge=edge;
+ return rtrn;
+}
+
+// Case 2: line passes through a vertex and a side of a triangle
+// (the first vertex passed and the side between the other two)
+private segment case2(pair p0, pair p1, pair p2,
+ real v0, real v1, real v2, int edge)
+{
+ segment rtrn;
+ pair val=interp(p1,p2,abs(v1/(v2-v1)));
+ rtrn.active=true;
+ if(edge < 4) {
+ rtrn.a=val;
+ rtrn.b=p0;
+ } else {
+ rtrn.a=p0;
+ rtrn.b=val;
+ }
+ rtrn.edge=edge;
+ return rtrn;
+}
+
+// Case 3: line passes through two sides of a triangle
+// (through the sides formed by the first & second, and second & third
+// vertices)
+private segment case3(pair p0, pair p1, pair p2,
+ real v0, real v1, real v2, int edge=-1)
+{
+ segment rtrn;
+ rtrn.active=true;
+ rtrn.a=interp(p1,p0,abs(v1/(v0-v1)));
+ rtrn.b=interp(p1,p2,abs(v1/(v2-v1)));
+ rtrn.edge=edge;
+ return rtrn;
+}
+
+// Check if a line passes through a triangle, and draw the required line.
+private segment checktriangle(pair p0, pair p1, pair p2,
+ real v0, real v1, real v2, int edge=-1)
+{
+ // default null return
+ static segment dflt;
+
+ real eps=eps*max(abs(v0),abs(v1),abs(v2));
+
+ if(v0 < -eps) {
+ if(v1 < -eps) {
+ if(v2 < -eps) return dflt; // nothing to do
+ else if(v2 <= eps) return dflt; // nothing to do
+ else return case3(p0,p2,p1,v0,v2,v1);
+ } else if(v1 <= eps) {
+ if(v2 < -eps) return dflt; // nothing to do
+ else if(v2 <= eps) return case1(p1,p2,5+edge);
+ else return case2(p1,p0,p2,v1,v0,v2,5+edge);
+ } else {
+ if(v2 < -eps) return case3(p0,p1,p2,v0,v1,v2,edge);
+ else if(v2 <= eps)
+ return case2(p2,p0,p1,v2,v0,v1,edge);
+ else return case3(p1,p0,p2,v1,v0,v2,edge);
+ }
+ } else if(v0 <= eps) {
+ if(v1 < -eps) {
+ if(v2 < -eps) return dflt; // nothing to do
+ else if(v2 <= eps) return case1(p0,p2,4+edge);
+ else return case2(p0,p1,p2,v0,v1,v2,4+edge);
+ } else if(v1 <= eps) {
+ if(v2 < -eps) return case1(p0,p1,9);
+ else if(v2 <= eps) return dflt; // use finer partitioning.
+ else return case1(p0,p1,9);
+ } else {
+ if(v2 < -eps) return case2(p0,p1,p2,v0,v1,v2,4+edge);
+ else if(v2 <= eps) return case1(p0,p2,4+edge);
+ else return dflt; // nothing to do
+ }
+ } else {
+ if(v1 < -eps) {
+ if(v2 < -eps) return case3(p1,p0,p2,v1,v0,v2,edge);
+ else if(v2 <= eps)
+ return case2(p2,p0,p1,v2,v0,v1,edge);
+ else return case3(p0,p1,p2,v0,v1,v2,edge);
+ } else if(v1 <= eps) {
+ if(v2 < -eps) return case2(p1,p0,p2,v1,v0,v2,5+edge);
+ else if(v2 <= eps) return case1(p1,p2,5+edge);
+ else return dflt; // nothing to do
+ } else {
+ if(v2 < -eps) return case3(p0,p2,p1,v0,v2,v1);
+ else if(v2 <= eps) return dflt; // nothing to do
+ else return dflt; // nothing to do
+ }
+ }
+}
+
+// Collect connecting path segments.
+private void collect(pair[][][] points, real[] c)
+{
+ // use to reverse an array, omitting the first point
+ int[] reverseF(int n) {return sequence(new int(int x){return n-1-x;},n-1);}
+ // use to reverse an array, omitting the last point
+ int[] reverseL(int n) {return sequence(new int(int x){return n-2-x;},n-1);}
+
+ for(int cnt=0; cnt < c.length; ++cnt) {
+ pair[][] gdscnt=points[cnt];
+ for(int i=0; i < gdscnt.length; ++i) {
+ pair[] gig=gdscnt[i];
+ int Li=gig.length;
+ for(int j=i+1; j < gdscnt.length; ++j) {
+ pair[] gjg=gdscnt[j];
+ int Lj=gjg.length;
+ if(abs(gig[0]-gjg[0]) < eps) {
+ gdscnt[j]=gjg[reverseF(Lj)];
+ gdscnt[j].append(gig);
+ gdscnt.delete(i);
+ --i;
+ break;
+ } else if(abs(gig[0]-gjg[Lj-1]) < eps) {
+ gig.delete(0);
+ gdscnt[j].append(gig);
+ gdscnt.delete(i);
+ --i;
+ break;
+ } else if(abs(gig[Li-1]-gjg[0]) < eps) {
+ gjg.delete(0);
+ gig.append(gjg);
+ gdscnt[j]=gig;
+ gdscnt.delete(i);
+ --i;
+ break;
+ } else if(abs(gig[Li-1]-gjg[Lj-1]) < eps) {
+ gig.append(gjg[reverseL(Lj)]);
+ gdscnt[j]=gig;
+ gdscnt.delete(i);
+ --i;
+ break;
+ }
+ }
+ }
+ }
+}
+
+// Join path segments.
+private guide[][] connect(pair[][][] points, real[] c, interpolate join)
+{
+ // set up return value
+ guide[][] result=new guide[c.length][];
+ for(int cnt=0; cnt < c.length; ++cnt) {
+ pair[][] pointscnt=points[cnt];
+ guide[] resultcnt=result[cnt]=new guide[pointscnt.length];
+ for(int i=0; i < pointscnt.length; ++i) {
+ pair[] pts=pointscnt[i];
+ guide gd;
+ if(pts.length > 0) {
+ if(pts.length > 1 && abs(pts[0]-pts[pts.length-1]) < eps) {
+ guide[] g=sequence(new guide(int i) {
+ return pts[i];
+ },pts.length-1);
+ g.push(cycle);
+ gd=join(...g);
+ } else
+ gd=join(...sequence(new guide(int i) {
+ return pts[i];
+ },pts.length));
+ }
+ resultcnt[i]=gd;
+ }
+ }
+ return result;
+}
+
+
+// Return contour guides for a 2D data array.
+// z: two-dimensional array of nonoverlapping mesh points
+// f: two-dimensional array of corresponding f(z) data values
+// midpoint: optional array containing values of f at cell midpoints
+// c: array of contour values
+// join: interpolation operator (e.g. operator -- or operator ..)
+guide[][] contour(pair[][] z, real[][] f,
+ real[][] midpoint=new real[][], real[] c,
+ interpolate join=operator --)
+{
+ int nx=z.length-1;
+ if(nx == 0)
+ abort("array z must have length >= 2");
+ int ny=z[0].length-1;
+ if(ny == 0)
+ abort("array z[0] must have length >= 2");
+
+ c=sort(c);
+ bool midpoints=midpoint.length > 0;
+
+ segment segments[][][]=new segment[nx][ny][];
+
+ // go over region a rectangle at a time
+ for(int i=0; i < nx; ++i) {
+ pair[] zi=z[i];
+ pair[] zp=z[i+1];
+ real[] fi=f[i];
+ real[] fp=f[i+1];
+ real[] midpointi;
+ if(midpoints) midpointi=midpoint[i];
+ segment[][] segmentsi=segments[i];
+ for(int j=0; j < ny; ++j) {
+ segment[] segmentsij=segmentsi[j];
+
+ // define points
+ pair bleft=zi[j];
+ pair bright=zp[j];
+ pair tleft=zi[j+1];
+ pair tright=zp[j+1];
+ pair middle=0.25*(bleft+bright+tleft+tright);
+
+ real f00=fi[j];
+ real f01=fi[j+1];
+ real f10=fp[j];
+ real f11=fp[j+1];
+ real fmm=midpoints ? midpoint[i][j] : 0.25*(f00+f01+f10+f11);
+
+ // optimization: we make sure we don't work with empty rectangles
+ int checkcell(int cnt) {
+ real C=c[cnt];
+ real vertdat0=f00-C; // bottom-left vertex
+ real vertdat1=f10-C; // bottom-right vertex
+ real vertdat2=f01-C; // top-left vertex
+ real vertdat3=f11-C; // top-right vertex
+
+ // optimization: we make sure we don't work with empty rectangles
+ int countm=0;
+ int countz=0;
+ int countp=0;
+
+ void check(real vertdat) {
+ if(vertdat < -eps) ++countm;
+ else {
+ if(vertdat <= eps) ++countz;
+ else ++countp;
+ }
+ }
+
+ check(vertdat0);
+ check(vertdat1);
+ check(vertdat2);
+ check(vertdat3);
+
+ if(countm == 4) return 1; // nothing to do
+ if(countp == 4) return -1; // nothing to do
+ if((countm == 3 || countp == 3) && countz == 1) return 0;
+
+ // go through the triangles
+
+ void addseg(segment seg) {
+ if(seg.active) {
+ seg.c=cnt;
+ segmentsij.push(seg);
+ }
+ }
+ real vertdat4=fmm-C;
+ addseg(checktriangle(bright,tright,middle,
+ vertdat1,vertdat3,vertdat4,0));
+ addseg(checktriangle(tright,tleft,middle,
+ vertdat3,vertdat2,vertdat4,1));
+ addseg(checktriangle(tleft,bleft,middle,
+ vertdat2,vertdat0,vertdat4,2));
+ addseg(checktriangle(bleft,bright,middle,
+ vertdat0,vertdat1,vertdat4,3));
+ return 0;
+ }
+
+ void process(int l, int u) {
+ if(l >= u) return;
+ int i=quotient(l+u,2);
+ int sign=checkcell(i);
+ if(sign == -1) process(i+1,u);
+ else if(sign == 1) process(l,i);
+ else {
+ process(l,i);
+ process(i+1,u);
+ }
+ }
+
+ process(0,c.length);
+ }
+ }
+
+ // set up return value
+ pair[][][] points=new pair[c.length][][];
+
+ for(int i=0; i < nx; ++i) {
+ segment[][] segmentsi=segments[i];
+ for(int j=0; j < ny; ++j) {
+ segment[] segmentsij=segmentsi[j];
+ for(int k=0; k < segmentsij.length; ++k) {
+ segment C=segmentsij[k];
+
+ if(!C.active) continue;
+
+ pair[] g=new pair[] {C.a,C.b};
+ segmentsij[k].active=false;
+
+ int forward(int I, int J, bool first=true) {
+ if(I >= 0 && I < nx && J >= 0 && J < ny) {
+ segment[] segmentsIJ=segments[I][J];
+ for(int l=0; l < segmentsIJ.length; ++l) {
+ segment D=segmentsIJ[l];
+ if(!D.active) continue;
+ if(abs(D.a-g[g.length-1]) < eps) {
+ g.push(D.b);
+ segmentsIJ[l].active=false;
+ if(D.edge >= 0 && !first) return D.edge;
+ first=false;
+ l=-1;
+ } else if(abs(D.b-g[g.length-1]) < eps) {
+ g.push(D.a);
+ segmentsIJ[l].active=false;
+ if(D.edge >= 0 && !first) return D.edge;
+ first=false;
+ l=-1;
+ }
+ }
+ }
+ return -1;
+ }
+
+ int backward(int I, int J, bool first=true) {
+ if(I >= 0 && I < nx && J >= 0 && J < ny) {
+ segment[] segmentsIJ=segments[I][J];
+ for(int l=0; l < segmentsIJ.length; ++l) {
+ segment D=segmentsIJ[l];
+ if(!D.active) continue;
+ if(abs(D.a-g[0]) < eps) {
+ g.insert(0,D.b);
+ segmentsIJ[l].active=false;
+ if(D.edge >= 0 && !first) return D.edge;
+ first=false;
+ l=-1;
+ } else if(abs(D.b-g[0]) < eps) {
+ g.insert(0,D.a);
+ segmentsIJ[l].active=false;
+ if(D.edge >= 0 && !first) return D.edge;
+ first=false;
+ l=-1;
+ }
+ }
+ }
+ return -1;
+ }
+
+ void follow(int f(int, int, bool first=true), int edge) {
+ int I=i;
+ int J=j;
+ while(true) {
+ static int ix[]={1,0,-1,0};
+ static int iy[]={0,1,0,-1};
+ if(edge >= 0 && edge < 4) {
+ I += ix[edge];
+ J += iy[edge];
+ edge=f(I,J);
+ } else {
+ if(edge == -1) break;
+ if(edge < 9) {
+ int edge0=(edge-5) % 4;
+ int edge1=(edge-4) % 4;
+ int ix0=ix[edge0];
+ int iy0=iy[edge0];
+ I += ix0;
+ J += iy0;
+ // Search all 3 corner cells
+ if((edge=f(I,J)) == -1) {
+ I += ix[edge1];
+ J += iy[edge1];
+ if((edge=f(I,J)) == -1) {
+ I -= ix0;
+ J -= iy0;
+ edge=f(I,J);
+ }
+ }
+ } else {
+ // Double-vertex edge: search all 8 surrounding cells
+ void search() {
+ for(int i=-1; i <= 1; ++i) {
+ for(int j=-1; j <= 1; ++j) {
+ if((edge=f(I+i,J+j,false)) >= 0) {
+ I += i;
+ J += j;
+ return;
+ }
+ }
+ }
+ }
+ search();
+ }
+ }
+ }
+ }
+
+ // Follow contour in cell
+ int edge=forward(i,j,first=false);
+
+ // Follow contour forward outside of cell
+ follow(forward,edge);
+
+ // Follow contour backward outside of cell
+ follow(backward,C.edge);
+
+ points[C.c].push(g);
+ }
+ }
+ }
+
+ collect(points,c); // Required to join remaining case1 cycles.
+
+ return connect(points,c,join);
+}
+
+// Return contour guides for a 2D data array on a uniform lattice
+// f: two-dimensional array of real data values
+// midpoint: optional array containing data values at cell midpoints
+// a,b: diagonally opposite vertices of rectangular domain
+// c: array of contour values
+// join: interpolation operator (e.g. operator -- or operator ..)
+guide[][] contour(real[][] f, real[][] midpoint=new real[][],
+ pair a, pair b, real[] c,
+ interpolate join=operator --)
+{
+ int nx=f.length-1;
+ if(nx == 0)
+ abort("array f must have length >= 2");
+ int ny=f[0].length-1;
+ if(ny == 0)
+ abort("array f[0] must have length >= 2");
+
+ pair[][] z=new pair[nx+1][ny+1];
+ for(int i=0; i <= nx; ++i) {
+ pair[] zi=z[i];
+ real xi=interp(a.x,b.x,i/nx);
+ for(int j=0; j <= ny; ++j) {
+ zi[j]=(xi,interp(a.y,b.y,j/ny));
+ }
+ }
+ return contour(z,f,midpoint,c,join);
+}
+
+// return contour guides for a real-valued function
+// f: real-valued function of two real variables
+// a,b: diagonally opposite vertices of rectangular domain
+// c: array of contour values
+// nx,ny: number of subdivisions in x and y directions (determines accuracy)
+// join: interpolation operator (e.g. operator -- or operator ..)
+guide[][] contour(real f(real, real), pair a, pair b,
+ real[] c, int nx=ngraph, int ny=nx,
+ interpolate join=operator --)
+{
+ // evaluate function at points and midpoints
+ real[][] dat=new real[nx+1][ny+1];
+ real[][] midpoint=new real[nx+1][ny+1];
+
+ for(int i=0; i <= nx; ++i) {
+ real x=interp(a.x,b.x,i/nx);
+ real x2=interp(a.x,b.x,(i+0.5)/nx);
+ real[] dati=dat[i];
+ real[] midpointi=midpoint[i];
+ for(int j=0; j <= ny; ++j) {
+ dati[j]=f(x,interp(a.y,b.y,j/ny));
+ midpointi[j]=f(x2,interp(a.y,b.y,(j+0.5)/ny));
+ }
+ }
+
+ return contour(dat,midpoint,a,b,c,join);
+}
+
+void draw(picture pic=currentpicture, Label[] L=new Label[],
+ guide[][] g, pen[] p)
+{
+ begingroup(pic);
+ for(int cnt=0; cnt < g.length; ++cnt) {
+ guide[] gcnt=g[cnt];
+ pen pcnt=p[cnt];
+ for(int i=0; i < gcnt.length; ++i)
+ draw(pic,gcnt[i],pcnt);
+ if(L.length > 0) {
+ Label Lcnt=L[cnt];
+ for(int i=0; i < gcnt.length; ++i) {
+ if(Lcnt.s != "" && size(gcnt[i]) > 1)
+ label(pic,Lcnt,gcnt[i],pcnt);
+ }
+ }
+ }
+ endgroup(pic);
+}
+
+void draw(picture pic=currentpicture, Label[] L=new Label[],
+ guide[][] g, pen p=currentpen)
+{
+ draw(pic,L,g,sequence(new pen(int) {return p;},g.length));
+}
+
+// Extend palette by the colors below and above at each end.
+pen[] extend(pen[] palette, pen below, pen above) {
+ pen[] p=copy(palette);
+ p.insert(0,below);
+ p.push(above);
+ return p;
+}
+
+// Compute the interior palette for a sequence of cyclic contours
+// corresponding to palette.
+pen[][] interior(picture pic=currentpicture, guide[][] g, pen[] palette)
+{
+ if(palette.length != g.length+1)
+ abort("Palette array must have length one more than guide array");
+ pen[][] fillpalette=new pen[g.length][];
+ for(int i=0; i < g.length; ++i) {
+ guide[] gi=g[i];
+ guide[] gp;
+ if(i+1 < g.length) gp=g[i+1];
+ guide[] gm;
+ if(i > 0) gm=g[i-1];
+
+ pen[] fillpalettei=new pen[gi.length];
+ for(int j=0; j < gi.length; ++j) {
+ path P=gi[j];
+ if(cyclic(P)) {
+ int index=i+1;
+ bool nextinside;
+ for(int k=0; k < gp.length; ++k) {
+ path next=gp[k];
+ if(cyclic(next)) {
+ if(inside(P,point(next,0)))
+ nextinside=true;
+ else if(inside(next,point(P,0)))
+ index=i;
+ }
+ }
+ if(!nextinside) {
+ // Check to see if previous contour is inside
+ for(int k=0; k < gm.length; ++k) {
+ path prev=gm[k];
+ if(cyclic(prev)) {
+ if(inside(P,point(prev,0)))
+ index=i;
+ }
+ }
+ }
+ fillpalettei[j]=palette[index];
+ }
+ fillpalette[i]=fillpalettei;
+ }
+ }
+ return fillpalette;
+}
+
+// Fill the interior of cyclic contours with palette
+void fill(picture pic=currentpicture, guide[][] g, pen[][] palette)
+{
+ for(int i=0; i < g.length; ++i) {
+ guide[] gi=g[i];
+ guide[] gp;
+ if(i+1 < g.length) gp=g[i+1];
+ guide[] gm;
+ if(i > 0) gm=g[i-1];
+
+ for(int j=0; j < gi.length; ++j) {
+ path P=gi[j];
+ path[] S=P;
+ if(cyclic(P)) {
+ for(int k=0; k < gp.length; ++k) {
+ path next=gp[k];
+ if(cyclic(next) && inside(P,point(next,0)))
+ S=S^^next;
+ }
+ for(int k=0; k < gm.length; ++k) {
+ path next=gm[k];
+ if(cyclic(next) && inside(P,point(next,0)))
+ S=S^^next;
+ }
+ fill(pic,S,palette[i][j]+evenodd);
+ }
+ }
+ }
+}
+
+// routines for irregularly spaced points:
+
+// check existing guides and adds new segment to them if possible,
+// or otherwise store segment as a new guide
+private void addseg(pair[][] gds, segment seg)
+{
+ if(!seg.active) return;
+ // search for a path to extend
+ for(int i=0; i < gds.length; ++i) {
+ pair[] gd=gds[i];
+ if(abs(gd[0]-seg.b) < eps) {
+ gd.insert(0,seg.a);
+ return;
+ } else if(abs(gd[gd.length-1]-seg.b) < eps) {
+ gd.push(seg.a);
+ return;
+ } else if(abs(gd[0]-seg.a) < eps) {
+ gd.insert(0,seg.b);
+ return;
+ } else if(abs(gd[gd.length-1]-seg.a) < eps) {
+ gd.push(seg.b);
+ return;
+ }
+ }
+
+ // in case nothing is found
+ pair[] segm;
+ segm=new pair[] {seg.a,seg.b};
+ gds.push(segm);
+
+ return;
+}
+
+guide[][] contour(real f(pair), pair a, pair b,
+ real[] c, int nx=ngraph, int ny=nx,
+ interpolate join=operator --)
+{
+ return contour(new real(real x, real y) {return f((x,y));},a,b,c,nx,ny,join);
+}
+
+guide[][] contour(pair[] z, real[] f, real[] c, interpolate join=operator --)
+{
+ if(z.length != f.length)
+ abort("z and f arrays have different lengths");
+
+ int[][] trn=triangulate(z);
+
+ // array to store guides found so far
+ pair[][][] points=new pair[c.length][][];
+
+ for(int cnt=0; cnt < c.length; ++cnt) {
+ pair[][] pointscnt=points[cnt];
+ real C=c[cnt];
+ for(int i=0; i < trn.length; ++i) {
+ int[] trni=trn[i];
+ int i0=trni[0], i1=trni[1], i2=trni[2];
+ addseg(pointscnt,checktriangle(z[i0],z[i1],z[i2],
+ f[i0]-C,f[i1]-C,f[i2]-C));
+ }
+ }
+
+ collect(points,c);
+
+ return connect(points,c,join);
+}
diff --git a/graphics/asymptote/base/contour3.asy b/graphics/asymptote/base/contour3.asy
new file mode 100644
index 0000000000..4977a351bf
--- /dev/null
+++ b/graphics/asymptote/base/contour3.asy
@@ -0,0 +1,479 @@
+import graph_settings;
+import three;
+
+real eps=10000*realEpsilon;
+
+private struct weighted
+{
+ triple normal;
+ real ratio;
+ int kpa0,kpa1,kpa2;
+ int kpb0,kpb1,kpb2;
+ triple v;
+}
+
+private struct bucket
+{
+ triple v;
+ triple val;
+ int count;
+}
+
+struct vertex
+{
+ triple v;
+ triple normal;
+}
+
+// A group of 3 or 4 points.
+private struct object
+{
+ bool active;
+ weighted[] pts;
+}
+
+// Return contour vertices for a 3D data array.
+// z: three-dimensional array of nonoverlapping mesh points
+// f: three-dimensional arrays of real data values
+// midpoint: optional array containing estimate of f at midpoint values
+vertex[][] contour3(triple[][][] v, real[][][] f,
+ real[][][] midpoint=new real[][][],
+ projection P=currentprojection)
+{
+ int nx=v.length-1;
+ if(nx == 0)
+ abort("array v must have length >= 2");
+ int ny=v[0].length-1;
+ if(ny == 0)
+ abort("array v[0] must have length >= 2");
+ int nz=v[0][0].length-1;
+ if(nz == 0)
+ abort("array v[0][0] must have length >= 2");
+
+ bool midpoints=midpoint.length > 0;
+
+ bucket[][][][] kps=new bucket[2nx+1][2ny+1][2nz+1][];
+ for(int i=0; i < 2nx+1; ++i)
+ for(int j=0; j < 2ny+1; ++j)
+ for(int k=0; k < 2nz+1; ++k)
+ kps[i][j][k]=new bucket[];
+
+ object[] objects;
+
+ // go over region a rectangle at a time
+ for(int i=0; i < nx; ++i) {
+ triple[][] vi=v[i];
+ triple[][] vp=v[i+1];
+ real[][] fi=f[i];
+ real[][] fp=f[i+1];
+ int i2=2i;
+ int i2p1=i2+1;
+ int i2p2=i2+2;
+ for(int j=0; j < ny; ++j) {
+ triple[] vij=vi[j];
+ triple[] vpj=vp[j];
+ triple[] vip=vi[j+1];
+ triple[] vpp=vp[j+1];
+ real[] fij=fi[j];
+ real[] fpj=fp[j];
+ real[] fip=fi[j+1];
+ real[] fpp=fp[j+1];
+ int j2=2j;
+ int j2p1=j2+1;
+ int j2p2=j2+2;
+
+ for(int k=0; k < nz; ++k) {
+ // vertex values
+ real vdat0=fij[k];
+ real vdat1=fij[k+1];
+ real vdat2=fip[k];
+ real vdat3=fip[k+1];
+ real vdat4=fpj[k];
+ real vdat5=fpj[k+1];
+ real vdat6=fpp[k];
+ real vdat7=fpp[k+1];
+
+ // define points
+ triple p000=vij[k];
+ triple p001=vij[k+1];
+ triple p010=vip[k];
+ triple p011=vip[k+1];
+ triple p100=vpj[k];
+ triple p101=vpj[k+1];
+ triple p110=vpp[k];
+ triple p111=vpp[k+1];
+ triple m0=0.25*(p000+p010+p110+p100);
+ triple m1=0.25*(p010+p110+p111+p011);
+ triple m2=0.25*(p110+p100+p101+p111);
+ triple m3=0.25*(p100+p000+p001+p101);
+ triple m4=0.25*(p000+p010+p011+p001);
+ triple m5=0.25*(p001+p011+p111+p101);
+ triple mc=0.5*(m0+m5);
+
+ // optimization: we make sure we don't work with empty rectangles
+ int countm=0;
+ int countz=0;
+ int countp=0;
+
+ void check(real vdat) {
+ if(vdat < -eps) ++countm;
+ else {
+ if(vdat <= eps) ++countz;
+ else ++countp;
+ }
+ }
+
+ check(vdat0);
+ check(vdat1);
+ check(vdat2);
+ check(vdat3);
+ check(vdat4);
+ check(vdat5);
+ check(vdat6);
+ check(vdat7);
+
+ if(countm == 8 || countp == 8 ||
+ ((countm == 7 || countp == 7) && countz == 1)) continue;
+
+ int k2=2k;
+ int k2p1=k2+1;
+ int k2p2=k2+2;
+
+ // Evaluate midpoints of cube sides.
+ // Then evaluate midpoint of cube.
+ real vdat8=midpoints ? midpoint[i2p1][j2p1][k2] :
+ 0.25*(vdat0+vdat2+vdat6+vdat4);
+ real vdat9=midpoints ? midpoint[i2p1][j2p2][k2p1] :
+ 0.25*(vdat2+vdat6+vdat7+vdat3);
+ real vdat10=midpoints ? midpoint[i2p2][j2p1][k2p1] :
+ 0.25*(vdat7+vdat6+vdat4+vdat5);
+ real vdat11=midpoints ? midpoint[i2p1][j2][k2p1] :
+ 0.25*(vdat0+vdat4+vdat5+vdat1);
+ real vdat12=midpoints ? midpoint[i2][j2p1][k2p1] :
+ 0.25*(vdat0+vdat2+vdat3+vdat1);
+ real vdat13=midpoints ? midpoint[i2p1][j2p1][k2p2] :
+ 0.25*(vdat1+vdat3+vdat7+vdat5);
+ real vdat14=midpoints ? midpoint[i2p1][j2p1][k2p1] :
+ 0.125*(vdat0+vdat1+vdat2+vdat3+vdat4+vdat5+vdat6+vdat7);
+
+ // Go through the 24 pyramids, 4 for each side.
+
+ void addval(int kp0, int kp1, int kp2, triple add, triple v) {
+ bucket[] cur=kps[kp0][kp1][kp2];
+ for(int q=0; q < cur.length; ++q) {
+ if(length(cur[q].v-v) < eps) {
+ cur[q].val += add;
+ ++cur[q].count;
+ return;
+ }
+ }
+ bucket newbuck;
+ newbuck.v=v;
+ newbuck.val=add;
+ newbuck.count=1;
+ cur.push(newbuck);
+ }
+
+ void accrue(weighted w) {
+ triple val1=w.normal*w.ratio;
+ triple val2=w.normal*(1-w.ratio);
+ addval(w.kpa0,w.kpa1,w.kpa2,val1,w.v);
+ addval(w.kpb0,w.kpb1,w.kpb2,val2,w.v);
+ }
+
+ triple dir=P.normal;
+
+ void addnormals(weighted[] pts) {
+ triple vec2=pts[1].v-pts[0].v;
+ triple vec1=pts[0].v-pts[2].v;
+ triple vec0=-vec2-vec1;
+ vec2=unit(vec2);
+ vec1=unit(vec1);
+ vec0=unit(vec0);
+ triple normal=cross(vec2,vec1);
+ normal *= sgn(dot(normal,dir));
+ real angle0=acos(-dot(vec1,vec2));
+ real angle1=acos(-dot(vec2,vec0));
+ pts[0].normal=normal*angle0;
+ pts[1].normal=normal*angle1;
+ pts[2].normal=normal*(pi-angle0-angle1);
+ }
+
+ void addobj(object obj) {
+ if(!obj.active) return;
+
+ if(obj.pts.length == 4) {
+ weighted[] points=obj.pts;
+ object obj1;
+ object obj2;
+ obj1.active=true;
+ obj2.active=true;
+ obj1.pts=new weighted[] {points[0],points[1],points[2]};
+ obj2.pts=new weighted[] {points[1],points[2],points[3]};
+ addobj(obj1);
+ addobj(obj2);
+ } else {
+ addnormals(obj.pts);
+ for(int q=0; q < obj.pts.length; ++q)
+ accrue(obj.pts[q]);
+ objects.push(obj);
+ }
+ }
+
+ weighted setupweighted(triple va, triple vb, real da, real db,
+ int[] kpa, int[] kpb) {
+ weighted w;
+ real ratio=abs(da/(db-da));
+ w.v=interp(va,vb,ratio);
+ w.ratio=ratio;
+ w.kpa0=i2+kpa[0];
+ w.kpa1=j2+kpa[1];
+ w.kpa2=k2+kpa[2];
+ w.kpb0=i2+kpb[0];
+ w.kpb1=j2+kpb[1];
+ w.kpb2=k2+kpb[2];
+
+ return w;
+ }
+
+ weighted setupweighted(triple v, int[] kp) {
+ weighted w;
+ w.v=v;
+ w.ratio=0.5;
+ w.kpa0=w.kpb0=i2+kp[0];
+ w.kpa1=w.kpb1=j2+kp[1];
+ w.kpa2=w.kpb2=k2+kp[2];
+
+ return w;
+ }
+
+ // Checks if a pyramid contains a contour object.
+ object checkpyr(triple v0, triple v1, triple v2, triple v3,
+ real d0, real d1, real d2, real d3,
+ int[] c0, int[] c1, int[] c2, int[] c3) {
+ object obj;
+ real a0=abs(d0);
+ real a1=abs(d1);
+ real a2=abs(d2);
+ real a3=abs(d3);
+
+ bool b0=a0 < eps;
+ bool b1=a1 < eps;
+ bool b2=a2 < eps;
+ bool b3=a3 < eps;
+
+ weighted[] pts;
+
+ if(b0) pts.push(setupweighted(v0,c0));
+ if(b1) pts.push(setupweighted(v1,c1));
+ if(b2) pts.push(setupweighted(v2,c2));
+ if(b3) pts.push(setupweighted(v3,c3));
+
+ if(!b0 && !b1 && abs(d0+d1)+eps < a0+a1)
+ pts.push(setupweighted(v0,v1,d0,d1,c0,c1));
+ if(!b0 && !b2 && abs(d0+d2)+eps < a0+a2)
+ pts.push(setupweighted(v0,v2,d0,d2,c0,c2));
+ if(!b0 && !b3 && abs(d0+d3)+eps < a0+a3)
+ pts.push(setupweighted(v0,v3,d0,d3,c0,c3));
+ if(!b1 && !b2 && abs(d1+d2)+eps < a1+a2)
+ pts.push(setupweighted(v1,v2,d1,d2,c1,c2));
+ if(!b1 && !b3 && abs(d1+d3)+eps < a1+a3)
+ pts.push(setupweighted(v1,v3,d1,d3,c1,c3));
+ if(!b2 && !b3 && abs(d2+d3)+eps < a2+a3)
+ pts.push(setupweighted(v2,v3,d2,d3,c2,c3));
+
+ int s=pts.length;
+ //There are three or four points.
+ if(s > 2) {
+ obj.active=true;
+ obj.pts=pts;
+ } else obj.active=false;
+
+ return obj;
+ }
+
+ void check4pyr(triple v0, triple v1, triple v2, triple v3,
+ triple v4, triple v5,
+ real d0, real d1, real d2, real d3, real d4, real d5,
+ int[] c0, int[] c1, int[] c2, int[] c3, int[] c4,
+ int[] c5) {
+ addobj(checkpyr(v5,v4,v0,v1,d5,d4,d0,d1,c5,c4,c0,c1));
+ addobj(checkpyr(v5,v4,v1,v2,d5,d4,d1,d2,c5,c4,c1,c2));
+ addobj(checkpyr(v5,v4,v2,v3,d5,d4,d2,d3,c5,c4,c2,c3));
+ addobj(checkpyr(v5,v4,v3,v0,d5,d4,d3,d0,c5,c4,c3,c0));
+ }
+
+ static int[] pp000={0,0,0};
+ static int[] pp001={0,0,2};
+ static int[] pp010={0,2,0};
+ static int[] pp011={0,2,2};
+ static int[] pp100={2,0,0};
+ static int[] pp101={2,0,2};
+ static int[] pp110={2,2,0};
+ static int[] pp111={2,2,2};
+ static int[] pm0={1,1,0};
+ static int[] pm1={1,2,1};
+ static int[] pm2={2,1,1};
+ static int[] pm3={1,0,1};
+ static int[] pm4={0,1,1};
+ static int[] pm5={1,1,2};
+ static int[] pmc={1,1,1};
+
+ check4pyr(p000,p010,p110,p100,mc,m0,
+ vdat0,vdat2,vdat6,vdat4,vdat14,vdat8,
+ pp000,pp010,pp110,pp100,pmc,pm0);
+ check4pyr(p010,p110,p111,p011,mc,m1,
+ vdat2,vdat6,vdat7,vdat3,vdat14,vdat9,
+ pp010,pp110,pp111,pp011,pmc,pm1);
+ check4pyr(p110,p100,p101,p111,mc,m2,
+ vdat6,vdat4,vdat5,vdat7,vdat14,vdat10,
+ pp110,pp100,pp101,pp111,pmc,pm2);
+ check4pyr(p100,p000,p001,p101,mc,m3,
+ vdat4,vdat0,vdat1,vdat5,vdat14,vdat11,
+ pp100,pp000,pp001,pp101,pmc,pm3);
+ check4pyr(p000,p010,p011,p001,mc,m4,
+ vdat0,vdat2,vdat3,vdat1,vdat14,vdat12,
+ pp000,pp010,pp011,pp001,pmc,pm4);
+ check4pyr(p001,p011,p111,p101,mc,m5,
+ vdat1,vdat3,vdat7,vdat5,vdat14,vdat13,
+ pp001,pp011,pp111,pp101,pmc,pm5);
+ }
+ }
+ }
+
+ vertex preparevertex(weighted w) {
+ vertex ret;
+ triple normal=O;
+ bool first=true;
+ bucket[] kp1=kps[w.kpa0][w.kpa1][w.kpa2];
+ bucket[] kp2=kps[w.kpb0][w.kpb1][w.kpb2];
+ bool notfound1=true;
+ bool notfound2=true;
+ int count=0;
+ int stop=max(kp1.length,kp2.length);
+ for(int r=0; r < stop; ++r) {
+ if(notfound1) {
+ if(length(w.v-kp1[r].v) < eps) {
+ if(first) {
+ ret.v=kp1[r].v;
+ first=false;
+ }
+ normal += kp1[r].val;
+ count += kp1[r].count;
+ notfound1=false;
+ }
+ }
+ if(notfound2) {
+ if(length(w.v-kp2[r].v) < eps) {
+ if(first) {
+ ret.v=kp2[r].v;
+ first=false;
+ }
+ normal += kp2[r].val;
+ count += kp2[r].count;
+ notfound2=false;
+ }
+ }
+ }
+ ret.normal=normal*2/count;
+ return ret;
+ }
+
+ // Prepare return value.
+ vertex[][] g;
+
+ for(int q=0; q < objects.length; ++q) {
+ object p=objects[q];
+ g.push(new vertex[] {preparevertex(p.pts[0]),preparevertex(p.pts[1]),
+ preparevertex(p.pts[2])});
+ }
+ return g;
+}
+
+// Return contour vertices for a 3D data array on a uniform lattice.
+// f: three-dimensional arrays of real data values
+// midpoint: optional array containing estimate of f at midpoint values
+// a,b: diagonally opposite points of rectangular parellelpiped domain
+vertex[][] contour3(real[][][] f, real[][][] midpoint=new real[][][],
+ triple a, triple b, projection P=currentprojection)
+
+{
+ int nx=f.length-1;
+ if(nx == 0)
+ abort("array f must have length >= 2");
+ int ny=f[0].length-1;
+ if(ny == 0)
+ abort("array f[0] must have length >= 2");
+ int nz=f[0][0].length-1;
+ if(nz == 0)
+ abort("array f[0][0] must have length >= 2");
+
+ triple[][][] v=new triple[nx+1][ny+1][nz+1];
+ for(int i=0; i <= nx; ++i) {
+ real xi=interp(a.x,b.x,i/nx);
+ triple[][] vi=v[i];
+ for(int j=0; j <= ny; ++j) {
+ triple[] vij=v[i][j];
+ real yj=interp(a.y,b.y,j/ny);
+ for(int k=0; k <= nz; ++k) {
+ vij[k]=(xi,yj,interp(a.z,b.z,k/nz));
+ }
+ }
+ }
+ return contour3(v,f,midpoint,P);
+}
+
+// Return contour vertices for a 3D data array, using a pyramid mesh
+// f: real-valued function of three real variables
+// a,b: diagonally opposite points of rectangular parellelpiped domain
+// nx,ny,nz number of subdivisions in x, y, and z directions
+vertex[][] contour3(real f(real, real, real), triple a, triple b,
+ int nx=nmesh, int ny=nx, int nz=nx,
+ projection P=currentprojection)
+{
+ // evaluate function at points and midpoints
+ real[][][] dat=new real[nx+1][ny+1][nz+1];
+ real[][][] midpoint=new real[2nx+2][2ny+2][2nz+1];
+
+ for(int i=0; i <= nx; ++i) {
+ real x=interp(a.x,b.x,i/nx);
+ real x2=interp(a.x,b.x,(i+0.5)/nx);
+ real[][] dati=dat[i];
+ real[][] midpointi2=midpoint[2i];
+ real[][] midpointi2p1=midpoint[2i+1];
+ for(int j=0; j <= ny; ++j) {
+ real y=interp(a.y,b.y,j/ny);
+ real y2=interp(a.y,b.y,(j+0.5)/ny);
+ real datij[]=dati[j];
+ real[] midpointi2p1j2=midpointi2p1[2j];
+ real[] midpointi2p1j2p1=midpointi2p1[2j+1];
+ real[] midpointi2j2p1=midpointi2[2j+1];
+ for(int k=0; k <= nz; ++k) {
+ real z=interp(a.z,b.z,k/nz);
+ real z2=interp(a.z,b.z,(k+0.5)/nz);
+ datij[k]=f(x,y,z);
+ if(i == nx || j == ny || k == nz) continue;
+ int k2p1=2k+1;
+ midpointi2p1j2p1[2k]=f(x2,y2,z);
+ midpointi2p1j2p1[k2p1]=f(x2,y2,z2);
+ midpointi2p1j2[k2p1]=f(x2,y,z2);
+ midpointi2j2p1[k2p1]=f(x,y2,z2);
+ if(i == 0) midpoint[2nx][2j+1][k2p1]=f(b.x,y2,z2);
+ if(j == 0) midpointi2p1[2ny][k2p1]=f(x2,b.y,z2);
+ if(k == 0) midpointi2p1j2p1[2nz]=f(x2,y2,b.z);
+ }
+ }
+ }
+ return contour3(dat,midpoint,a,b,P);
+}
+
+// Construct contour surface for a 3D data array, using a pyramid mesh.
+surface surface(vertex[][] g)
+{
+ surface s=surface(g.length);
+ for(int i=0; i < g.length; ++i) {
+ vertex[] cur=g[i];
+ s.s[i]=patch(cur[0].v--cur[1].v--cur[2].v--cycle);
+ }
+ return s;
+}
diff --git a/graphics/asymptote/base/drawtree.asy b/graphics/asymptote/base/drawtree.asy
new file mode 100644
index 0000000000..832dfc658a
--- /dev/null
+++ b/graphics/asymptote/base/drawtree.asy
@@ -0,0 +1,101 @@
+// A simple tree drawing module contributed by adarovsky
+// See example treetest.asy
+
+real treeNodeStep = 0.5cm;
+real treeLevelStep = 1cm;
+real treeMinNodeWidth = 2cm;
+
+struct TreeNode {
+ TreeNode parent;
+ TreeNode[] children;
+
+ frame content;
+
+ pair pos;
+ real adjust;
+}
+
+void add( TreeNode child, TreeNode parent )
+{
+ child.parent = parent;
+ parent.children.push( child );
+}
+
+TreeNode makeNode( TreeNode parent = null, frame f )
+{
+ TreeNode child = new TreeNode;
+ child.content = f;
+ if( parent != null ) {
+ add( child, parent );
+ }
+ return child;
+}
+
+TreeNode makeNode( TreeNode parent = null, Label label )
+{
+ frame f;
+ box( f, label);
+ return makeNode( parent, f );
+}
+
+
+real layout( int level, TreeNode node )
+{
+ if( node.children.length > 0 ) {
+ real width[] = new real[node.children.length];
+ real curWidth = 0;
+
+ for( int i = 0; i < node.children.length; ++i ) {
+ width[i] = layout( level+1, node.children[i] );
+
+ node.children[i].pos = (curWidth + width[i]/2,
+ -level*treeLevelStep);
+ curWidth += width[i] + treeNodeStep;
+ }
+
+ real midPoint = ( sum( width )+treeNodeStep*(width.length-1)) / 2;
+ for( int i = 0; i < node.children.length; ++i ) {
+ node.children[i].adjust = - midPoint;
+ }
+
+ return max( (max(node.content)-min(node.content)).x,
+ sum(width)+treeNodeStep*(width.length-1) );
+ }
+ else {
+ return max( treeMinNodeWidth, (max(node.content)-min(node.content)).x );
+ }
+}
+
+void drawAll( TreeNode node, frame f )
+{
+ pair pos;
+ if( node.parent != null )
+ pos = (node.parent.pos.x+node.adjust, 0);
+ else
+ pos = (node.adjust, 0);
+ node.pos += pos;
+
+ node.content = shift(node.pos)*node.content;
+ add( f, node.content );
+
+
+ if( node.parent != null ) {
+ path p = point(node.content, N)--point(node.parent.content,S);
+ draw( f, p, currentpen );
+ }
+
+ for( int i = 0; i < node.children.length; ++i )
+ drawAll( node.children[i], f );
+}
+
+void draw( TreeNode root, pair pos )
+{
+ frame f;
+
+ root.pos = (0,0);
+ layout( 1, root );
+
+ drawAll( root, f );
+
+ add(f,pos);
+}
diff --git a/graphics/asymptote/base/embed.asy b/graphics/asymptote/base/embed.asy
new file mode 100644
index 0000000000..88495f1212
--- /dev/null
+++ b/graphics/asymptote/base/embed.asy
@@ -0,0 +1,37 @@
+if(latex()) {
+ usepackage("hyperref");
+ texpreamble("\hypersetup{"+settings.hyperrefOptions+"}");
+ usepackage("media9","bigfiles");
+}
+
+// For documentation of the options see
+// http://mirror.ctan.org/macros/latex/contrib/media9/doc/media9.pdf
+
+// Embed PRC or SWF content in pdf file
+string embedplayer(string name, string text="", string options="",
+ real width=0, real height=0)
+{
+ if(width != 0) options += ",width="+(string) (width/pt)+"pt";
+ if(height != 0) options += ",height="+(string) (height/pt)+"pt";
+ return "%
+\includemedia[noplaybutton,"+options+"]{"+text+"}{"+name+"}";
+}
+
+// Embed media in pdf file
+string embed(string name, string text="", string options="",
+ real width=0, real height=0)
+{
+ return embedplayer("VPlayer.swf",text,"label="+name+
+ ",activate=pageopen,addresource="+name+
+ ",flashvars={source="+name+"&scaleMode=letterbox},"+
+ options,width,height);
+}
+
+string link(string label, string text="Play")
+{
+ return "\PushButton[
+ onclick={
+ annotRM['"+label+"'].activated=true;
+ annotRM['"+label+"'].callAS('playPause');
+ }]{\fbox{"+text+"}}";
+}
diff --git a/graphics/asymptote/base/external.asy b/graphics/asymptote/base/external.asy
new file mode 100644
index 0000000000..f9ab5365ae
--- /dev/null
+++ b/graphics/asymptote/base/external.asy
@@ -0,0 +1,37 @@
+usepackage("hyperref");
+texpreamble("\hypersetup{"+settings.hyperrefOptions+"}");
+
+// Embed object to be run in an external window. An image file name can be
+// specified; if not given one will be automatically generated.
+string embed(string name, string text="", string options="",
+ real width=0, real height=0, string image="")
+{
+ string options; // Ignore passed options.
+ if(image == "") {
+ image=stripdirectory(stripextension(name))+"."+nativeformat();
+ convert(name+"[0]",image,nativeformat());
+
+ if(!settings.keep) {
+ exitfcn currentexitfunction=atexit();
+ void exitfunction() {
+ if(currentexitfunction != null) currentexitfunction();
+ delete(image);
+ }
+ atexit(exitfunction);
+ }
+ }
+ if(width != 0) options += ", width="+(string) (width/pt)+"pt";
+ if(height != 0) options +=", height="+(string) (height/pt)+"pt";
+ return "\href{run:"+name+"}{"+graphic(image,options)+"}";
+}
+
+string hyperlink(string url, string text)
+{
+ return "\href{"+url+"}{"+text+"}";
+}
+
+string link(string label, string text="Play")
+{
+ return hyperlink("run:"+label,text);
+}
+
diff --git a/graphics/asymptote/base/feynman.asy b/graphics/asymptote/base/feynman.asy
new file mode 100644
index 0000000000..4360ee9f52
--- /dev/null
+++ b/graphics/asymptote/base/feynman.asy
@@ -0,0 +1,622 @@
+/*****************************************************************************
+ * feynman.asy -- An Asymptote library for drawing Feynman diagrams. *
+ * *
+ * by: Martin Wiebusch <martin.wiebusch@gmx.net> *
+ * last change: 2007/04/13 *
+ *****************************************************************************/
+
+
+/* default parameters ********************************************************/
+
+// default ratio of width (distance between two loops) to amplitude for a gluon
+// line. The gluon function uses this ratio, if the width parameter is
+// negative.
+real gluonratio;
+
+// default ratio of width (distance between two crests) to amplitude for a
+// photon line. The photon function uses this ratio, if the width parameter is
+// negative.
+real photonratio;
+
+// default gluon amplitude
+real gluonamplitude;
+
+// default photon amplitude
+real photonamplitude;
+
+// default pen for drawing the background. Usually white.
+pen backgroundpen;
+
+// default pen for drawing gluon lines
+pen gluonpen;
+
+// default pen for drawing photon lines
+pen photonpen;
+
+// default pen for drawing fermion lines
+pen fermionpen;
+
+// default pen for drawing scalar lines
+pen scalarpen;
+
+// default pen for drawing ghost lines
+pen ghostpen;
+
+// default pen for drawing double lines
+pen doublelinepen;
+
+// default pen for drawing vertices
+pen vertexpen;
+
+// default pen for drawing big vertices (drawVertexOX and drawVertexBoxX)
+pen bigvertexpen;
+
+// inner spacing of a double line
+real doublelinespacing;
+
+// default arrow for propagators
+arrowbar currentarrow;
+
+// if true, each of the drawSomething commands blots out the background
+// (with pen backgroundpen) before drawing.
+bool overpaint;
+
+// margin around lines. If one line is drawn over anoter, a white margin
+// of size linemargin is kept around the top one.
+real linemargin;
+
+// at vertices, where many lines join, the last line drawn should not blot
+// out the others. By not erasing the background near the ends of lines,
+// this is prevented for lines with an angle greater than minvertexangle to
+// each other. Note, that small values for minvertexangle mean that the
+// background is only erased behind a small segment of every line. Setting
+// minvertexangle = 0 effectively disables background erasing for lines.
+real minvertexangle;
+
+// size (radius) of vertices
+real vertexsize;
+
+// size (radius) of big vertices (drawVertexOX and drawVertexBoxX)
+real bigvertexsize;
+
+/* defaults for momentum arrows **********************************************/
+
+// (momentum arrows are small arrows parallel to particle lines indicating the
+// direction of momentum)
+
+// default size of the arrowhead of momentum arrows
+arrowbar currentmomarrow;
+
+// default length of momentum arrows
+real momarrowlength;
+
+// default pen for momentum arrows
+pen momarrowpen;
+
+// default offset between momentum arrow and related particle line
+real momarrowoffset;
+
+// default margin for momentum arrows
+real momarrowmargin;
+
+// factor for determining the size of momentum arrowheads. After changing it,
+// you still have to update currentmomarrow manually.
+real momarrowfactor;
+
+// size function for momentum arrowheads
+real momarrowsize(pen p=momarrowpen) { return momarrowfactor*linewidth(p); }
+
+
+/* defaults for texshipout ***************************************************/
+
+// tex command for including graphics. It takes one argument, which is the
+// name of the graphics (eps or pdf) file.
+string includegraphicscommand;
+
+// Determines whether the suffix (.eps or .pdf) should be appended to the stem
+// of the file name in the \includegraphics command.
+bool appendsuffix;
+
+
+/* helper functions **********************************************************/
+
+// internal function for overpainting
+private void do_overpaint(picture pic, path p, pen bgpen,
+ real halfwidth, real vertexangle)
+{
+ real tanvertexangle = tan(vertexangle*pi/180);
+ if(tanvertexangle != 0) {
+ real t1 = arctime(p, halfwidth/tanvertexangle+halfwidth);
+ real t2 = arctime(p, arclength(p)-halfwidth/tanvertexangle-halfwidth);
+ draw(pic, subpath(p, t1, t2),
+ bgpen+linewidth(2*halfwidth));
+ }
+}
+
+// returns the path of a gluon line along path p, with amplitude amp and width
+// width (distance between two loops). If width is negative, the width is
+// set to amp*gluonratio
+path gluon(path p, real amp = gluonamplitude, real width=-1)
+{
+ if(width < 0) width = abs(gluonratio*amp);
+
+ real pathlen = arclength(p);
+ int ncurls = floor(pathlen/width);
+ real firstlen = (pathlen - width*(ncurls-1))/2;
+ real firstt = arctime(p, firstlen);
+ pair firstv = dir(p, firstt);
+ guide g = point(p, 0)..{firstv}( point(p, firstt)
+ +amp*unit(rotate(90)*firstv));
+
+ real t1;
+ pair v1;
+ real t2;
+ pair v2;
+ pathlen -= firstlen;
+ for(real len = firstlen+width/2; len < pathlen; len += width) {
+ t1 = arctime(p, len);
+ v1 = dir(p, t1);
+ t2 = arctime(p, len + width/2);
+ v2 = dir(p, t2);
+
+ g=g..{-v1}(point(p, t1)+amp*unit(rotate(-90)*v1))
+ ..{+v2}(point(p, t2)+amp*unit(rotate(+90)*v2));
+ }
+ g = g..point(p, size(p));
+ return g;
+}
+
+// returns the path of a photon line along path p, with amplitude amp and width
+// width (distance between two crests). If width is negative, the width is
+// set to amp*photonratio
+path photon(path p, real amp = photonamplitude, real width=-1)
+{
+ if(width < 0)
+ width = abs(photonratio*amp)/2;
+ else
+ width = width/2;
+
+ real pathlen = arclength(p);
+ int ncurls = floor(pathlen/width);
+ real firstlen = (pathlen - width*ncurls)/2;
+ real firstt = arctime(p, firstlen+width);
+ guide g = point(p, 0){unit(point(p, firstt)-point(p, 0))};
+
+ real t;
+ pair v;
+ pathlen -= firstlen;
+ for(real len = firstlen+width; len < pathlen; len += width) {
+ t = arctime(p, len);
+ v = dir(p, t);
+
+ g=g..{v}(point(p, t)+amp*unit(rotate(90)*v));
+ amp = -amp;
+ }
+ g = g..{unit(point(p, size(p))-point(p, t))}point(p, size(p));
+ return g;
+}
+
+// returns the path of a momentum arrow along path p, with length length,
+// an offset offset from the path p and at position position. position will
+// usually be one of the predefined pairs left or right. Making adjust
+// nonzero shifts the momentum arrow along the path.
+path momArrowPath(path p,
+ align align,
+ position pos,
+ real offset = momarrowoffset,
+ real length = momarrowlength)
+{
+ real pathlen = arclength(p);
+
+ real t1, t2;
+ if(pos.relative) {
+ t1 = arctime(p, (pathlen-length)*pos.position.x);
+ t2 = arctime(p, (pathlen-length)*pos.position.x+length);
+ } else {
+ t1 = arctime(p, (pathlen-length)/2 + pos.position.x);
+ t2 = arctime(p, (pathlen+length)/2+ pos.position.x);
+ }
+
+ pair v1 = dir(p, t1);
+ pair v2 = dir(p, t2);
+
+ pair p1, p2;
+ if(align.relative) {
+ p1 = point(p, t1) + offset*abs(align.dir)
+ *unit(rotate(degrees(align.dir)-90)*v1);
+ p2 = point(p, t2) + offset*abs(align.dir)
+ *unit(rotate(degrees(align.dir)-90)*v2);
+ } else {
+ p1 = point(p, t1) + offset*align.dir;
+ p2 = point(p, t2) + offset*align.dir;
+ }
+
+ return p1{v1}..{v2}p2;
+}
+
+
+
+
+/* drawing functions *********************************************************/
+
+// draw a gluon line on picture pic, along path p, with amplitude amp, width
+// width (distance between loops) and with pen fgpen. If erasebg is true,
+// bgpen is used to erase the background behind the line and at a margin
+// margin around it. The background is not erased at a certain distance to
+// the endpoints, which is determined by vertexangle (see comments to the
+// default parameter minvertexangle). For negative values of width, the width
+// is set to gluonratio*amp.
+void drawGluon(picture pic = currentpicture,
+ path p,
+ real amp = gluonamplitude,
+ real width = -1,
+ pen fgpen = gluonpen,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen,
+ real vertexangle = minvertexangle,
+ real margin = linemargin)
+{
+ if(width < 0) width = abs(2*amp);
+
+ if(erasebg) do_overpaint(pic, p, bgpen, amp+margin, vertexangle);
+ draw(pic, gluon(p, amp, width), fgpen);
+}
+
+// draw a photon line on picture pic, along path p, with amplitude amp, width
+// width (distance between loops) and with pen fgpen. If erasebg is true,
+// bgpen is used to erase the background behind the line and at a margin
+// margin around it. The background is not erased at a certain distance to
+// the endpoints, which is determined by vertexangle (see comments to the
+// default parameter minvertexangle). For negative values of width, the width
+// is set to photonratio*amp.
+void drawPhoton(picture pic = currentpicture,
+ path p,
+ real amp = photonamplitude,
+ real width = -1,
+ pen fgpen = currentpen,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen,
+ real vertexangle = minvertexangle,
+ real margin = linemargin)
+{
+ if(width < 0) width = abs(4*amp);
+
+ if(erasebg) do_overpaint(pic, p, bgpen, amp+margin, vertexangle);
+ draw(pic, photon(p, amp, width), fgpen);
+}
+
+// draw a fermion line on picture pic, along path p with pen fgpen and an
+// arrowhead arrow. If erasebg is true, bgpen is used to erase the background
+// at a margin margin around the line. The background is not erased at a
+// certain distance to the endpoints, which is determined by vertexangle
+// (see comments to the default parameter minvertexangle).
+void drawFermion(picture pic = currentpicture,
+ path p,
+ pen fgpen = currentpen,
+ arrowbar arrow = currentarrow,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen,
+ real vertexangle = minvertexangle,
+ real margin = linemargin)
+{
+ if(erasebg) do_overpaint(pic, p, bgpen,
+ linewidth(fgpen)+margin, vertexangle);
+ draw(pic, p, fgpen, arrow);
+}
+
+// draw a scalar line on picture pic, along path p with pen fgpen and an
+// arrowhead arrow. If erasebg is true, bgpen is used to erase the background
+// at a margin margin around the line. The background is not erased at a
+// certain distance to the endpoints, which is determined by vertexangle
+// (see comments to the default parameter minvertexangle).
+void drawScalar(picture pic = currentpicture,
+ path p,
+ pen fgpen = scalarpen,
+ arrowbar arrow = currentarrow,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen,
+ real vertexangle = minvertexangle,
+ real margin = linemargin)
+{
+ if(erasebg) do_overpaint(pic, p, bgpen,
+ linewidth(fgpen)+margin, vertexangle);
+ draw(pic, p, fgpen, arrow);
+}
+
+// draw a ghost line on picture pic, along path p with pen fgpen and an
+// arrowhead arrow. If erasebg is true, bgpen is used to erase the background
+// at a margin margin around the line. The background is not erased at a
+// certain distance to the endpoints, which is determined by vertexangle
+// (see comments to the default parameter minvertexangle).
+void drawGhost(picture pic = currentpicture,
+ path p,
+ pen fgpen = ghostpen,
+ arrowbar arrow = currentarrow,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen,
+ real vertexangle = minvertexangle,
+ real margin = linemargin)
+{
+ if(erasebg) do_overpaint(pic, p, bgpen,
+ linewidth(fgpen)+margin, vertexangle);
+ draw(pic, p, fgpen, arrow);
+}
+
+// draw a double line on picture pic, along path p with pen fgpen, an inner
+// spacing of dlspacint and an arrowhead arrow. If erasebg is true, bgpen is
+// used to erase the background at a margin margin around the line. The
+// background is not erased at a certain distance to the endpoints, which is
+// determined by vertexangle (see comments to the default parameter
+// minvertexangle).
+void drawDoubleLine(picture pic = currentpicture,
+ path p,
+ pen fgpen = doublelinepen,
+ real dlspacing = doublelinespacing,
+ arrowbar arrow = currentarrow,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen,
+ real vertexangle = minvertexangle,
+ real margin = linemargin)
+{
+ if(erasebg) do_overpaint(pic, p, bgpen,
+ linewidth(fgpen)+margin, vertexangle);
+
+ real htw = linewidth(fgpen)+dlspacing/2;
+ draw(pic, p, fgpen+2*htw);
+ draw(pic, p, bgpen+(linewidth(dlspacing)));
+ path rect = (-htw,-htw)--(-htw,htw)--(0,htw)--(0,-htw)--cycle;
+ fill(shift(point(p,0))*rotate(degrees(dir(p,0)))*rect, bgpen);
+ fill(shift(point(p,size(p)))*scale(-1)*rotate(degrees(dir(p,size(p))))*
+ rect,bgpen);
+ draw(pic, p, invisible, arrow);
+}
+
+// draw a vertex dot on picture pic, at position xy with radius r and pen
+// fgpen
+void drawVertex(picture pic = currentpicture,
+ pair xy,
+ real r = vertexsize,
+ pen fgpen = vertexpen)
+{
+ fill(pic, circle(xy, r), fgpen);
+}
+
+// draw an empty vertex dot on picture pic, at position xy with radius r
+// and pen fgpen. If erasebg is true, the background is erased in the inside
+// of the circle.
+void drawVertexO(picture pic = currentpicture,
+ pair xy,
+ real r = vertexsize,
+ pen fgpen = vertexpen,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen)
+{
+ if(erasebg)
+ filldraw(pic, circle(xy, r), bgpen, fgpen);
+ else
+ draw(pic, circle(xy, r), fgpen);
+}
+
+// draw a vertex triangle on picture pic, at position xy with radius r and pen
+// fgpen
+void drawVertexTriangle(picture pic = currentpicture,
+ pair xy,
+ real r = vertexsize,
+ pen fgpen = vertexpen)
+{
+ real cospi6 = cos(pi/6);
+ real sinpi6 = sin(pi/6);
+ path triangle = (cospi6,-sinpi6)--(0,1)--(-cospi6,-sinpi6)--cycle;
+ fill(pic, shift(xy)*scale(r)*triangle, fgpen);
+}
+
+// draw an empty vertex triangle on picture pic, at position xy with size r
+// and pen fgpen. If erasebg is true, the background is erased in the inside
+// of the triangle.
+void drawVertexTriangleO(picture pic = currentpicture,
+ pair xy,
+ real r = vertexsize,
+ pen fgpen = vertexpen,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen)
+{
+ real cospi6 = cos(pi/6);
+ real sinpi6 = sin(pi/6);
+ path triangle = (cospi6,-sinpi6)--(0,1)--(-cospi6,-sinpi6)--cycle;
+
+ if(erasebg)
+ filldraw(pic, shift(xy)*scale(r)*triangle, bgpen, fgpen);
+ else
+ draw(pic, shift(xy)*scale(r)*triangle, fgpen);
+}
+
+// draw a vertex box on picture pic, at position xy with radius r and pen
+// fgpen
+void drawVertexBox(picture pic = currentpicture,
+ pair xy,
+ real r = vertexsize,
+ pen fgpen = vertexpen)
+{
+ path box = (1,1)--(-1,1)--(-1,-1)--(1,-1)--cycle;
+ fill(pic, shift(xy)*scale(r)*box, fgpen);
+}
+
+// draw an empty vertex box on picture pic, at position xy with size r
+// and pen fgpen. If erasebg is true, the background is erased in the inside
+// of the box.
+void drawVertexBoxO(picture pic = currentpicture,
+ pair xy,
+ real r = vertexsize,
+ pen fgpen = vertexpen,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen)
+{
+ path box = (1,1)--(-1,1)--(-1,-1)--(1,-1)--cycle;
+ if(erasebg)
+ filldraw(pic, shift(xy)*scale(r)*box, bgpen, fgpen);
+ else
+ draw(pic, shift(xy)*scale(r)*box, fgpen);
+}
+
+// draw an X on picture pic, at position xy with size r and pen
+// fgpen
+void drawVertexX(picture pic = currentpicture,
+ pair xy,
+ real r = vertexsize,
+ pen fgpen = vertexpen)
+{
+ draw(pic, shift(xy)*scale(r)*((-1,-1)--(1,1)), fgpen);
+ draw(pic, shift(xy)*scale(r)*((1,-1)--(-1,1)), fgpen);
+}
+
+// draw a circle with an X in the middle on picture pic, at position xy with
+// size r and pen fgpen. If erasebg is true, the background is erased in the
+// inside of the circle.
+void drawVertexOX(picture pic = currentpicture,
+ pair xy,
+ real r = bigvertexsize,
+ pen fgpen = vertexpen,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen)
+{
+ if(erasebg)
+ filldraw(pic, circle(xy, r), bgpen, fgpen);
+ else
+ draw(pic, circle(xy, r), fgpen);
+ draw(pic, shift(xy)*scale(r)*(NW--SE), fgpen);
+ draw(pic, shift(xy)*scale(r)*(SW--NE), fgpen);
+}
+
+// draw a box with an X in the middle on picture pic, at position xy with
+// size r and pen fgpen. If erasebg is true, the background is erased in the
+// inside of the box.
+void drawVertexBoxX(picture pic = currentpicture,
+ pair xy,
+ real r = bigvertexsize,
+ pen fgpen = vertexpen,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen)
+{
+ path box = (1,1)--(-1,1)--(-1,-1)--(1,-1)--cycle;
+ box = shift(xy)*scale(r)*box;
+ if(erasebg)
+ filldraw(pic, box, bgpen, fgpen);
+ else
+ draw(pic, box, fgpen);
+ draw(pic, shift(xy)*scale(r)*((-1,-1)--(1,1)), fgpen);
+ draw(pic, shift(xy)*scale(r)*((1,-1)--(-1,1)), fgpen);
+}
+
+// draw a momentum arrow on picture pic, along path p, at position position
+// (use one of the predefined pairs left or right), with an offset offset
+// from the path, a length length, a pen fgpen and an arrowhead arrow. Making
+// adjust nonzero shifts the momentum arrow along the path. If erasebg is true,
+// the background is erased inside a margin margin around the momentum arrow.
+// Make sure that offset and margin are chosen in such a way that the momentum
+// arrow does not overdraw the particle line.
+void drawMomArrow(picture pic = currentpicture,
+ path p,
+ align align,
+ position pos = MidPoint,
+ real offset = momarrowoffset,
+ real length = momarrowlength,
+ pen fgpen = momarrowpen,
+ arrowbar arrow = currentmomarrow,
+ bool erasebg = overpaint,
+ pen bgpen = backgroundpen,
+ real margin = momarrowmargin)
+{
+ path momarrow = momArrowPath(p, align, pos, offset, length);
+ if(erasebg) do_overpaint(pic, momarrow, bgpen,
+ linewidth(fgpen)+margin, 90);
+ draw(pic, momarrow, fgpen, arrow);
+}
+
+
+/* initialisation ************************************************************/
+
+// The function fmdefaults() tries to guess reasonable values for the
+// default parameters above by looking at the default parameters of plain.asy
+// (essentially, currentpen, arrowfactor and dotfactor). After customising the
+// default parameters of plain.asy, you may call fmdefaults to adjust the
+// parameters of feynman.asy.
+void fmdefaults()
+{
+ real arrowsize=arrowsize(currentpen);
+ real linewidth=linewidth(currentpen);
+
+ gluonratio = 2;
+ photonratio = 4;
+ gluonamplitude = arrowsize/3;
+ photonamplitude = arrowsize/4;
+
+ backgroundpen = white;
+ gluonpen = currentpen;
+ photonpen = currentpen;
+ fermionpen = currentpen;
+ scalarpen = dashed+linewidth;
+ ghostpen = dotted+linewidth;
+ doublelinepen = currentpen;
+ vertexpen = currentpen;
+ bigvertexpen = currentpen;
+ currentarrow = MidArrow;
+
+ doublelinespacing = 2*linewidth;
+ linemargin = 0.5*arrowsize;
+ minvertexangle = 30;
+ overpaint = true;
+ vertexsize = 0.5*dotfactor*linewidth;
+ bigvertexsize = 0.4*arrowsize;
+
+ momarrowfactor = 1.5*arrowfactor;
+ momarrowlength = 2.5*arrowsize;
+ momarrowpen = currentpen+0.5*linewidth;
+ momarrowoffset = 0.8*arrowsize;
+ momarrowmargin = 0.25*arrowsize;
+ currentmomarrow = EndArrow(momarrowsize());
+
+ includegraphicscommand = "\includegraphics";
+ appendsuffix = false;
+}
+
+// We call fmdefaults once, when the module is loaded.
+fmdefaults();
+
+
+/* shipout *******************************************************************/
+
+bool YAlign = false;
+bool XYAlign = true;
+
+// texshipout("filename", pic) creates two files: filename.eps holding the
+// picture pic and filename.tex holding some LaTeX code that includes the
+// picture from filename.eps and shifts it vertically in such a way that the
+// point (0,0) lies on the baseline.
+void texshipout(string stem,
+ picture pic = currentpicture,
+ bool xalign = YAlign)
+{
+ file tf = output(stem + ".tex");
+ pair min=pic.min();
+ real depth = min.y;
+ real xoffset = min.x;
+ if(xalign) {
+ write(tf, "\makebox[0pt][l]{\kern");
+ write(tf, xoffset);
+ write(tf, "bp\relax");
+ }
+ write(tf, "\raisebox{");
+ write(tf, depth);
+ write(tf, "bp}{"+includegraphicscommand+"{");
+ write(tf, stem);
+ string suffix="."+nativeformat();
+ if(appendsuffix)
+ write(tf, suffix);
+ write(tf, "}}");
+ if(xalign)
+ write(tf, "}");
+ close(tf);
+ shipout(stem+suffix, pic);
+}
+
+
diff --git a/graphics/asymptote/base/flowchart.asy b/graphics/asymptote/base/flowchart.asy
new file mode 100644
index 0000000000..3db6617375
--- /dev/null
+++ b/graphics/asymptote/base/flowchart.asy
@@ -0,0 +1,526 @@
+// Flowchart routines written by Jacques Pienaar, Steve Melenchuk, John Bowman.
+
+private import math;
+
+struct flowdir {}
+
+restricted flowdir Horizontal;
+restricted flowdir Vertical;
+
+real minblockwidth=0;
+real minblockheight=0;
+real mincirclediameter=0;
+real defaultexcursion=0.1;
+
+struct block {
+ // The absolute center of the block in user coordinates.
+ pair center;
+
+ // The size of the block
+ pair size;
+
+ // The relative center of the block.
+ pair f_center;
+
+ // These eight variables return the appropriate location on the block
+ // in relative coordinates, where the lower left corner of the block is (0,0).
+ pair f_top;
+ pair f_left;
+ pair f_right;
+ pair f_bottom;
+ pair f_topleft;
+ pair f_topright;
+ pair f_bottomleft;
+ pair f_bottomright;
+
+ void operator init(pair z) {
+ center=z;
+ }
+
+ void operator init(real x, real y) {
+ center=(x,y);
+ }
+
+ pair shift(transform t=identity()) {
+ return t*center-f_center;
+ }
+
+ // Returns the relative position along the boundary of the block.
+ pair f_position(real x);
+
+ // Returns the absolute position along the boundary of the block.
+ pair position(real x, transform t=identity()) {
+ return shift(t)+f_position(x);
+ }
+
+ // These eight functions return the appropriate location on the block
+ // in absolute coordinates.
+ pair top(transform t=identity()) {
+ return shift(t)+f_top;
+ }
+ pair bottom(transform t=identity()) {
+ return shift(t)+f_bottom;
+ }
+ pair left(transform t=identity()) {
+ return shift(t)+f_left;
+ }
+ pair right(transform t=identity()) {
+ return shift(t)+f_right;
+ }
+ pair topleft(transform t=identity()) {
+ return shift(t)+f_topleft;
+ }
+ pair topright(transform t=identity()) {
+ return shift(t)+f_topright;
+ }
+ pair bottomleft(transform t=identity()) {
+ return shift(t)+f_bottomleft;
+ }
+ pair bottomright(transform t=identity()) {
+ return shift(t)+f_bottomright;
+ }
+
+ // Return a frame representing the block.
+ frame draw(pen p=currentpen);
+
+ // Store optional label on outgoing edge.
+ Label label;
+
+ // Store rectilinear path directions.
+ pair[] dirs;
+
+ // Store optional arrow.
+ arrowbar arrow=None;
+};
+
+// Construct a rectangular block with header and body objects.
+block rectangle(object header, object body, pair center=(0,0),
+ pen headerpen=mediumgray, pen bodypen=invisible,
+ pen drawpen=currentpen,
+ real dx=3, real minheaderwidth=minblockwidth,
+ real minheaderheight=minblockwidth,
+ real minbodywidth=minblockheight,
+ real minbodyheight=minblockheight)
+{
+ frame fbody=body.f;
+ frame fheader=header.f;
+ pair mheader=min(fheader);
+ pair Mheader=max(fheader);
+ pair mbody=min(fbody);
+ pair Mbody=max(fbody);
+ pair bound0=Mheader-mheader;
+ pair bound1=Mbody-mbody;
+ real width=max(bound0.x,bound1.x);
+ pair z0=maxbound((width+2dx,bound0.y+2dx),(minbodywidth,minbodyheight));
+ pair z1=maxbound((width+2dx,bound1.y+2dx),(minheaderwidth,minheaderheight));
+ path shape=(0,0)--(0,z1.y)--(0,z0.y+z1.y)--(z0.x,z0.y+z1.y)--z1--(z0.x,0)--
+ cycle;
+
+ block block;
+ block.draw=new frame(pen p) {
+ frame block;
+ filldraw(block,shift(0,z1.y)*box((0,0),z0),headerpen,drawpen);
+ add(block,shift(-0.5*(Mheader+mheader))*fheader,(0,z1.y)+0.5z0);
+ filldraw(block,box((0,0),z1),bodypen,drawpen);
+ add(block,shift(-0.5*(Mbody+mbody))*fbody,0.5z1);
+ return block;
+ };
+ block.f_position=new pair(real x) {
+ return point(shape,x);
+ };
+ block.f_center=interp(point(shape,0),point(shape,3),0.5);
+ block.f_bottomleft=point(shape,0);
+ block.f_bottom=point(shape,5.5);
+ block.f_bottomright=point(shape,5);
+ block.f_right=point(shape,4.5);
+ block.f_topright=point(shape,3);
+ block.f_top=point(shape,2.5);
+ block.f_topleft=point(shape,2);
+ block.f_left=point(shape,0.5);
+ block.center=center;
+ block.size=point(shape,3);
+ return block;
+}
+
+// As above, but without the header.
+block rectangle(object body, pair center=(0,0),
+ pen fillpen=invisible, pen drawpen=currentpen,
+ real dx=3, real minwidth=minblockwidth,
+ real minheight=minblockheight)
+{
+ frame f=body.f;
+ pair m=min(f);
+ pair M=max(f);
+ pair z=maxbound(M-m+dx*(2,2),(minwidth,minheight));
+ path shape=box((0,0),z);
+
+ block block;
+ block.draw=new frame(pen p) {
+ frame block;
+ filldraw(block,shape,fillpen,drawpen);
+ add(block,shift(-0.5*(M+m))*f,0.5z);
+ return block;
+ };
+ block.f_position=new pair(real x) {
+ return point(shape,x);
+ };
+ block.f_center=0.5*z;
+ block.center=center;
+ block.size=z;
+ block.f_bottomleft=point(shape,0);
+ block.f_bottom=point(shape,0.5);
+ block.f_bottomright=point(shape,1);
+ block.f_right=point(shape,1.5);
+ block.f_topright=point(shape,2);
+ block.f_top=point(shape,2.5);
+ block.f_topleft=point(shape,3);
+ block.f_left=point(shape,3.5);
+ return block;
+}
+
+block parallelogram(object body, pair center=(0,0),
+ pen fillpen=invisible, pen drawpen=currentpen,
+ real dx=3, real slope=2,
+ real minwidth=minblockwidth,
+ real minheight=minblockheight)
+{
+ frame f=body.f;
+ pair m=min(f);
+ pair M=max(f);
+ pair bound=maxbound(M-m+dx*(0,2),(minwidth,minheight));
+
+ real skew=bound.y/slope;
+ real a=bound.x+skew;
+ real b=bound.y;
+
+ path shape=(0,0)--(a,0)--(a+skew,b)--(skew,b)--cycle;
+
+ block block;
+ block.draw=new frame(pen p) {
+ frame block;
+ filldraw(block,shape,fillpen,drawpen);
+ add(block,shift(-0.5*(M+m))*f,((a+skew)/2,b/2));
+ return block;
+ };
+ block.f_position=new pair(real x) {
+ return point(shape,x);
+ };
+ block.f_center=((a+skew)/2,b/2);
+ block.center=center;
+ block.size=(a+skew,b);
+ block.f_bottomleft=(0,0);
+ block.f_bottom=((a+skew)/2,0);
+ block.f_bottomright=(a,0);
+ block.f_right=(a+skew/2,b/2);
+ block.f_topright=(a+skew,b);
+ block.f_top=((a+skew)/2,b);
+ block.f_topleft=(skew,b);
+ block.f_left=(skew/2,b/2);
+ return block;
+}
+
+block diamond(object body, pair center=(0,0),
+ pen fillpen=invisible, pen drawpen=currentpen,
+ real ds=5, real dw=1,
+ real height=20, real minwidth=minblockwidth,
+ real minheight=minblockheight)
+{
+ frame f=body.f;
+ pair m=min(f);
+ pair M=max(f);
+ pair bound=maxbound(M-m,(minwidth,minheight));
+
+ real e=ds;
+ real a=0.5bound.x-dw;
+ real b=0.5bound.y;
+ real c=b+height;
+
+ real arg=a^2+b^2+c^2-2b*c-e^2;
+ real denom=e^2-a^2;
+ real slope=arg >= 0 && denom != 0 ? (a*(c-b)-e*sqrt(arg))/denom : 1.0;
+ real d=abs(c/slope);
+
+ path shape=(2d,c)--(d,2c)--(0,c)--(d,0)--cycle;
+
+ block block;
+ block.draw=new frame(pen p) {
+ frame block;
+ filldraw(block,shape,fillpen,drawpen);
+ add(block,shift(-0.5*(M+m))*f,(d,c));
+ return block;
+ };
+ block.f_position=new pair(real x) {
+ return point(shape,x);
+ };
+ block.f_center=(point(shape,1).x,point(shape,0).y);
+ block.center=center;
+ block.size=(point(shape,0).x,point(shape,1).y);
+ block.f_bottomleft=point(shape,2.5);
+ block.f_bottom=point(shape,3);
+ block.f_bottomright=point(shape,3.5);
+ block.f_right=point(shape,0);
+ block.f_topright=point(shape,0.5);
+ block.f_top=point(shape,1);
+ block.f_topleft=point(shape,1.5);
+ block.f_left=point(shape,2);
+ return block;
+}
+
+block circle(object body, pair center=(0,0), pen fillpen=invisible,
+ pen drawpen=currentpen, real dr=3,
+ real mindiameter=mincirclediameter)
+{
+ frame f=body.f;
+ pair m=min(f);
+ pair M=max(f);
+ real r=max(0.5length(M-m)+dr,0.5mindiameter);
+
+ path shape=(0,r)..(r,2r)..(2r,r)..(r,0)..cycle;
+
+ block block;
+ block.draw=new frame(pen p) {
+ frame block;
+ filldraw(block,shape,fillpen,drawpen);
+ add(block,shift(-0.5*(M+m))*f,(r,r));
+ return block;
+ };
+ block.f_position=new pair(real x) {
+ return point(shape,x);
+ };
+ block.f_center=(r,r);
+ block.center=center;
+ block.size=(2r,2r);
+ block.f_left=point(shape,0);
+ block.f_topleft=point(shape,0.5);
+ block.f_top=point(shape,1);
+ block.f_topright=point(shape,1.5);
+ block.f_right=point(shape,2);
+ block.f_bottomright=point(shape,2.5);
+ block.f_bottom=point(shape,3);
+ block.f_bottomleft=point(shape,3.5);
+ return block;
+}
+
+block roundrectangle(object body, pair center=(0,0),
+ pen fillpen=invisible, pen drawpen=currentpen,
+ real ds=5, real dw=0, real minwidth=minblockwidth,
+ real minheight=minblockheight)
+{
+ frame f=body.f;
+ pair m=min(f);
+ pair M=max(f);
+ pair bound=maxbound(M-m,(minwidth,minheight));
+
+ real a=bound.x;
+ real b=bound.y;
+
+ path shape=(0,ds+dw)--(0,ds+b-dw){up}..{right}
+ (ds+dw,2ds+b)--(ds+a-dw,2ds+b){right}..{down}
+ (2ds+a,ds+b-dw)--(2ds+a,ds+dw){down}..{left}
+ (ds+a-dw,0)--(ds+dw,0){left}..{up}cycle;
+
+ block block;
+ block.draw=new frame(pen p) {
+ frame block;
+ filldraw(block,shape,fillpen,drawpen);
+ add(block,shift(-0.5*(M+m))*f,(ds,ds)+0.5bound);
+ return block;
+ };
+ block.f_position=new pair(real x) {
+ return point(shape,x);
+ };
+ block.f_center=(ds+0.5a,ds+0.5b);
+ block.center=center;
+ block.size=(2ds+a,2ds+b);
+ block.f_bottomleft=point(shape,7.5);
+ block.f_bottom=point(shape,6.5);
+ block.f_bottomright=point(shape,5.5);
+ block.f_right=point(shape,4.5);
+ block.f_topright=point(shape,3.5);
+ block.f_top=point(shape,2.5);
+ block.f_topleft=point(shape,1.5);
+ block.f_left=point(shape,0.5);
+ return block;
+}
+
+block bevel(object body, pair center=(0,0), pen fillpen=invisible,
+ pen drawpen=currentpen, real dh=5, real dw=5,
+ real minwidth=minblockwidth, real minheight=minblockheight)
+{
+ frame f=body.f;
+ pair m=min(f);
+ pair M=max(f);
+ pair bound=maxbound(M-m,(minwidth,minheight));
+
+ real a=bound.x;
+ real b=0.5bound.y;
+
+ path shape=(2dw+a,b+dh)--(dw+a,2b+2dh)--(dw,2b+2dh)--(0,b+dh)--(dw,0)--
+ (dw+a,0)--cycle;
+ block block;
+ block.draw=new frame(pen p) {
+ frame block;
+ filldraw(block,shape,fillpen,drawpen);
+ add(block,shift(-0.5*(M+m))*f,(0.5bound+(dw,dh)));
+ return block;
+ };
+ block.f_position=new pair(real x) {
+ return point(shape,x);
+ };
+ block.f_center=(dw+0.5a,dh+b);
+ block.center=center;
+ block.size=(2dw+a,2dh+2b);
+ block.f_bottomleft=point(shape,4);
+ block.f_bottom=point(shape,4.5);
+ block.f_bottomright=point(shape,5);
+ block.f_right=point(shape,0);
+ block.f_topright=point(shape,1);
+ block.f_top=point(shape,1.5);
+ block.f_topleft=point(shape,2);
+ block.f_left=point(shape,3);
+ return block;
+}
+
+path path(pair point[] ... flowdir dir[])
+{
+ path line=point[0];
+ pair current, prev=point[0];
+ for(int i=1; i < point.length; ++i) {
+ if(i-1 >= dir.length || dir[i-1] == Horizontal)
+ current=(point[i].x,point[i-1].y);
+ else
+ current=(point[i-1].x,point[i].y);
+
+ if(current != prev) {
+ line=line--current;
+ prev=current;
+ }
+
+ current=point[i];
+ if(current != prev) {
+ line=line--current;
+ prev=current;
+ }
+ }
+ return line;
+}
+
+void draw(picture pic=currentpicture, block block, pen p=currentpen)
+{
+ pic.add(new void(frame f, transform t) {
+ add(f,shift(block.shift(t))*block.draw(p));
+ },true);
+ pic.addBox(block.center,block.center,
+ -0.5*block.size+min(p),0.5*block.size+max(p));
+}
+
+typedef block blockconnector(block, block);
+
+blockconnector blockconnector(picture pic, transform t, pen p=currentpen,
+ margin margin=PenMargin)
+{
+ return new block(block b1, block b2) {
+ if(b1.dirs.length == 0) {
+ if(abs(b1.center.y-b2.center.y) < sqrtEpsilon) {
+ // horizontally aligned
+ b1.dirs[0]=b1.center.x < b2.center.x ? right : left;
+ blockconnector(pic,t,p,margin)(b1,b2);
+ } else if(abs(b1.center.x-b2.center.x) < sqrtEpsilon) {
+ // vertically aligned
+ b1.dirs[0]=b1.center.y < b2.center.y ? up : down;
+ blockconnector(pic,t,p,margin)(b1,b2);
+ } else {
+ if(abs(b1.center.y-b2.center.y) < abs(b1.center.x-b2.center.x)) {
+ b1.dirs[0]=b1.center.x < b2.center.x ? right : left;
+ b1.dirs[1]=b1.center.y < b2.center.y ? up : down;
+ blockconnector(pic,t,p,margin)(b1,b2);
+ } else {
+ b1.dirs[0]=b1.center.y < b2.center.y ? up : down;
+ b1.dirs[1]=b1.center.x < b2.center.x ? right : left;
+ blockconnector(pic,t,p,margin)(b1,b2);
+ }
+ }
+ return b2;
+ }
+
+ // compute the link for given directions (and label if any)
+ pair[] dirs=copy(b1.dirs); // deep copy
+ pair current,prev;
+ pair dir=dirs[0];
+ if(dir == up) prev=b1.top(t);
+ if(dir == down) prev=b1.bottom(t);
+ if(dir == left) prev=b1.left(t);
+ if(dir == right) prev=b1.right(t);
+ path line=prev;
+ arrowbar arrow=b1.arrow;
+
+ int i;
+ for(i=1; i < dirs.length-1; ++i) {
+ if(abs(length(dirs[i-1])-1) < sqrtEpsilon)
+ current=prev+t*dirs[i-1]*defaultexcursion;
+ else
+ current=prev+t*dirs[i-1];
+
+ if(current != prev) {
+ line=line--current;
+ prev=current;
+ }
+ }
+ dir=dirs[dirs.length-1];
+ current=0;
+ if(dir == up) current=b2.bottom(t);
+ if(dir == down) current=b2.top(t);
+ if(dir == left) current=b2.right(t);
+ if(dir == right) current=b2.left(t);
+ if(abs(dirs[i-1].y) < sqrtEpsilon &&
+ abs(prev.x-current.x) > sqrtEpsilon) {
+ prev=(current.x,prev.y);
+ line=line--prev; // horizontal
+ } else if(abs(dirs[i-1].x) < sqrtEpsilon &&
+ abs(prev.y-current.y) > sqrtEpsilon) {
+ prev=(prev.x,current.y);
+ line=line--prev;
+ }
+ if(current != prev)
+ line=line--current;
+
+ draw(pic,b1.label,line,p,arrow,margin);
+
+ b1.label="";
+ b1.dirs.delete();
+ b1.arrow=None;
+ return b2;
+ };
+}
+
+struct Dir
+{
+ pair z;
+ void operator init(pair z) {this.z=z;}
+}
+
+Dir Right=Dir(right);
+Dir Left=Dir(left);
+Dir Up=Dir(up);
+Dir Down=Dir(down);
+
+// Add a label to the current link
+block operator --(block b1, Label label)
+{
+ b1.label=label;
+ return b1;
+}
+
+// Add a direction to the current link
+block operator --(block b1, Dir dir)
+{
+ b1.dirs.push(dir.z);
+ return b1;
+}
+
+// Add an arrowbar to the current link
+block operator --(block b, arrowbar arrowbar)
+{
+ b.arrow=arrowbar;
+ return b;
+}
diff --git a/graphics/asymptote/base/fontsize.asy b/graphics/asymptote/base/fontsize.asy
new file mode 100644
index 0000000000..54b0182955
--- /dev/null
+++ b/graphics/asymptote/base/fontsize.asy
@@ -0,0 +1 @@
+if(latex()) usepackage("type1cm");
diff --git a/graphics/asymptote/base/geometry.asy b/graphics/asymptote/base/geometry.asy
new file mode 100644
index 0000000000..245c59ec08
--- /dev/null
+++ b/graphics/asymptote/base/geometry.asy
@@ -0,0 +1,7192 @@
+// geometry.asy
+
+// Copyright (C) 2007
+// Author: Philippe IVALDI 2007/09/01
+// http://www.piprime.fr/
+
+// This program is free software ; you can redistribute it and/or modify
+// it under the terms of the GNU Lesser 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
+// Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program ; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+// COMMENTARY:
+// An Asymptote geometry module.
+
+// THANKS:
+// Special thanks to Olivier Guibé for his help in mathematical issues.
+
+// BUGS:
+
+// CODE:
+
+import math;
+import markers;
+
+// A rotation in the direction dir limited to [-90,90]
+// This is useful for rotating text along a line in the direction dir.
+private transform rotate(explicit pair dir)
+{
+ real angle=degrees(dir);
+ if(angle > 90 && angle < 270) angle -= 180;
+ return rotate(angle);
+}
+
+// *=======================================================*
+// *........................HEADER.........................*
+/*<asyxml><variable type="real" signature="epsgeo"><code></asyxml>*/
+real epsgeo = 10 * sqrt(realEpsilon);/*<asyxml></code><documentation>Variable used in the approximate calculations.</documentation></variable></asyxml>*/
+
+/*<asyxml><function type="void" signature="addMargins(picture,real,real,real,real)"><code></asyxml>*/
+void addMargins(picture pic = currentpicture,
+ real lmargin = 0, real bmargin = 0,
+ real rmargin = lmargin, real tmargin = bmargin,
+ bool rigid = true, bool allObject = true)
+{/*<asyxml></code><documentation>Add margins to 'pic' with respect to
+ the current bounding box of 'pic'.
+ If 'rigid' is false, margins are added iff an infinite curve will
+ be prolonged on the margin.
+ If 'allObject' is false, fixed - size objects (such as labels and
+ arrowheads) will be ignored.</documentation></function></asyxml>*/
+ pair m = allObject ? truepoint(pic, SW) : point(pic, SW);
+ pair M = allObject ? truepoint(pic, NE) : point(pic, NE);
+ if(rigid) {
+ draw(m - inverse(pic.calculateTransform()) * (lmargin, bmargin), invisible);
+ draw(M + inverse(pic.calculateTransform()) * (rmargin, tmargin), invisible);
+ } else pic.addBox(m, M, -(lmargin, bmargin), (rmargin, tmargin));
+}
+
+real approximate(real t)
+{
+ real ot = t;
+ if(abs(t - ceil(t)) < epsgeo) ot = ceil(t);
+ else if(abs(t - floor(t)) < epsgeo) ot = floor(t);
+ return ot;
+}
+
+real[] approximate(real[] T)
+{
+ return map(approximate, T);
+}
+
+/*<asyxml><function type="real" signature="binomial(real,real)"><code></asyxml>*/
+real binomial(real n, real k)
+{/*<asyxml></code><documentation>Return n!/((n - k)!*k!)</documentation></function></asyxml>*/
+ return gamma(n + 1)/(gamma(n - k + 1) * gamma(k + 1));
+}
+
+/*<asyxml><function type="real" signature="rf(real,real,real)"><code></asyxml>*/
+real rf(real x, real y, real z)
+{/*<asyxml></code><documentation>Computes Carlson's elliptic integral of the first kind.
+ x, y, and z must be non negative, and at most one can be zero.</documentation></function></asyxml>*/
+ real ERRTOL = 0.0025,
+ TINY = 1.5e-38,
+ BIG = 3e37,
+ THIRD = 1/3,
+ C1 = 1/24,
+ C2 = 0.1,
+ C3 = 3/44,
+ C4 = 1/14;
+ real alamb, ave, delx, dely, delz, e2, e3, sqrtx, sqrty, sqrtz, xt, yt, zt;
+ if(min(x, y, z) < 0 || min(x + y, x + z, y + z) < TINY ||
+ max(x, y, z) > BIG) abort("rf: invalid arguments.");
+ xt = x;
+ yt = y;
+ zt = z;
+ do {
+ sqrtx = sqrt(xt);
+ sqrty = sqrt(yt);
+ sqrtz = sqrt(zt);
+ alamb = sqrtx * (sqrty + sqrtz) + sqrty * sqrtz;
+ xt = 0.25 * (xt + alamb);
+ yt = 0.25 * (yt + alamb);
+ zt = 0.25 * (zt + alamb);
+ ave = THIRD * (xt + yt + zt);
+ delx = (ave - xt)/ave;
+ dely = (ave - yt)/ave;
+ delz = (ave - zt)/ave;
+ } while(max(fabs(delx), fabs(dely), fabs(delz)) > ERRTOL);
+ e2 = delx * dely - delz * delz;
+ e3 = delx * dely * delz;
+ return (1.0 + (C1 * e2 - C2 - C3 * e3) * e2 + C4 * e3)/sqrt(ave);
+}
+
+/*<asyxml><function type="real" signature="rd(real,real,real)"><code></asyxml>*/
+real rd(real x, real y, real z)
+{/*<asyxml></code><documentation>Computes Carlson's elliptic integral of the second kind.
+ x and y must be positive, and at most one can be zero.
+ z must be non negative.</documentation></function></asyxml>*/
+ real ERRTOL = 0.0015,
+ TINY = 1e-25,
+ BIG = 4.5 * 10.0^21,
+ C1 = (3/14),
+ C2 = (1/6),
+ C3 = (9/22),
+ C4 = (3/26),
+ C5 = (0.25 * C3),
+ C6 = (1.5 * C4);
+ real alamb, ave, delx, dely, delz, ea, eb, ec, ed, ee, fac, sqrtx, sqrty,
+ sqrtz, sum, xt, yt, zt;
+ if (min(x, y) < 0 || min(x + y, z) < TINY || max(x, y, z) > BIG)
+ abort("rd: invalid arguments");
+ xt = x;
+ yt = y;
+ zt = z;
+ sum = 0;
+ fac = 1;
+ do {
+ sqrtx = sqrt(xt);
+ sqrty = sqrt(yt);
+ sqrtz = sqrt(zt);
+ alamb = sqrtx * (sqrty + sqrtz) + sqrty * sqrtz;
+ sum += fac/(sqrtz * (zt + alamb));
+ fac = 0.25 * fac;
+ xt = 0.25 * (xt + alamb);
+ yt = 0.25 * (yt + alamb);
+ zt = 0.25 * (zt + alamb);
+ ave = 0.2 * (xt + yt + 3.0 * zt);
+ delx = (ave - xt)/ave;
+ dely = (ave - yt)/ave;
+ delz = (ave - zt)/ave;
+ } while (max(fabs(delx), fabs(dely), fabs(delz)) > ERRTOL);
+ ea = delx * dely;
+ eb = delz * delz;
+ ec = ea - eb;
+ ed = ea - 6 * eb;
+ ee = ed + ec + ec;
+ return 3 * sum + fac * (1.0 + ed * (-C1 + C5 * ed - C6 * delz * ee)
+ +delz * (C2 * ee + delz * (-C3 * ec + delz * C4 * ea)))/(ave * sqrt(ave));
+}
+
+/*<asyxml><function type="real" signature="elle(real,real)"><code></asyxml>*/
+real elle(real phi, real k)
+{/*<asyxml></code><documentation>Legendre elliptic integral of the 2nd kind,
+ evaluated using Carlson's functions RD and RF.
+ The argument ranges are -infinity < phi < +infinity, 0 <= k * sin(phi) <= 1.</documentation></function></asyxml>*/
+ real result;
+ if (phi >= 0 && phi <= pi/2) {
+ real cc, q, s;
+ s = sin(phi);
+ cc = cos(phi)^2;
+ q = (1 - s * k) * (1 + s * k);
+ result = s * (rf(cc, q, 1) - (s * k)^2 * rd(cc, q, 1)/3);
+ } else
+ if (phi <= pi && phi >= 0) {
+ result = 2 * elle(pi/2, k) - elle(pi - phi, k);
+ } else
+ if (phi <= 3 * pi/2 && phi >= 0) {
+ result = 2 * elle(pi/2, k) + elle(phi - pi, k);
+ } else
+ if (phi <= 2 * pi && phi >= 0) {
+ result = 4 * elle(pi/2, k) - elle(2 * pi - phi, k);
+ } else
+ if (phi >= 0) {
+ int nb = floor(0.5 * phi/pi);
+ result = nb * elle(2 * pi, k) + elle(phi%(2 * pi), k);
+ } else result = -elle(-phi, k);
+ return result;
+}
+
+/*<asyxml><function type="pair[]" signature="intersectionpoints(pair,pair,real,real,real,real,real,real)"><code></asyxml>*/
+pair[] intersectionpoints(pair A, pair B,
+ real a, real b, real c, real d, real f, real g)
+{/*<asyxml></code><documentation>Intersection points with the line (AB) and the quadric curve
+ a * x^2 + b * x * y + c * y^2 + d * x + f * y + g = 0 given in the default coordinate system</documentation></function></asyxml>*/
+ pair[] op;
+ real ap = B.y - A.y,
+ bpp = A.x - B.x,
+ cp = A.y * B.x - A.x * B.y;
+ real sol[];
+ if (abs(ap) > epsgeo) {
+ real aa = ap * c + a * bpp^2/ap - b * bpp,
+ bb = ap * f - bpp * d + 2 * a * bpp * cp/ap - b * cp,
+ cc = ap * g - cp * d + a * cp^2/ap;
+ sol = quadraticroots(aa, bb, cc);
+ for (int i = 0; i < sol.length; ++i) {
+ op.push((-bpp * sol[i]/ap - cp/ap, sol[i]));
+ }
+ } else {
+ real aa = a * bpp,
+ bb = d * bpp - b * cp,
+ cc = g * bpp - cp * f + c * cp^2/bpp;
+ sol = quadraticroots(aa, bb, cc);
+ for (int i = 0; i < sol.length; ++i) {
+ op.push((sol[i], -cp/bpp));
+ }
+ }
+ return op;
+}
+
+/*<asyxml><function type="pair[]" signature="intersectionpoints(pair,pair,real[])"><code></asyxml>*/
+pair[] intersectionpoints(pair A, pair B, real[] equation)
+{/*<asyxml></code><documentation>Return the intersection points of the line AB with
+ the conic whose an equation is
+ equation[0] * x^2 + equation[1] * x * y + equation[2] * y^2 + equation[3] * x + equation[4] * y + equation[5] = 0</documentation></function></asyxml>*/
+ if(equation.length != 6) abort("intersectionpoints: bad length of array for a conic equation.");
+ return intersectionpoints(A, B, equation[0], equation[1], equation[2],
+ equation[3], equation[4], equation[5]);
+}
+// *........................HEADER.........................*
+// *=======================================================*
+
+// *=======================================================*
+// *......................COORDINATES......................*
+
+real EPS = sqrt(realEpsilon);
+
+/*<asyxml><typedef type = "convert" return = "pair" params = "pair"><code></asyxml>*/
+typedef pair convert(pair);/*<asyxml></code><documentation>Function type to convert pair in an other coordinate system.</documentation></typedef></asyxml>*/
+/*<asyxml><typedef type = "abs" return = "real" params = "pair"><code></asyxml>*/
+typedef real abs(pair);/*<asyxml></code><documentation>Function type to calculate modulus of pair.</documentation></typedef></asyxml>*/
+/*<asyxml><typedef type = "dot" return = "real" params = "pair, pair"><code></asyxml>*/
+typedef real dot(pair, pair);/*<asyxml></code><documentation>Function type to calculate dot product.</documentation></typedef></asyxml>*/
+/*<asyxml><typedef type = "polar" return = "pair" params = "real, real"><code></asyxml>*/
+typedef pair polar(real, real);/*<asyxml></code><documentation>Function type to calculate the coordinates from the polar coordinates.</documentation></typedef></asyxml>*/
+
+/*<asyxml><struct signature="coordsys"><code></asyxml>*/
+struct coordsys
+{/*<asyxml></code><documentation>This structure represents a coordinate system in the plane.</documentation></asyxml>*/
+ /*<asyxml><method type = "pair" signature="relativetodefault(pair)"><code></asyxml>*/
+ restricted convert relativetodefault = new pair(pair m){return m;};/*<asyxml></code><documentation>Convert a pair given relatively to this coordinate system to
+ the pair relatively to the default coordinate system.</documentation></method></asyxml>*/
+ /*<asyxml><method type = "pair" signature="defaulttorelativet(pair)"><code></asyxml>*/
+ restricted convert defaulttorelative = new pair(pair m){return m;};/*<asyxml></code><documentation>Convert a pair given relatively to the default coordinate system to
+ the pair relatively to this coordinate system.</documentation></method></asyxml>*/
+ /*<asyxml><method type = "real" signature="dot(pair,pair)"><code></asyxml>*/
+ restricted dot dot = new real(pair m, pair n){return dot(m, n);};/*<asyxml></code><documentation>Return the dot product of this coordinate system.</documentation></method></asyxml>*/
+ /*<asyxml><method type = "real" signature="abs(pair)"><code></asyxml>*/
+ restricted abs abs = new real(pair m){return abs(m);};/*<asyxml></code><documentation>Return the modulus of a pair in this coordinate system.</documentation></method></asyxml>*/
+ /*<asyxml><method type = "pair" signature="polar(real,real)"><code></asyxml>*/
+ restricted polar polar = new pair(real r, real a){return (r * cos(a), r * sin(a));};/*<asyxml></code><documentation>Polar coordinates routine of this coordinate system.</documentation></method></asyxml>*/
+ /*<asyxml><property type = "pair" signature="O,i,j"><code></asyxml>*/
+ restricted pair O = (0, 0), i = (1, 0), j = (0, 1);/*<asyxml></code><documentation>Origin and units vector.</documentation></property></asyxml>*/
+ /*<asyxml><method type = "void" signature="init(convert,convert,polar,dot)"><code></asyxml>*/
+ void init(convert rtd, convert dtr,
+ polar polar, dot dot)
+ {/*<asyxml></code><documentation>The default constructor of the coordinate system.</documentation></method></asyxml>*/
+ this.relativetodefault = rtd;
+ this.defaulttorelative = dtr;
+ this.polar = polar;
+ this.dot = dot;
+ this.abs = new real(pair m){return sqrt(dot(m, m));};;
+ this.O = rtd((0, 0));
+ this.i = rtd((1, 0)) - O;
+ this.j = rtd((0, 1)) - O;
+ }
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><operator type = "bool" signature="==(coordsys,coordsys)"><code></asyxml>*/
+bool operator ==(coordsys c1, coordsys c2)
+{/*<asyxml></code><documentation>Return true iff the coordinate system have the same origin and units vector.</documentation></operator></asyxml>*/
+ return c1.O == c2.O && c1.i == c2.i && c1.j == c2.j;
+}
+
+/*<asyxml><function type="coordsys" signature="cartesiansystem(pair,pair,pair)"><code></asyxml>*/
+coordsys cartesiansystem(pair O = (0, 0), pair i, pair j)
+{/*<asyxml></code><documentation>Return the Cartesian coordinate system (O, i, j).</documentation></function></asyxml>*/
+ coordsys R;
+ real[][] P = {{0, 0}, {0, 0}};
+ real[][] iP;
+ P[0][0] = i.x;
+ P[0][1] = j.x;
+ P[1][0] = i.y;
+ P[1][1] = j.y;
+ iP = inverse(P);
+ real ni = abs(i);
+ real nj = abs(j);
+ real ij = angle(j) - angle(i);
+
+ pair rtd(pair m)
+ {
+ return O + (P[0][0] * m.x + P[0][1] * m.y, P[1][0] * m.x + P[1][1] * m.y);
+ }
+
+ pair dtr(pair m)
+ {
+ m-=O;
+ return (iP[0][0] * m.x + iP[0][1] * m.y, iP[1][0] * m.x + iP[1][1] * m.y);
+ }
+
+ pair polar(real r, real a)
+ {
+ real ca = sin(ij - a)/(ni * sin(ij));
+ real sa = sin(a)/(nj * sin(ij));
+ return r * (ca, sa);
+ }
+
+ real tdot(pair m, pair n)
+ {
+ return m.x * n.x * ni^2 + m.y * n.y * nj^2 + (m.x * n.y + n.x * m.y) * dot(i, j);
+ }
+
+ R.init(rtd, dtr, polar, tdot);
+ return R;
+}
+
+
+/*<asyxml><function type="void" signature="show(picture,Label,Label,Label,coordsys,pen,pen,pen,pen,pen)"><code></asyxml>*/
+void show(picture pic = currentpicture, Label lo = "$O$",
+ Label li = "$\vec{\imath}$",
+ Label lj = "$\vec{\jmath}$",
+ coordsys R,
+ pen dotpen = currentpen, pen xpen = currentpen, pen ypen = xpen,
+ pen ipen = red,
+ pen jpen = ipen,
+ arrowbar arrow = Arrow)
+{/*<asyxml></code><documentation>Draw the components (O, i, j, x - axis, y - axis) of 'R'.</documentation></function></asyxml>*/
+ unravel R;
+ dot(pic, O, dotpen);
+ drawline(pic, O, O + i, xpen);
+ drawline(pic, O, O + j, ypen);
+ draw(pic, li, O--(O + i), ipen, arrow);
+ Label lj = lj.copy();
+ lj.align(lj.align, unit(I * j));
+ draw(pic, lj, O--(O + j), jpen, arrow);
+ draw(pic, lj, O--(O + j), jpen, arrow);
+ Label lo = lo.copy();
+ lo.align(lo.align, -2 * dir(O--O + i, O--O + j));
+ lo.p(dotpen);
+ label(pic, lo, O);
+}
+
+/*<asyxml><operator type = "pair" signature="/(pair,coordsys)"><code></asyxml>*/
+pair operator /(pair p, coordsys R)
+{/*<asyxml></code><documentation>Return the xy - coordinates of 'p' relatively to
+ the coordinate system 'R'.
+ For example, if R = cartesiansystem((1, 2), (1, 0), (0, 1)), (0, 0)/R is (-1, -2).</documentation></operator></asyxml>*/
+ return R.defaulttorelative(p);
+}
+
+/*<asyxml><operator type = "pair" signature="*(coordsys,pair)"><code></asyxml>*/
+pair operator *(coordsys R, pair p)
+{/*<asyxml></code><documentation>Return the coordinates of 'p' given in the
+ xy - coordinates 'R'.
+ For example, if R = cartesiansystem((1, 2), (1, 0), (0, 1)), R * (0, 0) is (1, 2).</documentation></operator></asyxml>*/
+ return R.relativetodefault(p);
+}
+
+/*<asyxml><operator type = "path" signature="*(coordsys,path)"><code></asyxml>*/
+path operator *(coordsys R, path g)
+{/*<asyxml></code><documentation>Return the reconstructed path applying R * pair to each node, pre and post control point of 'g'.</documentation></operator></asyxml>*/
+ guide og = R * point(g, 0);
+ real l = length(g);
+ for(int i = 1; i <= l; ++i)
+ {
+ pair P = R * point(g, i);
+ pair post = R * postcontrol(g, i - 1);
+ pair pre = R * precontrol(g, i);
+ if(i == l && (cyclic(g)))
+ og = og..controls post and pre..cycle;
+ else
+ og = og..controls post and pre..P;
+ }
+ return og;
+}
+
+/*<asyxml><operator type = "coordsys" signature="*(transform,coordsys)"><code></asyxml>*/
+coordsys operator *(transform t,coordsys R)
+{/*<asyxml></code><documentation>Provide transform * coordsys.
+ Note that shiftless(t) is applied to R.i and R.j.</documentation></operator></asyxml>*/
+ coordsys oc;
+ oc = cartesiansystem(t * R.O, shiftless(t) * R.i, shiftless(t) * R.j);
+ return oc;
+}
+
+/*<asyxml><constant type = "coordsys" signature="defaultcoordsys"><code></asyxml>*/
+restricted coordsys defaultcoordsys = cartesiansystem(0, (1, 0), (0, 1));/*<asyxml></code><documentation>One can always refer to the default coordinate system using this constant.</documentation></constant></asyxml>*/
+/*<asyxml><variable type="coordsys" signature="currentcoordsys"><code></asyxml>*/
+coordsys currentcoordsys = defaultcoordsys;/*<asyxml></code><documentation>The coordinate system used by default.</documentation></variable></asyxml>*/
+
+/*<asyxml><struct signature="point"><code></asyxml>*/
+struct point
+{/*<asyxml></code><documentation>This structure replaces the pair to embed its coordinate system.
+ For example, if 'P = point(cartesiansystem((1, 2), i, j), (0, 0))',
+ P is equal to the pair (1, 2).</documentation></asyxml>*/
+ /*<asyxml><property type = "coordsys" signature="coordsys"><code></asyxml>*/
+ coordsys coordsys;/*<asyxml></code><documentation>The coordinate system of this point.</documentation></property><property type = "pair" signature="coordinates"><code></asyxml>*/
+ restricted pair coordinates;/*<asyxml></code><documentation>The coordinates of this point relatively to the coordinate system 'coordsys'.</documentation></property><property type = "real" signature="x, y"><code></asyxml>*/
+ restricted real x, y;/*<asyxml></code><documentation>The xpart and the ypart of 'coordinates'.</documentation></property></asyxml>*/
+ /*<asyxml><method type = "" signature="init(coordsys,pair)"><code><property type = "real" signature="m"><code></asyxml>*/
+ real m = 1;/*<asyxml></code><documentation>Used to cast mass<->point.</documentation></property></asyxml>*/
+ void init(coordsys R, pair coordinates, real mass)
+ {/*<asyxml></code><documentation>The constructor.</documentation></method></asyxml>*/
+ this.coordsys = R;
+ this.coordinates = coordinates;
+ this.x = coordinates.x;
+ this.y = coordinates.y;
+ this.m = mass;
+ }
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><function type="point" signature="point(coordsys,pair,real)"><code></asyxml>*/
+point point(coordsys R, pair p, real m = 1)
+{/*<asyxml></code><documentation>Return the point which has the coodinates 'p' in the
+ coordinate system 'R' and the mass 'm'.</documentation></function></asyxml>*/
+ point op;
+ op.init(R, p, m);
+ return op;
+}
+
+/*<asyxml><function type="point" signature="point(explicit pair,real)"><code></asyxml>*/
+point point(explicit pair p, real m)
+{/*<asyxml></code><documentation>Return the point which has the coodinates 'p' in the current
+ coordinate system and the mass 'm'.</documentation></function></asyxml>*/
+ point op;
+ op.init(currentcoordsys, p, m);
+ return op;
+}
+
+/*<asyxml><function type="point" signature="point(coordsys,explicit point,real)"><code></asyxml>*/
+point point(coordsys R, explicit point M, real m = M.m)
+{/*<asyxml></code><documentation>Return the point of 'R' which has the coordinates of 'M' and the mass 'm'.
+ Do not confuse this routine with the further routine 'changecoordsys'.</documentation></function></asyxml>*/
+ point op;
+ op.init(R, M.coordinates, M.m);
+ return op;
+}
+
+/*<asyxml><function type="point" signature="changecoordsys(coordsys,point)"><code></asyxml>*/
+point changecoordsys(coordsys R, point M)
+{/*<asyxml></code><documentation>Return the point 'M' in the coordinate system 'coordsys'.
+ In other words, the returned point marks the same plot as 'M' does.</documentation></function></asyxml>*/
+ point op;
+ coordsys mco = M.coordsys;
+ op.init(R, R.defaulttorelative(mco.relativetodefault(M.coordinates)), M.m);
+ return op;
+}
+
+/*<asyxml><function type="pair" signature="pair coordinates(point)"><code></asyxml>*/
+pair coordinates(point M)
+{/*<asyxml></code><documentation>Return the coordinates of 'M' in its coordinate system.</documentation></function></asyxml>*/
+ return M.coordinates;
+}
+
+/*<asyxml><function type="bool" signature="bool samecoordsys(bool...point[])"><code></asyxml>*/
+bool samecoordsys(bool warn = true ... point[] M)
+{/*<asyxml></code><documentation>Return true iff all the points have the same coordinate system.
+ If 'warn' is true and the coordinate systems are different, a warning is sent.</documentation></function></asyxml>*/
+ bool ret = true;
+ coordsys t = M[0].coordsys;
+ for (int i = 1; i < M.length; ++i) {
+ ret = (t == M[i].coordsys);
+ if(!ret) break;
+ t = M[i].coordsys;
+ }
+ if(warn && !ret)
+ warning("coodinatesystem",
+ "the coordinate system of two objects are not the same.
+The operation will be done relative to the default coordinate system.");
+ return ret;
+}
+
+/*<asyxml><function type="point[]" signature="standardizecoordsys(coordsys,bool...point[])"><code></asyxml>*/
+point[] standardizecoordsys(coordsys R = currentcoordsys,
+ bool warn = true ... point[] M)
+{/*<asyxml></code><documentation>Return the points with the same coordinate system 'R'.
+ If 'warn' is true and the coordinate systems are different, a warning is sent.</documentation></function></asyxml>*/
+ point[] op = new point[];
+ op = M;
+ if(!samecoordsys(warn ... M))
+ for (int i = 1; i < M.length; ++i)
+ op[i] = changecoordsys(R, M[i]);
+ return op;
+}
+
+/*<asyxml><operator type = "pair" signature="cast(point)"><code></asyxml>*/
+pair operator cast(point P)
+{/*<asyxml></code><documentation>Cast point to pair.</documentation></operator></asyxml>*/
+ return P.coordsys.relativetodefault(P.coordinates);
+}
+
+/*<asyxml><operator type = "pair[]" signature="cast(point[])"><code></asyxml>*/
+pair[] operator cast(point[] P)
+{/*<asyxml></code><documentation>Cast point[] to pair[].</documentation></operator></asyxml>*/
+ pair[] op;
+ for (int i = 0; i < P.length; ++i) {
+ op.push((pair)P[i]);
+ }
+ return op;
+}
+
+/*<asyxml><operator type = "point" signature="cast(pair)"><code></asyxml>*/
+point operator cast(pair p)
+{/*<asyxml></code><documentation>Cast pair to point relatively to the current coordinate
+ system 'currentcoordsys'.</documentation></operator></asyxml>*/
+ return point(currentcoordsys, p);
+}
+
+/*<asyxml><operator type = "point[]" signature="cast(pair[])"><code></asyxml>*/
+point[] operator cast(pair[] p)
+{/*<asyxml></code><documentation>Cast pair[] to point[] relatively to the current coordinate
+ system 'currentcoordsys'.</documentation></operator></asyxml>*/
+ pair[] op;
+ for (int i = 0; i < p.length; ++i) {
+ op.push((point)p[i]);
+ }
+ return op;
+}
+
+/*<asyxml><function type="pair" signature="locate(point)"><code></asyxml>*/
+pair locate(point P)
+{/*<asyxml></code><documentation>Return the coordinates of 'P' in the default coordinate system.</documentation></function></asyxml>*/
+ return P.coordsys * P.coordinates;
+}
+
+/*<asyxml><function type="point" signature="locate(pair)"><code></asyxml>*/
+point locate(pair p)
+{/*<asyxml></code><documentation>Return the point in the current coordinate system 'currentcoordsys'.</documentation></function></asyxml>*/
+ return p; //automatic casting 'pair to point'.
+}
+
+/*<asyxml><operator type = "point" signature="*(real,explicit point)"><code></asyxml>*/
+point operator *(real x, explicit point P)
+{/*<asyxml></code><documentation>Multiply the coordinates (not the mass) of 'P' by 'x'.</documentation></operator></asyxml>*/
+ return point(P.coordsys, x * P.coordinates, P.m);
+}
+
+/*<asyxml><operator type = "point" signature="/(explicit point,real)"><code></asyxml>*/
+point operator /(explicit point P, real x)
+{/*<asyxml></code><documentation>Divide the coordinates (not the mass) of 'P' by 'x'.</documentation></operator></asyxml>*/
+ return point(P.coordsys, P.coordinates/x, P.m);
+}
+
+/*<asyxml><operator type = "point" signature="/(real,explicit point)"><code></asyxml>*/
+point operator /(real x, explicit point P)
+{/*<asyxml></code><documentation></documentation></operator></asyxml>*/
+ return point(P.coordsys, x/P.coordinates, P.m);
+}
+
+/*<asyxml><operator type = "point" signature="-(explicit point)"><code></asyxml>*/
+point operator -(explicit point P)
+{/*<asyxml></code><documentation>-P. The mass is inchanged.</documentation></operator></asyxml>*/
+ return point(P.coordsys, -P.coordinates, P.m);
+}
+
+/*<asyxml><operator type = "point" signature="+(explicit point,explicit point)"><code></asyxml>*/
+point operator +(explicit point P1, explicit point P2)
+{/*<asyxml></code><documentation>Provide 'point + point'.
+ If the two points haven't the same coordinate system, a warning is sent and the
+ returned point has the default coordinate system 'defaultcoordsys'.
+ The masses are added.</documentation></operator></asyxml>*/
+ point[] P = standardizecoordsys(P1, P2);
+ coordsys R = P[0].coordsys;
+ return point(R, P[0].coordinates + P[1].coordinates, P1.m + P2.m);
+}
+
+/*<asyxml><operator type = "point" signature="+(explicit point,explicit pair)"><code></asyxml>*/
+point operator +(explicit point P1, explicit pair p2)
+{/*<asyxml></code><documentation>Provide 'point + pair'.
+ The pair 'p2' is supposed to be coordinates relatively to the coordinates system of 'P1'.
+ The mass is not changed.</documentation></operator></asyxml>*/
+ coordsys R = currentcoordsys;
+ return point(R, P1.coordinates + point(R, p2).coordinates, P1.m);
+}
+point operator +(explicit pair p1, explicit point p2)
+{
+ return p2 + p1;
+}
+
+/*<asyxml><operator type = "point" signature="-(explicit point,explicit point)"><code></asyxml>*/
+point operator -(explicit point P1, explicit point P2)
+{/*<asyxml></code><documentation>Provide 'point - point'.</documentation></operator></asyxml>*/
+ return P1 + (-P2);
+}
+
+/*<asyxml><operator type = "point" signature="-(explicit point,explicit pair)"><code></asyxml>*/
+point operator -(explicit point P1, explicit pair p2)
+{/*<asyxml></code><documentation>Provide 'point - pair'.
+ The pair 'p2' is supposed to be coordinates relatively to the coordinates system of 'P1'.</documentation></operator></asyxml>*/
+ return P1 + (-p2);
+}
+point operator -(explicit pair p1, explicit point P2)
+{
+ return p1 + (-P2);
+}
+
+/*<asyxml><operator type = "point" signature="*(transform,explicit point)"><code></asyxml>*/
+point operator *(transform t, explicit point P)
+{/*<asyxml></code><documentation>Provide 'transform * point'.
+ Note that the transforms scale, xscale, yscale and rotate are carried out relatively
+ the default coordinate system 'defaultcoordsys' which is not desired for point
+ defined in an other coordinate system.
+ On can use scale(real, point), xscale(real, point), yscale(real, point), rotate(real, point),
+ scaleO(real), xscaleO(real), yscaleO(real) and rotateO(real) (described further)
+ to change the coordinate system of reference.</documentation></operator></asyxml>*/
+ coordsys R = P.coordsys;
+ return point(R, (t * locate(P))/R, P.m);
+}
+
+/*<asyxml><operator type = "point" signature="*(explicit point,explicit point)"><code></asyxml>*/
+point operator *(explicit point P1, explicit point P2)
+{/*<asyxml></code><documentation>Provide 'point * point'.
+ The resulted mass is the mass of P2</documentation></operator></asyxml>*/
+ point[] P = standardizecoordsys(P1, P2);
+ coordsys R = P[0].coordsys;
+ return point(R, P[0].coordinates * P[1].coordinates, P2.m);
+}
+
+/*<asyxml><operator type = "point" signature="*(explicit point,explicit pair)"><code></asyxml>*/
+point operator *(explicit point P1, explicit pair p2)
+{/*<asyxml></code><documentation>Provide 'point * pair'.
+ The pair 'p2' is supposed to be the coordinates of
+ the point in the coordinates system of 'P1'.
+ 'pair * point' is also defined.</documentation></operator></asyxml>*/
+ point P = point(P1.coordsys, p2, P1.m);
+ return P1 * P;
+}
+point operator *(explicit pair p1, explicit point p2)
+{
+ return p2 * p1;
+}
+
+/*<asyxml><operator type = "bool" signature="==(explicit point,explicit point)"><code></asyxml>*/
+bool operator ==(explicit point M, explicit point N)
+{/*<asyxml></code><documentation>Provide the test 'M == N' wish returns true iff MN < EPS</documentation></operator></asyxml>*/
+ return abs(locate(M) - locate(N)) < EPS;
+}
+
+/*<asyxml><operator type = "bool" signature="!=(explicit point,explicit point)"><code></asyxml>*/
+bool operator !=(explicit point M, explicit point N)
+{/*<asyxml></code><documentation>Provide the test 'M != N' wish return true iff MN >= EPS</documentation></operator></asyxml>*/
+ return !(M == N);
+}
+
+/*<asyxml><operator type = "guide" signature="cast(point)"><code></asyxml>*/
+guide operator cast(point p)
+{/*<asyxml></code><documentation>Cast point to guide.</documentation></operator></asyxml>*/
+ return locate(p);
+}
+
+/*<asyxml><operator type = "path" signature="cast(point)"><code></asyxml>*/
+path operator cast(point p)
+{/*<asyxml></code><documentation>Cast point to path.</documentation></operator></asyxml>*/
+ return locate(p);
+}
+
+/*<asyxml><function type="void" signature="dot(picture,Label,explicit point,align,string,pen)"><code></asyxml>*/
+void dot(picture pic = currentpicture, Label L, explicit point Z,
+ align align = NoAlign,
+ string format = defaultformat, pen p = currentpen)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ Label L = L.copy();
+ L.position(locate(Z));
+ if(L.s == "") {
+ if(format == "") format = defaultformat;
+ L.s = "("+format(format, Z.x)+", "+format(format, Z.y)+")";
+ }
+ L.align(align, E);
+ L.p(p);
+ dot(pic, locate(Z), p);
+ add(pic, L);
+}
+
+/*<asyxml><function type="real" signature="abs(coordsys,pair)"><code></asyxml>*/
+real abs(coordsys R, pair m)
+{/*<asyxml></code><documentation>Return the modulus |m| in the coordinate system 'R'.</documentation></function></asyxml>*/
+ return R.abs(m);
+}
+
+/*<asyxml><function type="real" signature="abs(explicit point)"><code></asyxml>*/
+real abs(explicit point M)
+{/*<asyxml></code><documentation>Return the modulus |M| in its coordinate system.</documentation></function></asyxml>*/
+ return M.coordsys.abs(M.coordinates);
+}
+
+/*<asyxml><function type="real" signature="length(explicit point)"><code></asyxml>*/
+real length(explicit point M)
+{/*<asyxml></code><documentation>Return the modulus |M| in its coordinate system (same as 'abs').</documentation></function></asyxml>*/
+ return M.coordsys.abs(M.coordinates);
+}
+
+/*<asyxml><function type="point" signature="conj(explicit point)"><code></asyxml>*/
+point conj(explicit point M)
+{/*<asyxml></code><documentation>Conjugate.</documentation></function></asyxml>*/
+ return point(M.coordsys, conj(M.coordinates), M.m);
+}
+
+/*<asyxml><function type="real" signature="degrees(explicit point,coordsys,bool)"><code></asyxml>*/
+real degrees(explicit point M, coordsys R = M.coordsys, bool warn = true)
+{/*<asyxml></code><documentation>Return the angle of M (in degrees) relatively to 'R'.</documentation></function></asyxml>*/
+ return (degrees(locate(M) - R.O, warn) - degrees(R.i))%360;
+}
+
+/*<asyxml><function type="real" signature="angle(explicit point,coordsys,bool)"><code></asyxml>*/
+real angle(explicit point M, coordsys R = M.coordsys, bool warn = true)
+{/*<asyxml></code><documentation>Return the angle of M (in radians) relatively to 'R'.</documentation></function></asyxml>*/
+ return radians(degrees(M, R, warn));
+}
+
+/*<asyxml><function type="bool" signature="finite(explicit point)"><code></asyxml>*/
+bool finite(explicit point p)
+{/*<asyxml></code><documentation>Avoid to compute 'finite((pair)(infinite_point))'.</documentation></function></asyxml>*/
+ return finite(p.coordinates);
+}
+
+/*<asyxml><function type="real" signature="dot(point,point)"><code></asyxml>*/
+real dot(point A, point B)
+{/*<asyxml></code><documentation>Return the dot product in the coordinate system of 'A'.</documentation></function></asyxml>*/
+ point[] P = standardizecoordsys(A.coordsys, A, B);
+ return P[0].coordsys.dot(P[0].coordinates, P[1].coordinates);
+}
+
+/*<asyxml><function type="real" signature="dot(point,explicit pair)"><code></asyxml>*/
+real dot(point A, explicit pair B)
+{/*<asyxml></code><documentation>Return the dot product in the default coordinate system.
+ dot(explicit pair, point) is also defined.</documentation></function></asyxml>*/
+ return dot(locate(A), B);
+}
+real dot(explicit pair A, point B)
+{
+ return dot(A, locate(B));
+}
+
+/*<asyxml><function type="transforms" signature="rotateO(real)"><code></asyxml>*/
+transform rotateO(real a)
+{/*<asyxml></code><documentation>Rotation around the origin of the current coordinate system.</documentation></function></asyxml>*/
+ return rotate(a, currentcoordsys.O);
+};
+
+/*<asyxml><function type="transform" signature="projection(point,point)"><code></asyxml>*/
+transform projection(point A, point B)
+{/*<asyxml></code><documentation>Return the orthogonal projection on the line (AB).</documentation></function></asyxml>*/
+ pair dir = unit(locate(A) - locate(B));
+ pair a = locate(A);
+ real cof = dir.x * a.x + dir.y * a.y;
+ real tx = a.x - dir.x * cof;
+ real txx = dir.x^2;
+ real txy = dir.x * dir.y;
+ real ty = a.y - dir.y * cof;
+ real tyx = txy;
+ real tyy = dir.y^2;
+ transform t = (tx, ty, txx, txy, tyx, tyy);
+ return t;
+}
+
+/*<asyxml><function type="transform" signature="projection(point,point,point,point,bool)"><code></asyxml>*/
+transform projection(point A, point B, point C, point D, bool safe = false)
+{/*<asyxml></code><documentation>Return the (CD) parallel projection on (AB).
+ If 'safe = true' and (AB)//(CD) return the identity.
+ If 'safe = false' and (AB)//(CD) return an infinity scaling.</documentation></function></asyxml>*/
+ pair a = locate(A);
+ pair u = unit(locate(B) - locate(A));
+ pair v = unit(locate(D) - locate(C));
+ real c = u.x * a.y - u.y * a.x;
+ real d = (conj(u) * v).y;
+ if (abs(d) < epsgeo) {
+ return safe ? identity() : scale(infinity);
+ }
+ real tx = c * v.x/d;
+ real ty = c * v.y/d;
+ real txx = u.x * v.y/d;
+ real txy = -u.x * v.x/d;
+ real tyx = u.y * v.y/d;
+ real tyy = -u.y * v.x/d;
+ transform t = (tx, ty, txx, txy, tyx, tyy);
+ return t;
+}
+
+/*<asyxml><function type="transform" signature="scale(real,point)"><code></asyxml>*/
+transform scale(real k, point M)
+{/*<asyxml></code><documentation>Homothety.</documentation></function></asyxml>*/
+ pair P = locate(M);
+ return shift(P) * scale(k) * shift(-P);
+}
+
+/*<asyxml><function type="transform" signature="xscale(real,point)"><code></asyxml>*/
+transform xscale(real k, point M)
+{/*<asyxml></code><documentation>xscale from 'M' relatively to the x - axis of the coordinate system of 'M'.</documentation></function></asyxml>*/
+ pair P = locate(M);
+ real a = degrees(M.coordsys.i);
+ return (shift(P) * rotate(a)) * xscale(k) * (rotate(-a) * shift(-P));
+}
+
+/*<asyxml><function type="transform" signature="yscale(real,point)"><code></asyxml>*/
+transform yscale(real k, point M)
+{/*<asyxml></code><documentation>yscale from 'M' relatively to the y - axis of the coordinate system of 'M'.</documentation></function></asyxml>*/
+ pair P = locate(M);
+ real a = degrees(M.coordsys.j) - 90;
+ return (shift(P) * rotate(a)) * yscale(k) * (rotate(-a) * shift(-P));
+}
+
+/*<asyxml><function type="transform" signature="scale(real,point,point,point,point,bool)"><code></asyxml>*/
+transform scale(real k, point A, point B, point C, point D, bool safe = false)
+{/*<asyxml></code><documentation><url href = "http://fr.wikipedia.org/wiki/Affinit%C3%A9_%28math%C3%A9matiques%29"/>
+ (help me for English translation...)
+ If 'safe = true' and (AB)//(CD) return the identity.
+ If 'safe = false' and (AB)//(CD) return a infinity scaling.</documentation></function></asyxml>*/
+ pair a = locate(A);
+ pair u = unit(locate(B) - locate(A));
+ pair v = unit(locate(D) - locate(C));
+ real c = u.x * a.y - u.y * a.x;
+ real d = (conj(u) * v).y;
+ real d = (conj(u) * v).y;
+ if (abs(d) < epsgeo) {
+ return safe ? identity() : scale(infinity);
+ }
+ real tx = (1 - k) * c * v.x/d;
+ real ty = (1 - k) * c * v.y/d;
+ real txx = (1 - k) * u.x * v.y/d + k;
+ real txy = (k - 1) * u.x * v.x/d;
+ real tyx = (1 - k) * u.y * v.y/d;
+ real tyy = (k - 1) * u.y * v.x/d + k;
+ transform t = (tx, ty, txx, txy, tyx, tyy);
+ return t;
+}
+
+/*<asyxml><function type="transform" signature="scaleO(real)"><code></asyxml>*/
+transform scaleO(real x)
+{/*<asyxml></code><documentation>Homothety from the origin of the current coordinate system.</documentation></function></asyxml>*/
+ return scale(x, (0, 0));
+}
+
+/*<asyxml><function type="transform" signature="xscaleO(real)"><code></asyxml>*/
+transform xscaleO(real x)
+{/*<asyxml></code><documentation>xscale from the origin and relatively to the current coordinate system.</documentation></function></asyxml>*/
+ return scale(x, (0, 0), (0, 1), (0, 0), (1, 0));
+}
+
+/*<asyxml><function type="transform" signature="yscaleO(real)"><code></asyxml>*/
+transform yscaleO(real x)
+{/*<asyxml></code><documentation>yscale from the origin and relatively to the current coordinate system.</documentation></function></asyxml>*/
+ return scale(x, (0, 0), (1, 0), (0, 0), (0, 1));
+}
+
+/*<asyxml><struct signature="vector"><code></asyxml>*/
+struct vector
+{/*<asyxml></code><documentation>Like a point but casting to pair, adding etc does not take account
+ of the origin of the coordinate system.</documentation><property type = "point" signature="v"><code></asyxml>*/
+ point v;/*<asyxml></code><documentation>Coordinates as a point (embed coordinate system and pair).</documentation></property></asyxml>*/
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><operator type = "point" signature="cast(vector)"><code></asyxml>*/
+point operator cast(vector v)
+{/*<asyxml></code><documentation>Cast vector 'v' to point 'M' so that OM = v.</documentation></operator></asyxml>*/
+ return v.v;
+}
+
+/*<asyxml><operator type = "vector" signature="cast(pair)"><code></asyxml>*/
+vector operator cast(pair v)
+{/*<asyxml></code><documentation>Cast pair to vector relatively to the current coordinate
+ system 'currentcoordsys'.</documentation></operator></asyxml>*/
+ vector ov;
+ ov.v = point(currentcoordsys, v);
+ return ov;
+}
+
+/*<asyxml><operator type = "vector" signature="cast(explicit point)"><code></asyxml>*/
+vector operator cast(explicit point v)
+{/*<asyxml></code><documentation>A point can be interpreted like a vector using the code
+ '(vector)a_point'.</documentation></operator></asyxml>*/
+ vector ov;
+ ov.v = v;
+ return ov;
+}
+
+/*<asyxml><operator type = "pair" signature="cast(explicit vector)"><code></asyxml>*/
+pair operator cast(explicit vector v)
+{/*<asyxml></code><documentation>Cast vector to pair (the coordinates of 'v' in the default coordinate system).</documentation></operator></asyxml>*/
+ return locate(v.v) - v.v.coordsys.O;
+}
+
+/*<asyxml><operator type = "align" signature="cast(vector)"><code></asyxml>*/
+align operator cast(vector v)
+{/*<asyxml></code><documentation>Cast vector to align.</documentation></operator></asyxml>*/
+ return (pair)v;
+}
+
+/*<asyxml><function type="vector" signature="vector(coordsys, pair)"><code></asyxml>*/
+vector vector(coordsys R = currentcoordsys, pair v)
+{/*<asyxml></code><documentation>Return the vector of 'R' which has the coordinates 'v'.</documentation></function></asyxml>*/
+ vector ov;
+ ov.v = point(R, v);
+ return ov;
+}
+
+/*<asyxml><function type="vector" signature="vector(point)"><code></asyxml>*/
+vector vector(point M)
+{/*<asyxml></code><documentation>Return the vector OM, where O is the origin of the coordinate system of 'M'.
+ Useful to write 'vector(P - M);' instead of '(vector)(P - M)'.</documentation></function></asyxml>*/
+ return M;
+}
+
+/*<asyxml><function type="point" signature="point(explicit vector)"><code></asyxml>*/
+point point(explicit vector u)
+{/*<asyxml></code><documentation>Return the point M so that OM = u, where O is the origin of the coordinate system of 'u'.</documentation></function></asyxml>*/
+ return u.v;
+}
+
+/*<asyxml><function type="pair" signature="locate(explicit vector)"><code></asyxml>*/
+pair locate(explicit vector v)
+{/*<asyxml></code><documentation>Return the coordinates of 'v' in the default coordinate system (like casting vector to pair).</documentation></function></asyxml>*/
+ return (pair)v;
+}
+
+/*<asyxml><function type="void" signature="show(Label,pen,arrowbar)"><code></asyxml>*/
+void show(Label L, vector v, pen p = currentpen, arrowbar arrow = Arrow)
+{/*<asyxml></code><documentation>Draw the vector v (from the origin of its coordinate system).</documentation></function></asyxml>*/
+ coordsys R = v.v.coordsys;
+ draw(L, R.O--v.v, p, arrow);
+}
+
+/*<asyxml><function type="vector" signature="changecoordsys(coordsys,vector)"><code></asyxml>*/
+vector changecoordsys(coordsys R, vector v)
+{/*<asyxml></code><documentation>Return the vector 'v' relatively to coordinate system 'R'.</documentation></function></asyxml>*/
+ vector ov;
+ ov.v = point(R, (locate(v) + R.O)/R);
+ return ov;
+}
+
+/*<asyxml><operator type = "vector" signature="*(real,explicit vector)"><code></asyxml>*/
+vector operator *(real x, explicit vector v)
+{/*<asyxml></code><documentation>Provide real * vector.</documentation></operator></asyxml>*/
+ return x * v.v;
+}
+
+/*<asyxml><operator type = "vector" signature="/(explicit vector,real)"><code></asyxml>*/
+vector operator /(explicit vector v, real x)
+{/*<asyxml></code><documentation>Provide vector/real</documentation></operator></asyxml>*/
+ return v.v/x;
+}
+
+/*<asyxml><operator type = "vector" signature="*(transform t,explicit vector)"><code></asyxml>*/
+vector operator *(transform t, explicit vector v)
+{/*<asyxml></code><documentation>Provide transform * vector.</documentation></operator></asyxml>*/
+ return t * v.v;
+}
+
+/*<asyxml><operator type = "vector" signature="*(explicit point,explicit vector)"><code></asyxml>*/
+vector operator *(explicit point M, explicit vector v)
+{/*<asyxml></code><documentation>Provide point * vector</documentation></operator></asyxml>*/
+ return M * v.v;
+}
+
+/*<asyxml><operator type = "point" signature="+(explicit point,explicit vector)"><code></asyxml>*/
+point operator +(point M, explicit vector v)
+{/*<asyxml></code><documentation>Return 'M' shifted by 'v'.</documentation></operator></asyxml>*/
+ return shift(locate(v)) * M;
+}
+
+/*<asyxml><operator type = "point" signature="-(explicit point,explicit vector)"><code></asyxml>*/
+point operator -(point M, explicit vector v)
+{/*<asyxml></code><documentation>Return 'M' shifted by '-v'.</documentation></operator></asyxml>*/
+ return shift(-locate(v)) * M;
+}
+
+/*<asyxml><operator type = "vector" signature="-(explicit vector)"><code></asyxml>*/
+vector operator -(explicit vector v)
+{/*<asyxml></code><documentation>Provide -v.</documentation></operator></asyxml>*/
+ return -v.v;
+}
+
+/*<asyxml><operator type = "point" signature="+(explicit pair,explicit vector)"><code></asyxml>*/
+point operator +(explicit pair m, explicit vector v)
+{/*<asyxml></code><documentation>The pair 'm' is supposed to be the coordinates of
+ a point in the current coordinates system 'currentcoordsys'.
+ Return this point shifted by the vector 'v'.</documentation></operator></asyxml>*/
+ return locate(m) + v;
+}
+
+/*<asyxml><operator type = "point" signature="-(explicit pair,explicit vector)"><code></asyxml>*/
+point operator -(explicit pair m, explicit vector v)
+{/*<asyxml></code><documentation>The pair 'm' is supposed to be the coordinates of
+ a point in the current coordinates system 'currentcoordsys'.
+ Return this point shifted by the vector '-v'.</documentation></operator></asyxml>*/
+ return m + (-v);
+}
+
+/*<asyxml><operator type = "vector" signature="+(explicit vector,explicit vector)"><code></asyxml>*/
+vector operator +(explicit vector v1, explicit vector v2)
+{/*<asyxml></code><documentation>Provide vector + vector.
+ If the two vector haven't the same coordinate system, the returned
+ vector is relative to the default coordinate system (without warning).</documentation></operator></asyxml>*/
+ coordsys R = v1.v.coordsys;
+ if(samecoordsys(false, v1, v2)){R = defaultcoordsys;}
+ return vector(R, (locate(v1) + locate(v2))/R);
+}
+
+/*<asyxml><operator type = "vector" signature="-(explicit vector, explicit vector)"><code></asyxml>*/
+vector operator -(explicit vector v1, explicit vector v2)
+{/*<asyxml></code><documentation>Provide vector - vector.
+ If the two vector haven't the same coordinate system, the returned
+ vector is relative to the default coordinate system (without warning).</documentation></operator></asyxml>*/
+ return v1 + (-v2);
+}
+
+/*<asyxml><operator type = "bool" signature="==(explicit vector,explicit vector)"><code></asyxml>*/
+bool operator ==(explicit vector u, explicit vector v)
+{/*<asyxml></code><documentation>Return true iff |u - v|<EPS.</documentation></operator></asyxml>*/
+ return abs(u - v) < EPS;
+}
+
+/*<asyxml><function type="bool" signature="collinear(vector,vector)"><code></asyxml>*/
+bool collinear(vector u, vector v)
+{/*<asyxml></code><documentation>Return 'true' iff the vectors 'u' and 'v' are collinear.</documentation></function></asyxml>*/
+ return abs(ypart((conj((pair)u) * (pair)v))) < EPS;
+}
+
+/*<asyxml><function type="vector" signature="unit(point)"><code></asyxml>*/
+vector unit(point M)
+{/*<asyxml></code><documentation>Return the unit vector according to the modulus of its coordinate system.</documentation></function></asyxml>*/
+ return M/abs(M);
+}
+
+/*<asyxml><function type="vector" signature="unit(vector)"><code></asyxml>*/
+vector unit(vector u)
+{/*<asyxml></code><documentation>Return the unit vector according to the modulus of its coordinate system.</documentation></function></asyxml>*/
+ return u.v/abs(u.v);
+}
+
+/*<asyxml><function type="real" signature="degrees(vector,coordsys,bool)"><code></asyxml>*/
+real degrees(vector v,
+ coordsys R = v.v.coordsys,
+ bool warn = true)
+{/*<asyxml></code><documentation>Return the angle of 'v' (in degrees) relatively to 'R'.</documentation></function></asyxml>*/
+ return (degrees(locate(v), warn) - degrees(R.i))%360;
+}
+
+/*<asyxml><function type="real" signature="angle(vector,coordsys,bool)"><code></asyxml>*/
+real angle(explicit vector v,
+ coordsys R = v.v.coordsys,
+ bool warn = true)
+{/*<asyxml></code><documentation>Return the angle of 'v' (in radians) relatively to 'R'.</documentation></function></asyxml>*/
+ return radians(degrees(v, R, warn));
+}
+
+/*<asyxml><function type="vector" signature="conj(explicit vector)"><code></asyxml>*/
+vector conj(explicit vector u)
+{/*<asyxml></code><documentation>Conjugate.</documentation></function></asyxml>*/
+ return conj(u.v);
+}
+
+/*<asyxml><function type="transform" signature="rotate(explicit vector)"><code></asyxml>*/
+transform rotate(explicit vector dir)
+{/*<asyxml></code><documentation>A rotation in the direction 'dir' limited to [-90, 90]
+ This is useful for rotating text along a line in the direction dir.
+ rotate(explicit point dir) is also defined.
+ </documentation></function></asyxml>*/
+ return rotate(locate(dir));
+}
+transform rotate(explicit point dir){return rotate(locate(vector(dir)));}
+// *......................COORDINATES......................*
+// *=======================================================*
+
+// *=======================================================*
+// *.........................BASES.........................*
+/*<asyxml><variable type="point" signature="origin"><code></asyxml>*/
+point origin = point(defaultcoordsys, (0, 0));/*<asyxml></code><documentation>The origin of the current coordinate system.</documentation></variable></asyxml>*/
+
+/*<asyxml><function type="point" signature="origin(coordsys)"><code></asyxml>*/
+point origin(coordsys R = currentcoordsys)
+{/*<asyxml></code><documentation>Return the origin of the coordinate system 'R'.</documentation></function></asyxml>*/
+ return point(R, (0, 0)); //use automatic casting;
+}
+
+/*<asyxml><variable type="real" signature="linemargin"><code></asyxml>*/
+real linemargin = 0;/*<asyxml></code><documentation>Margin used to draw lines.</documentation></variable></asyxml>*/
+/*<asyxml><function type="real" signature="linemargin()"><code></asyxml>*/
+real linemargin()
+{/*<asyxml></code><documentation>Return the margin used to draw lines.</documentation></function></asyxml>*/
+ return linemargin;
+}
+
+/*<asyxml><variable type="pen" signature="addpenline"><code></asyxml>*/
+pen addpenline = squarecap;/*<asyxml></code><documentation>Add this property to the drawing pen of "finish" lines.</documentation></variable></asyxml>*/
+pen addpenline(pen p) {
+ return addpenline + p;
+}
+
+/*<asyxml><variable type="pen" signature="addpenarc"><code></asyxml>*/
+pen addpenarc = squarecap;/*<asyxml></code><documentation>Add this property to the drawing pen of arcs.</documentation></variable></asyxml>*/
+pen addpenarc(pen p) {return addpenarc + p;}
+
+/*<asyxml><variable type="string" signature="defaultmassformat"><code></asyxml>*/
+string defaultmassformat = "$\left(%L;%.4g\right)$";/*<asyxml></code><documentation>Format used to construct the default label of masses.</documentation></variable></asyxml>*/
+
+/*<asyxml><function type="int" signature="sgnd(real)"><code></asyxml>*/
+int sgnd(real x)
+{/*<asyxml></code><documentation>Return the -1 if x < 0, 1 if x >= 0.</documentation></function></asyxml>*/
+ return (x == 0) ? 1 : sgn(x);
+}
+int sgnd(int x)
+{
+ return (x == 0) ? 1 : sgn(x);
+}
+
+/*<asyxml><function type="bool" signature="defined(pair)"><code></asyxml>*/
+bool defined(point P)
+{/*<asyxml></code><documentation>Return true iff the coordinates of 'P' are finite.</documentation></function></asyxml>*/
+ return finite(P.coordinates);
+}
+
+/*<asyxml><function type="bool" signature="onpath(picture,path,point,pen)"><code></asyxml>*/
+bool onpath(picture pic = currentpicture, path g, point M, pen p = currentpen)
+{/*<asyxml></code><documentation>Return true iff 'M' is on the path drawn with the pen 'p' in 'pic'.</documentation></function></asyxml>*/
+ transform t = inverse(pic.calculateTransform());
+ return intersect(g, shift(locate(M)) * scale(linewidth(p)/2) * t * unitcircle).length > 0;
+}
+
+/*<asyxml><function type="bool" signature="sameside(point,point,point)"><code></asyxml>*/
+bool sameside(point M, point N, point O)
+{/*<asyxml></code><documentation>Return 'true' iff 'M' and 'N' are same side of the point 'O'.</documentation></function></asyxml>*/
+ pair m = M, n = N, o = O;
+ return dot(m - o, n - o) >= -epsgeo;
+}
+
+/*<asyxml><function type="bool" signature="between(point,point,point)"><code></asyxml>*/
+bool between(point M, point O, point N)
+{/*<asyxml></code><documentation>Return 'true' iff 'O' is between 'M' and 'N'.</documentation></function></asyxml>*/
+ return (!sameside(N, M, O) || M == O || N == O);
+}
+
+
+typedef path pathModifier(path);
+pathModifier NoModifier = new path(path g){return g;};
+
+private void Drawline(picture pic = currentpicture, Label L = "", pair P, bool dirP = true, pair Q, bool dirQ = true,
+ align align = NoAlign, pen p = currentpen,
+ arrowbar arrow = None,
+ Label legend = "", marker marker = nomarker,
+ pathModifier pathModifier = NoModifier)
+{/* Add the two parameters 'dirP' and 'dirQ' to the native routine
+ 'drawline' of the module 'math'.
+ Segment [PQ] will be prolonged in direction of P if 'dirP = true', in
+ direction of Q if 'dirQ = true'.
+ If 'dirP = dirQ = true', the behavior is that of the native 'drawline'.
+ Add all the other parameters of 'Draw'.*/
+ pic.add(new void (frame f, transform t, transform T, pair m, pair M) {
+ picture opic;
+ // Reduce the bounds by the size of the pen.
+ m -= min(p) - (linemargin(), linemargin()); M -= max(p) + (linemargin(), linemargin());
+
+ // Calculate the points and direction vector in the transformed space.
+ t = t * T;
+ pair z = t * P;
+ pair q = t * Q;
+ pair v = q - z;
+ // path g;
+ pair ptp, ptq;
+ real cp = dirP ? 1:0;
+ real cq = dirQ ? 1:0;
+ // Handle horizontal and vertical lines.
+ if(v.x == 0) {
+ if(m.x <= z.x && z.x <= M.x)
+ if (dot(v, m - z) < 0) {
+ ptp = (z.x, z.y + cp * (m.y - z.y));
+ ptq = (z.x, q.y + cq * (M.y - q.y));
+ } else {
+ ptq = (z.x, q.y + cq * (m.y - q.y));
+ ptp = (z.x, z.y + cp * (M.y - z.y));
+ }
+ } else if(v.y == 0) {
+ if (dot(v, m - z) < 0) {
+ ptp = (z.x + cp * (m.x - z.x), z.y);
+ ptq = (q.x + cq * (M.x - q.x), z.y);
+ } else {
+ ptq = (q.x + cq * (m.x - q.x), z.y);
+ ptp = (z.x + cp * (M.x - z.x), z.y);
+ }
+ } else {
+ // Calculate the maximum and minimum t values allowed for the
+ // parametric equation z + t * v
+ real mx = (m.x - z.x)/v.x, Mx = (M.x - z.x)/v.x;
+ real my = (m.y - z.y)/v.y, My = (M.y - z.y)/v.y;
+ real tmin = max(v.x > 0 ? mx : Mx, v.y > 0 ? my : My);
+ real tmax = min(v.x > 0 ? Mx : mx, v.y > 0 ? My : my);
+ pair pmin = z + tmin * v;
+ pair pmax = z + tmax * v;
+ if(tmin <= tmax) {
+ ptp = z + cp * tmin * v;
+ ptq = z + (cq == 0 ? v:tmax * v);
+ }
+ }
+ path g = ptp--ptq;
+ if (length(g)>0)
+ {
+ if(L.s != "") {
+ Label lL = L.copy();
+ if(L.defaultposition) lL.position(Relative(.9));
+ lL.p(p);
+ lL.out(opic, g);
+ }
+ g = pathModifier(g);
+ if(linetype(p).length == 0){
+ pair m = midpoint(g);
+ pen tp;
+ tp = dirP ? p : addpenline(p);
+ draw(opic, pathModifier(m--ptp), tp);
+ tp = dirQ ? p : addpenline(p);
+ draw(opic, pathModifier(m--ptq), tp);
+ } else {
+ draw(opic, g, p);
+ }
+ marker.markroutine(opic, marker.f, g);
+ arrow(opic, g, p, NoMargin);
+ add(f, opic.fit());
+ }
+ });
+}
+
+/*<asyxml><function type="void" signature="clipdraw(picture,Label,path,align,pen,arrowbar,arrowbar,real,real,Label,marker)"><code></asyxml>*/
+void clipdraw(picture pic = currentpicture, Label L = "", path g,
+ align align = NoAlign, pen p = currentpen,
+ arrowbar arrow = None, arrowbar bar = None,
+ real xmargin = 0, real ymargin = xmargin,
+ Label legend = "", marker marker = nomarker)
+{/*<asyxml></code><documentation>Draw the path 'g' on 'pic' clipped to the bounding box of 'pic'.</documentation></function></asyxml>*/
+ if(L.s != "") {
+ picture tmp;
+ label(tmp, L, g, p);
+ add(pic, tmp);
+ }
+ pic.add(new void (frame f, transform t, transform T, pair m, pair M) {
+ // Reduce the bounds by the size of the pen and the margins.
+ m += min(p) + (xmargin, ymargin); M -= max(p) + (xmargin, ymargin);
+ path bound = box(m, M);
+ picture tmp;
+ draw(tmp, "", t * T * g, align, p, arrow, bar, NoMargin, legend, marker);
+ clip(tmp, bound);
+ add(f, tmp.fit());
+ });
+}
+
+/*<asyxml><function type="void" signature="distance(picture pic,Label,point,point,bool,real,pen,pen,arrow)"><code></asyxml>*/
+void distance(picture pic = currentpicture, Label L = "", point A, point B,
+ bool rotated = true, real offset = 3mm,
+ pen p = currentpen, pen joinpen = invisible,
+ arrowbar arrow = Arrows(NoFill))
+{/*<asyxml></code><documentation>Draw arrow between A and B (from FAQ).</documentation></function></asyxml>*/
+ pair A = A, B = B;
+ path g = A--B;
+ transform Tp = shift(-offset * unit(B - A) * I);
+ pic.add(new void(frame f, transform t) {
+ picture opic;
+ path G = Tp * t * g;
+ transform id = identity();
+ transform T = rotated ? rotate(B - A) : id;
+ Label L = L.copy();
+ L.align(L.align, Center);
+ if(abs(ypart((conj(A - B) * L.align.dir))) < epsgeo && L.filltype == NoFill)
+ L.filltype = UnFill(1);
+ draw(opic, T * L, G, p, arrow, Bars, PenMargins);
+ pair Ap = t * A, Bp = t * B;
+ draw(opic, (Ap--Tp * Ap)^^(Bp--Tp * Bp), joinpen);
+ add(f, opic.fit());
+ }, true);
+ pic.addBox(min(g), max(g), Tp * min(p), Tp * max(p));
+}
+
+/*<asyxml><variable type="real" signature="perpfactor"><code></asyxml>*/
+real perpfactor = 1;/*<asyxml></code><documentation>Factor for drawing perpendicular symbol.</documentation></variable></asyxml>*/
+/*<asyxml><function type="void" signature="perpendicularmark(picture,point,explicit pair,explicit pair,real,pen,margin,filltype)"><code></asyxml>*/
+void perpendicularmark(picture pic = currentpicture, point z,
+ explicit pair align,
+ explicit pair dir = E, real size = 0,
+ pen p = currentpen,
+ margin margin = NoMargin,
+ filltype filltype = NoFill)
+{/*<asyxml></code><documentation>Draw a perpendicular symbol at z aligned in the direction align
+ relative to the path z--z + dir.
+ dir(45 + n * 90), where n in N*, are common values for 'align'.</documentation></function></asyxml>*/
+ p = squarecap + miterjoin + p;
+ if(size == 0) size = perpfactor * 3mm + linewidth(p) / 2;
+ frame apic;
+ pair d1 = size * align * unit(dir) * dir(-45);
+ pair d2 = I * d1;
+ path g = d1--d1 + d2--d2;
+ g = margin(g, p).g;
+ draw(apic, g, p);
+ if(filltype != NoFill) filltype.fill(apic, (relpoint(g, 0) - relpoint(g, 0.5)+
+ relpoint(g, 1))--g--cycle, p + solid);
+ add(pic, apic, locate(z));
+}
+
+/*<asyxml><function type="void" signature="perpendicularmark(picture,point,vector,vector,real,pen,margin,filltype)"><code></asyxml>*/
+void perpendicularmark(picture pic = currentpicture, point z,
+ vector align,
+ vector dir = E, real size = 0,
+ pen p = currentpen,
+ margin margin = NoMargin,
+ filltype filltype = NoFill)
+{/*<asyxml></code><documentation>Draw a perpendicular symbol at z aligned in the direction align
+ relative to the path z--z + dir.
+ dir(45 + n * 90), where n in N, are common values for 'align'.</documentation></function></asyxml>*/
+ perpendicularmark(pic, z, (pair)align, (pair)dir, size,
+ p, margin, filltype);
+}
+
+/*<asyxml><function type="void" signature="perpendicularmark(picture,point,explicit pair,path,real,pen,margin,filltype)"><code></asyxml>*/
+void perpendicularmark(picture pic = currentpicture, point z, explicit pair align, path g,
+ real size = 0, pen p = currentpen,
+ margin margin = NoMargin,
+ filltype filltype = NoFill)
+{/*<asyxml></code><documentation>Draw a perpendicular symbol at z aligned in the direction align
+ relative to the path z--z + dir(g, 0).
+ dir(45 + n * 90), where n in N, are common values for 'align'.</documentation></function></asyxml>*/
+ perpendicularmark(pic, z, align, dir(g, 0), size, p, margin, filltype);
+}
+
+/*<asyxml><function type="void" signature="perpendicularmark(picture,point,vector,path,real,pen,margin,filltype)"><code></asyxml>*/
+void perpendicularmark(picture pic = currentpicture, point z, vector align, path g,
+ real size = 0, pen p = currentpen,
+ margin margin = NoMargin,
+ filltype filltype = NoFill)
+{/*<asyxml></code><documentation>Draw a perpendicular symbol at z aligned in the direction align
+ relative to the path z--z + dir(g, 0).
+ dir(45 + n * 90), where n in N, are common values for 'align'.</documentation></function></asyxml>*/
+ perpendicularmark(pic, z, (pair)align, dir(g, 0), size, p, margin, filltype);
+}
+
+/*<asyxml><function type="void" signature="markrightangle(picture,point,point,point,real,pen,margin,filltype)"><code></asyxml>*/
+void markrightangle(picture pic = currentpicture, point A, point O,
+ point B, real size = 0, pen p = currentpen,
+ margin margin = NoMargin,
+ filltype filltype = NoFill)
+{/*<asyxml></code><documentation>Mark the angle AOB with a perpendicular symbol.</documentation></function></asyxml>*/
+ pair Ap = A, Bp = B, Op = O;
+ pair dir = Ap - Op;
+ real a1 = degrees(dir);
+ pair align = rotate(-a1) * unit(dir(Op--Ap, Op--Bp));
+ perpendicularmark(pic = pic, z = O, align = align,
+ dir = dir, size = size, p = p,
+ margin = margin, filltype = filltype);
+}
+
+/*<asyxml><function type="bool" signature="simeq(point,point,real)"><code></asyxml>*/
+bool simeq(point A, point B, real fuzz = epsgeo)
+{/*<asyxml></code><documentation>Return true iff abs(A - B) < fuzz.
+ This routine is used internally to know if two points are equal, in particular by the operator == in 'point == point'.</documentation></function></asyxml>*/
+ return (abs(A - B) < fuzz);
+}
+bool simeq(point a, real b, real fuzz = epsgeo)
+{
+ coordsys R = a.coordsys;
+ return (abs(a - point(R, ((pair)b)/R)) < fuzz);
+}
+
+/*<asyxml><function type="pair" signature="attract(pair,path,real)"><code></asyxml>*/
+pair attract(pair m, path g, real fuzz = 0)
+{/*<asyxml></code><documentation>Return the nearest point (A PAIR) of 'm' which is on the path g.
+ 'fuzz' is the argument 'fuzz' of 'intersect'.</documentation></function></asyxml>*/
+ if(intersect(m, g, fuzz).length > 0) return m;
+ pair p;
+ real step = 1, r = 0;
+ real[] t;
+ static real eps = sqrt(realEpsilon);
+ do {// Find a radius for intersection
+ r += step;
+ t = intersect(shift(m) * scale(r) * unitcircle, g);
+ } while(t.length <= 0);
+ p = point(g, t[1]);
+ real rm = 0, rM = r;
+ while(rM - rm > eps) {
+ r = (rm + rM)/2;
+ t = intersect(shift(m) * scale(r) * unitcircle, g, fuzz);
+ if(t.length <= 0) {
+ rm = r;
+ } else {
+ rM = r;
+ p = point(g, t[1]);
+ }
+ }
+ return p;
+}
+
+/*<asyxml><function type="point" signature="attract(point,path,real)"><code></asyxml>*/
+point attract(point M, path g, real fuzz = 0)
+{/*<asyxml></code><documentation>Return the nearest point (A POINT) of 'M' which is on the path g.
+ 'fuzz' is the argument 'fuzz' of 'intersect'.</documentation></function></asyxml>*/
+ return point(M.coordsys, attract(locate(M), g)/M.coordsys);
+}
+
+/*<asyxml><function type="real[]" signature="intersect(path,explicit pair)"><code></asyxml>*/
+real[] intersect(path g, explicit pair p, real fuzz = 0)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ fuzz = fuzz <= 0 ? sqrt(realEpsilon) : fuzz;
+ real[] or;
+ real r = realEpsilon;
+ do{
+ or = intersect(g, shift(p) * scale(r) * unitcircle, fuzz);
+ r *= 2;
+ } while(or.length == 0);
+ return or;
+}
+
+/*<asyxml><function type="real[]" signature="intersect(path,explicit point)"><code></asyxml>*/
+real[] intersect(path g, explicit point P, real fuzz = epsgeo)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersect(g, locate(P), fuzz);
+}
+// *.........................BASES.........................*
+// *=======================================================*
+
+// *=======================================================*
+// *.........................LINES.........................*
+/*<asyxml><struct signature="line"><code></asyxml>*/
+struct line
+{/*<asyxml></code><documentation>This structure provides the objects line, semi - line and segment oriented from A to B.
+ All the calculus with this structure will be as exact as Asymptote can do.
+ For a full precision, you must not cast 'line' to 'path' excepted for drawing routines.</documentation></asyxml>*/
+ /*<asyxml><property type = "point" signature="A,B"><code></asyxml>*/
+ restricted point A,B;/*<asyxml></code><documentation>Two line's points with same coordinate system.</documentation></property><property type = "bool" signature="extendA,extendB"><code></asyxml>*/
+ bool extendA,extendB;/*<asyxml></code><documentation>If true,extend 'l' in direction of A (resp. B).</documentation></property><property type = "vector" signature="u,v"><code></asyxml>*/
+ restricted vector u,v;/*<asyxml></code><documentation>u = unit(AB) = direction vector,v = normal vector.</documentation></property><property type = "real" signature="a,b,c"><code></asyxml>*/
+ restricted real a,b,c;/*<asyxml></code><documentation>Coefficients of the equation ax + by + c = 0 in the coordinate system of 'A'.</documentation></property><property type = "real" signature="slope,origin"><code></asyxml>*/
+ restricted real slope, origin;/*<asyxml></code><documentation>Slope and ordinate at the origin.</documentation></property></asyxml>*/
+ /*<asyxml><method type = "line" signature="copy()"><code></asyxml>*/
+ line copy()
+ {/*<asyxml></code><documentation>Copy a line in a new instance.</documentation></method></asyxml>*/
+ line l = new line;
+ l.A = A;
+ l.B = B;
+ l.a = a;
+ l.b = b;
+ l.c = c;
+ l.slope = slope;
+ l.origin = origin;
+ l.u = u;
+ l.v = v;
+ l.extendA = extendA;
+ l.extendB = extendB;
+ return l;
+ }
+
+ /*<asyxml><method type = "void" signature="init(point,bool,point,bool)"><code></asyxml>*/
+ void init(point A, bool extendA = true, point B, bool extendB = true)
+ {/*<asyxml></code><documentation>Initialize line.
+ If 'extendA' is true, the "line" is infinite in the direction of A.</documentation></method></asyxml>*/
+ point[] P = standardizecoordsys(A, B);
+ this.A = P[0];
+ this.B = P[1];
+ this.a = B.y - A.y;
+ this.b = A.x - B.x;
+ this.c = A.y * B.x - A.x * B.y;
+ this.slope= (this.b == 0) ? infinity : -this.a/this.b;
+ this.origin = (this.b == 0) ? (this.c == 0) ? 0:infinity : -this.c/this.b;
+ this.u = unit(P[1]-P[0]);
+ // int tmp = sgnd(this.slope);
+ // this.u = (dot((pair)this.u, N) >= 0) ? tmp * this.u : -tmp * this.u;
+ this.v = rotate(90, point(P[0].coordsys, (0, 0))) * this.u;
+ this.extendA = extendA;
+ this.extendB = extendB;
+ }
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><function type="line" signature="line(point,bool,point,bool)"><code></asyxml>*/
+line line(point A, bool extendA = true, point B, bool extendB = true)
+{/*<asyxml></code><documentation>Return the line passing through 'A' and 'B'.
+ If 'extendA' is true, the "line" is infinite in the direction of A.
+ A "line" can be half-line or segment.</documentation></function></asyxml>*/
+ if (A == B) abort("line: the points must be distinct.");
+ line l;
+ l.init(A, extendA, B, extendB);
+ return l;
+}
+
+/*<asyxml><struct signature="segment"><code></asyxml>*/
+struct segment
+{/*<asyxml></code><documentation><look href = "struct line"/>.</documentation></asyxml>*/
+ restricted point A, B;// Extremity.
+ restricted vector u, v;// u = direction vector, v = normal vector.
+ restricted real a, b, c;// Coefficients of the équation ax + by + c = 0
+ restricted real slope, origin;
+ segment copy()
+ {
+ segment s = new segment;
+ s.A = A;
+ s.B = B;
+ s.a = a;
+ s.b = b;
+ s.c = c;
+ s.slope = slope;
+ s.origin = origin;
+ s.u = u;
+ s.v = v;
+ return s;
+ }
+
+ void init(point A, point B)
+ {
+ line l;
+ l.init(A, B);
+ this.A = l.A; this.B = l.B;
+ this.a = l.a; this.b = l.b; this.c = l.c;
+ this.slope = l.slope; this.origin = l.origin;
+ this.u = l.u; this.v = l.v;
+ }
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><function type="segment" signature="segment(point,point)"><code></asyxml>*/
+segment segment(point A, point B)
+{/*<asyxml></code><documentation>Return the segment whose the extremities are A and B.</documentation></function></asyxml>*/
+ segment s;
+ s.init(A, B);
+ return s;
+}
+
+/*<asyxml><function type="real" signature="length(segment)"><code></asyxml>*/
+real length(segment s)
+{/*<asyxml></code><documentation>Return the length of 's'.</documentation></function></asyxml>*/
+ return abs(s.A - s.B);
+}
+
+/*<asyxml><operator type = "line" signature="cast(segment)"><code></asyxml>*/
+line operator cast(segment s)
+{/*<asyxml></code><documentation>A segment is casted to a "finite line".</documentation></operator></asyxml>*/
+ return line(s.A, false, s.B, false);
+}
+
+/*<asyxml><operator type = "segment" signature="cast(line)"><code></asyxml>*/
+segment operator cast(line l)
+{/*<asyxml></code><documentation>Cast line 'l' to segment [l.A l.B].</documentation></operator></asyxml>*/
+ return segment(l.A, l.B);
+}
+
+/*<asyxml><operator type = "line" signature="*(transform,line)"><code></asyxml>*/
+line operator *(transform t, line l)
+{/*<asyxml></code><documentation>Provide transform * line</documentation></operator></asyxml>*/
+ return line(t * l.A, l.extendA, t * l.B, l.extendB);
+}
+/*<asyxml><operator type = "line" signature="/(line,real)"><code></asyxml>*/
+line operator /(line l, real x)
+{/*<asyxml></code><documentation>Provide l/x.
+ Return the line passing through l.A/x and l.B/x.</documentation></operator></asyxml>*/
+ return line(l.A/x, l.extendA, l.B/x, l.extendB);
+}
+line operator /(line l, int x){return line(l.A/x, l.B/x);}
+/*<asyxml><operator type = "line" signature="*(real,line)"><code></asyxml>*/
+line operator *(real x, line l)
+{/*<asyxml></code><documentation>Provide x * l.
+ Return the line passing through x * l.A and x * l.B.</documentation></operator></asyxml>*/
+ return line(x * l.A, l.extendA, x * l.B, l.extendB);
+}
+line operator *(int x, line l){return line(x * l.A, l.extendA, x * l.B, l.extendB);}
+
+/*<asyxml><operator type = "line" signature="*(point,line)"><code></asyxml>*/
+line operator *(point M, line l)
+{/*<asyxml></code><documentation>Provide point * line.
+ Return the line passing through unit(M) * l.A and unit(M) * l.B.</documentation></operator></asyxml>*/
+ return line(unit(M) * l.A, l.extendA, unit(M) * l.B, l.extendB);
+}
+/*<asyxml><operator type = "line" signature="+(line,point)"><code></asyxml>*/
+line operator +(line l, vector u)
+{/*<asyxml></code><documentation>Provide line + vector (and so line + point).
+ Return the line 'l' shifted by 'u'.</documentation></operator></asyxml>*/
+ return line(l.A + u, l.extendA, l.B + u, l.extendB);
+}
+/*<asyxml><operator type = "line" signature="-(line,vector)"><code></asyxml>*/
+line operator -(line l, vector u)
+{/*<asyxml></code><documentation>Provide line - vector (and so line - point).
+ Return the line 'l' shifted by '-u'.</documentation></operator></asyxml>*/
+ return line(l.A - u, l.extendA, l.B - u, l.extendB);
+}
+
+/*<asyxml><operator type = "line[]" signature="^^(line,line)"><code></asyxml>*/
+line[] operator ^^(line l1, line l2)
+{/*<asyxml></code><documentation>Provide line^^line.
+ Return the line array {l1, l2}.</documentation></operator></asyxml>*/
+ line[] ol;
+ ol.push(l1); ol.push(l2);
+ return ol;
+}
+
+/*<asyxml><operator type = "line[]" signature="^^(line,line[])"><code></asyxml>*/
+line[] operator ^^(line l1, line[] l2)
+{/*<asyxml></code><documentation>Provide line^^line[].
+ Return the line array {l1, l2[0], l2[1]...}.
+ line[]^^line is also defined.</documentation></operator></asyxml>*/
+ line[] ol;
+ ol.push(l1);
+ for (int i = 0; i < l2.length; ++i) {
+ ol.push(l2[i]);
+ }
+ return ol;
+}
+line[] operator ^^(line[] l2, line l1)
+{
+ line[] ol = l2;
+ ol.push(l1);
+ return ol;
+}
+
+/*<asyxml><operator type = "line[]" signature="^^(line,line[])"><code></asyxml>*/
+line[] operator ^^(line l1[], line[] l2)
+{/*<asyxml></code><documentation>Provide line[]^^line[].
+ Return the line array {l1[0], l1[1], ..., l2[0], l2[1], ...}.</documentation></operator></asyxml>*/
+ line[] ol = l1;
+ for (int i = 0; i < l2.length; ++i) {
+ ol.push(l2[i]);
+ }
+ return ol;
+}
+
+/*<asyxml><function type="bool" signature="sameside(point,point,line)"><code></asyxml>*/
+bool sameside(point M, point P, line l)
+{/*<asyxml></code><documentation>Return 'true' iff 'M' and 'N' are same side of the line (or on the line) 'l'.</documentation></function></asyxml>*/
+ pair A = l.A, B = l.B, m = M, p = P;
+ pair mil = (A + B)/2;
+ pair mA = rotate(90, mil) * A;
+ pair mB = rotate(-90, mil) * A;
+ return (abs(m - mA) <= abs(m - mB)) == (abs(p - mA) <= abs(p - mB));
+ // transform proj = projection(l.A, l.B);
+ // point Mp = proj * M;
+ // point Pp = proj * P;
+ // dot(Mp);dot(Pp);
+ // return dot(locate(Mp - M), locate(Pp - P)) >= 0;
+}
+
+/*<asyxml><function type="line" signature="line(segment)"><code></asyxml>*/
+line line(segment s)
+{/*<asyxml></code><documentation>Return the line passing through 's.A'
+ and 's.B'.</documentation></function></asyxml>*/
+ return line(s.A, s.B);
+}
+/*<asyxml><function type="segment" signature="segment(line)"><code></asyxml>*/
+segment segment(line l)
+{/*<asyxml></code><documentation>Return the segment whose extremities
+ are 'l.A' and 'l.B'.</documentation></function></asyxml>*/
+ return segment(l.A, l.B);
+}
+
+/*<asyxml><function type="point" signature="midpoint(segment)"><code></asyxml>*/
+point midpoint(segment s)
+{/*<asyxml></code><documentation>Return the midpoint of 's'.</documentation></function></asyxml>*/
+ return 0.5 * (s.A + s.B);
+}
+
+/*<asyxml><function type="void" signature="write(line)"><code></asyxml>*/
+void write(explicit line l)
+{/*<asyxml></code><documentation>Write some informations about 'l'.</documentation></function></asyxml>*/
+ write("A = "+(string)((pair)l.A));
+ write("Extend A = "+(l.extendA ? "true" : "false"));
+ write("B = "+(string)((pair)l.B));
+ write("Extend B = "+(l.extendB ? "true" : "false"));
+ write("u = "+(string)((pair)l.u));
+ write("v = "+(string)((pair)l.v));
+ write("a = "+(string) l.a);
+ write("b = "+(string) l.b);
+ write("c = "+(string) l.c);
+ write("slope = "+(string) l.slope);
+ write("origin = "+(string) l.origin);
+}
+
+/*<asyxml><function type="void" signature="write(explicit segment)"><code></asyxml>*/
+void write(explicit segment s)
+{/*<asyxml></code><documentation>Write some informations about 's'.</documentation></function></asyxml>*/
+ write("A = "+(string)((pair)s.A));
+ write("B = "+(string)((pair)s.B));
+ write("u = "+(string)((pair)s.u));
+ write("v = "+(string)((pair)s.v));
+ write("a = "+(string) s.a);
+ write("b = "+(string) s.b);
+ write("c = "+(string) s.c);
+ write("slope = "+(string) s.slope);
+ write("origin = "+(string) s.origin);
+}
+
+/*<asyxml><operator type = "bool" signature="==(line,line)"><code></asyxml>*/
+bool operator ==(line l1, line l2)
+{/*<asyxml></code><documentation>Provide the test 'line == line'.</documentation></operator></asyxml>*/
+ return (collinear(l1.u, l2.u) &&
+ abs(ypart((locate(l1.A) - locate(l1.B))/(locate(l1.A) - locate(l2.B)))) < epsgeo &&
+ l1.extendA == l2.extendA && l1.extendB == l2.extendB);
+}
+
+/*<asyxml><operator type = "bool" signature="!=(line,line)"><code></asyxml>*/
+bool operator !=(line l1, line l2)
+{/*<asyxml></code><documentation>Provide the test 'line != line'.</documentation></operator></asyxml>*/
+ return !(l1 == l2);
+}
+
+/*<asyxml><operator type = "bool" signature="@(point,line)"><code></asyxml>*/
+bool operator @(point m, line l)
+{/*<asyxml></code><documentation>Provide the test 'point @ line'.
+ Return true iff 'm' is on the 'l'.</documentation></operator></asyxml>*/
+ point M = changecoordsys(l.A.coordsys, m);
+ if (abs(l.a * M.x + l.b * M.y + l.c) >= epsgeo) return false;
+ if (l.extendA && l.extendB) return true;
+ if (!l.extendA && !l.extendB) return between(l.A, M, l.B);
+ if (l.extendA) return sameside(M, l.A, l.B);
+ return sameside(M, l.B, l.A);
+}
+
+/*<asyxml><function type="coordsys" signature="coordsys(line)"><code></asyxml>*/
+coordsys coordsys(line l)
+{/*<asyxml></code><documentation>Return the coordinate system in which 'l' is defined.</documentation></function></asyxml>*/
+ return l.A.coordsys;
+}
+
+/*<asyxml><function type="line" signature="reverse(line)"><code></asyxml>*/
+line reverse(line l)
+{/*<asyxml></code><documentation>Permute the points 'A' and 'B' of 'l' and so its orientation.</documentation></function></asyxml>*/
+ return line(l.B, l.extendB, l.A, l.extendA);
+}
+
+/*<asyxml><function type="line" signature="extend(line)"><code></asyxml>*/
+line extend(line l)
+{/*<asyxml></code><documentation>Return the infinite line passing through 'l.A' and 'l.B'.</documentation></function></asyxml>*/
+ line ol = l.copy();
+ ol.extendA = true;
+ ol.extendB = true;
+ return ol;
+}
+
+/*<asyxml><function type="line" signature="complementary(explicit line)"><code></asyxml>*/
+line complementary(explicit line l)
+{/*<asyxml></code><documentation>Return the complementary of a half-line with respect of
+ the full line 'l'.</documentation></function></asyxml>*/
+ if (l.extendA && l.extendB)
+ abort("complementary: the parameter is not a half-line.");
+ point origin = l.extendA ? l.B : l.A;
+ point ptdir = l.extendA ?
+ rotate(180, l.B) * l.A : rotate(180, l.A) * l.B;
+ return line(origin, false, ptdir);
+}
+
+/*<asyxml><function type="line[]" signature="complementary(explicit segment)"><code></asyxml>*/
+line[] complementary(explicit segment s)
+{/*<asyxml></code><documentation>Return the two half-lines of origin 's.A' and 's.B' respectively.</documentation></function></asyxml>*/
+ line[] ol = new line[2];
+ ol[0] = complementary(line(s.A, false, s.B));
+ ol[1] = complementary(line(s.A, s.B, false));
+ return ol;
+}
+
+/*<asyxml><function type="line" signature="Ox(coordsys)"><code></asyxml>*/
+line Ox(coordsys R = currentcoordsys)
+{/*<asyxml></code><documentation>Return the x-axis of 'R'.</documentation></function></asyxml>*/
+ return line(point(R, (0, 0)), point(R, E));
+}
+/*<asyxml><constant type = "line" signature="Ox"><code></asyxml>*/
+restricted line Ox = Ox();/*<asyxml></code><documentation>the x-axis of
+ the default coordinate system.</documentation></constant></asyxml>*/
+
+/*<asyxml><function type="line" signature="Oy(coordsys)"><code></asyxml>*/
+line Oy(coordsys R = currentcoordsys)
+{/*<asyxml></code><documentation>Return the y-axis of 'R'.</documentation></function></asyxml>*/
+ return line(point(R, (0, 0)), point(R, N));
+}
+/*<asyxml><constant type = "line" signature="Oy"><code></asyxml>*/
+restricted line Oy = Oy();/*<asyxml></code><documentation>the y-axis of
+ the default coordinate system.</documentation></constant></asyxml>*/
+
+/*<asyxml><function type="line" signature="line(real,point)"><code></asyxml>*/
+line line(real a, point A = point(currentcoordsys, (0, 0)))
+{/*<asyxml></code><documentation>Return the line passing through 'A' with an
+ angle (in the coordinate system of A) 'a' in degrees.
+ line(point, real) is also defined.</documentation></function></asyxml>*/
+ return line(A, A + point(A.coordsys, A.coordsys.polar(1, radians(a))));
+}
+line line(point A = point(currentcoordsys, (0, 0)), real a)
+{
+ return line(a, A);
+}
+line line(int a, point A = point(currentcoordsys, (0, 0)))
+{
+ return line((real)a, A);
+}
+
+/*<asyxml><function type="line" signature="line(coordsys,real,real)"><code></asyxml>*/
+line line(coordsys R = currentcoordsys, real slope, real origin)
+{/*<asyxml></code><documentation>Return the line defined by slope and y-intercept relative to 'R'.</documentation></function></asyxml>*/
+ if (slope == infinity || slope == -infinity)
+ abort("The slope is infinite. Please, use the routine 'vline'.");
+ return line(point(R, (0, origin)), point(R, (1, origin + slope)));
+}
+
+/*<asyxml><function type="line" signature="line(coordsys,real,real,real)"><code></asyxml>*/
+line line(coordsys R = currentcoordsys, real a, real b, real c)
+{/*<asyxml></code><documentation>Retrun the line defined by equation relative to 'R'.</documentation></function></asyxml>*/
+ if (a == 0 && b == 0) abort("line: inconsistent equation...");
+ pair M;
+ M = (a == 0) ? (0, -c/b) : (-c/a, 0);
+ return line(point(R, M), point(R, M + (-b, a)));
+}
+
+/*<asyxml><function type="line" signature="vline(coordsys)"><code></asyxml>*/
+line vline(coordsys R = currentcoordsys)
+{/*<asyxml></code><documentation>Return a vertical line in 'R' passing through the origin of 'R'.</documentation></function></asyxml>*/
+ point P = point(R, (0, 0));
+ point PP = point(R, (R.O + N)/R);
+ return line(P, PP);
+}
+/*<asyxml><constant type = "line" signature="vline"><code></asyxml>*/
+restricted line vline = vline();/*<asyxml></code><documentation>The vertical line in the current coordinate system passing
+ through the origin of this system.</documentation></constant></asyxml>*/
+
+/*<asyxml><function type="line" signature="hline(coordsys)"><code></asyxml>*/
+line hline(coordsys R = currentcoordsys)
+{/*<asyxml></code><documentation>Return a horizontal line in 'R' passing through the origin of 'R'.</documentation></function></asyxml>*/
+ point P = point(R, (0, 0));
+ point PP = point(R, (R.O + E)/R);
+ return line(P, PP);
+}
+/*<asyxml><constant type = "line" signature="hline"><code></asyxml>*/
+line hline = hline();/*<asyxml></code><documentation>The horizontal line in the current coordinate system passing
+ through the origin of this system.</documentation></constant></asyxml>*/
+
+/*<asyxml><function type="line" signature="changecoordsys(coordsys,line)"><code></asyxml>*/
+line changecoordsys(coordsys R, line l)
+{/*<asyxml></code><documentation>Return the line 'l' in the coordinate system 'R'.</documentation></function></asyxml>*/
+ point A = changecoordsys(R, l.A);
+ point B = changecoordsys(R, l.B);
+ return line(A, B);
+}
+
+/*<asyxml><function type="transform" signature="scale(real,line,line,bool)"><code></asyxml>*/
+transform scale(real k, line l1, line l2, bool safe = false)
+{/*<asyxml></code><documentation>Return the dilatation with respect to
+ 'l1' in the direction of 'l2'.</documentation></function></asyxml>*/
+ return scale(k, l1.A, l1.B, l2.A, l2.B, safe);
+}
+
+/*<asyxml><function type="transform" signature="reflect(line)"><code></asyxml>*/
+transform reflect(line l)
+{/*<asyxml></code><documentation>Return the reflect about the line 'l'.</documentation></function></asyxml>*/
+ return reflect((pair)l.A, (pair)l.B);
+}
+
+/*<asyxml><function type="transform" signature="reflect(line,line)"><code></asyxml>*/
+transform reflect(line l1, line l2, bool safe = false)
+{/*<asyxml></code><documentation>Return the reflect about the line
+ 'l1' in the direction of 'l2'.</documentation></function></asyxml>*/
+ return scale(-1.0, l1, l2, safe);
+}
+
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(line,path)"><code></asyxml>*/
+point[] intersectionpoints(line l, path g)
+{/*<asyxml></code><documentation>Return all points of intersection of the line 'l' with the path 'g'.</documentation></function></asyxml>*/
+ // TODO utiliser la version 1.44 de intersections(path g, pair p, pair q)
+ // real [] t = intersections(g, l.A, l.B);
+ // coordsys R = coordsys(l);
+ // return sequence(new point(int n){return point(R, point(g, t[n])/R);}, t.length);
+ real [] t;
+ pair[] op;
+ pair A = l.A;
+ pair B = l.B;
+ real dy = B.y - A.y,
+ dx = A.x - B.x,
+ lg = length(g);
+
+ for (int i = 0; i < lg; ++i)
+ {
+ pair z0 = point(g, i),
+ z1 = point(g, i + 1),
+ c0 = postcontrol(g, i),
+ c1 = precontrol(g, i + 1),
+ t3 = z1 - z0 - 3 * c1 + 3 * c0,
+ t2 = 3 * z0 + 3 * c1 - 6 * c0,
+ t1 = 3 * c0 - 3z0;
+ real a = dy * t3.x + dx * t3.y,
+ b = dy * t2.x + dx * t2.y,
+ c = dy * t1.x + dx * t1.y,
+ d = dy * z0.x + dx * z0.y + A.y * B.x - A.x * B.y;
+
+ t = cubicroots(a, b, c, d);
+ for (int j = 0; j < t.length; ++j)
+ if (
+ t[j]>=0
+ && (
+ t[j]<1
+ || (
+ t[j] == 1
+ && (i == lg - 1)
+ && !cyclic(g)
+ )
+ )
+ ) {
+ op.push(point(g, i + t[j]));
+ }
+ }
+
+ point[] opp;
+ for (int i = 0; i < op.length; ++i)
+ opp.push(point(coordsys(l), op[i]/coordsys(l)));
+ return opp;
+}
+
+/*<asyxml><function type="point" signature="intersectionpoint(line,line)"><code></asyxml>*/
+point intersectionpoint(line l1, line l2)
+{/*<asyxml></code><documentation>Return the point of intersection of line 'l1' with 'l2'.
+ If 'l1' and 'l2' have an infinity or none point of intersection,
+ this routine return (infinity, infinity).</documentation></function></asyxml>*/
+ point[] P = standardizecoordsys(l1.A, l1.B, l2.A, l2.B);
+ coordsys R = P[0].coordsys;
+ pair p = extension(P[0], P[1], P[2], P[3]);
+ if(finite(p)){
+ point p = point(R, p/R);
+ if (p @ l1 && p @ l2) return p;
+ }
+ return point(R, (infinity, infinity));
+}
+
+/*<asyxml><function type="line" signature="parallel(point,line)"><code></asyxml>*/
+line parallel(point M, line l)
+{/*<asyxml></code><documentation>Return the line parallel to 'l' passing through 'M'.</documentation></function></asyxml>*/
+ point A, B;
+ if (M.coordsys != coordsys(l))
+ {
+ A = changecoordsys(M.coordsys, l.A);
+ B = changecoordsys(M.coordsys, l.B);
+ } else {A = l.A;B = l.B;}
+ return line(M, M - A + B);
+}
+
+/*<asyxml><function type="line" signature="parallel(point,explicit vector)"><code></asyxml>*/
+line parallel(point M, explicit vector dir)
+{/*<asyxml></code><documentation>Return the line of direction 'dir' and passing through 'M'.</documentation></function></asyxml>*/
+ return line(M, M + locate(dir));
+}
+
+/*<asyxml><function type="line" signature="parallel(point,explicit pair)"><code></asyxml>*/
+line parallel(point M, explicit pair dir)
+{/*<asyxml></code><documentation>Return the line of direction 'dir' and passing through 'M'.</documentation></function></asyxml>*/
+ return line(M, M + vector(currentcoordsys, dir));
+}
+
+/*<asyxml><function type="bool" signature="parallel(line,line)"><code></asyxml>*/
+bool parallel(line l1, line l2, bool strictly = false)
+{/*<asyxml></code><documentation>Return 'true' if 'l1' and 'l2' are (strictly ?) parallel.</documentation></function></asyxml>*/
+ bool coll = collinear(l1.u, l2.u);
+ return strictly ? coll && (l1 != l2) : coll;
+}
+
+/*<asyxml><function type="bool" signature="concurrent(...line[])"><code></asyxml>*/
+bool concurrent(... line[] l)
+{/*<asyxml></code><documentation>Returns true if all the lines 'l' are concurrent.</documentation></function></asyxml>*/
+ if (l.length < 3) abort("'concurrent' needs at least for three lines ...");
+ pair point = intersectionpoint(l[0], l[1]);
+ bool conc;
+ for (int i = 2; i < l.length; ++i) {
+ pair pt = intersectionpoint(l[i - 1], l[i]);
+ conc = simeq(pt, point);
+ if (!conc) break;
+ }
+ return conc;
+}
+
+/*<asyxml><function type="transform" signature="projection(line)"><code></asyxml>*/
+transform projection(line l)
+{/*<asyxml></code><documentation>Return the orthogonal projection on 'l'.</documentation></function></asyxml>*/
+ return projection(l.A, l.B);
+}
+
+/*<asyxml><function type="transform" signature="projection(line,line,bool)"><code></asyxml>*/
+transform projection(line l1, line l2, bool safe = false)
+{/*<asyxml></code><documentation>Return the projection on (AB) in parallel of (CD).
+ If 'safe = true' and (l1)//(l2) return the identity.
+ If 'safe = false' and (l1)//(l2) return a infinity scaling.</documentation></function></asyxml>*/
+ return projection(l1.A, l1.B, l2.A, l2.B, safe);
+}
+
+/*<asyxml><function type="transform" signature="vprojection(line,bool)"><code></asyxml>*/
+transform vprojection(line l, bool safe = false)
+{/*<asyxml></code><documentation>Return the projection on 'l' in parallel of N--S.
+ If 'safe' is 'true' the projected point keeps the same place if 'l'
+ is vertical.</documentation></function></asyxml>*/
+ coordsys R = defaultcoordsys;
+ return projection(l, line(point(R, N), point(R, S)), safe);
+}
+
+/*<asyxml><function type="transform" signature="hprojection(line,bool)"><code></asyxml>*/
+transform hprojection(line l, bool safe = false)
+{/*<asyxml></code><documentation>Return the projection on 'l' in parallel of E--W.
+ If 'safe' is 'true' the projected point keeps the same place if 'l'
+ is horizontal.</documentation></function></asyxml>*/
+ coordsys R = defaultcoordsys;
+ return projection(l, line(point(R, E), point(R, W)), safe);
+}
+
+/*<asyxml><function type="line" signature="perpendicular(point,line)"><code></asyxml>*/
+line perpendicular(point M, line l)
+{/*<asyxml></code><documentation>Return the perpendicular line of 'l' passing through 'M'.</documentation></function></asyxml>*/
+ point Mp = projection(l) * M;
+ point A = Mp == l.A ? l.B : l.A;
+ return line(Mp, rotate(90, Mp) * A);
+}
+
+/*<asyxml><function type="line" signature="perpendicular(point,explicit vector)"><code></asyxml>*/
+line perpendicular(point M, explicit vector normal)
+{/*<asyxml></code><documentation>Return the line passing through 'M'
+ whose normal is \param{normal}.</documentation></function></asyxml>*/
+ return perpendicular(M, line(M, M + locate(normal)));
+}
+
+/*<asyxml><function type="line" signature="perpendicular(point,explicit pair)"><code></asyxml>*/
+line perpendicular(point M, explicit pair normal)
+{/*<asyxml></code><documentation>Return the line passing through 'M'
+ whose normal is \param{normal} (given in the currentcoordsys).</documentation></function></asyxml>*/
+ return perpendicular(M, line(M, M + vector(currentcoordsys, normal)));
+}
+
+/*<asyxml><function type="bool" signature="perpendicular(line,line)"><code></asyxml>*/
+bool perpendicular(line l1, line l2)
+{/*<asyxml></code><documentation>Return 'true' if 'l1' and 'l2' are perpendicular.</documentation></function></asyxml>*/
+ return abs(dot(locate(l1.u), locate(l2.u))) < epsgeo ;
+}
+
+/*<asyxml><function type="real" signature="angle(line,coordsys)"><code></asyxml>*/
+real angle(line l, coordsys R = coordsys(l))
+{/*<asyxml></code><documentation>Return the angle of the oriented line 'l',
+ in radian, in the interval ]-pi, pi] and relatively to 'R'.</documentation></function></asyxml>*/
+ return angle(l.u, R, false);
+}
+
+/*<asyxml><function type="real" signature="degrees(line,coordsys,bool)"><code></asyxml>*/
+real degrees(line l, coordsys R = coordsys(l))
+{/*<asyxml></code><documentation>Returns the angle of the oriented line 'l' in degrees,
+ in the interval [0, 360[ and relatively to 'R'.</documentation></function></asyxml>*/
+ return degrees(angle(l, R));
+}
+
+/*<asyxml><function type="real" signature="sharpangle(line,line)"><code></asyxml>*/
+real sharpangle(line l1, line l2)
+{/*<asyxml></code><documentation>Return the measure in radians of the sharp angle formed by 'l1' and 'l2'.</documentation></function></asyxml>*/
+ vector u1 = l1.u;
+ vector u2 = (dot(l1.u, l2.u) < 0) ? -l2.u : l2.u;
+ real a12 = angle(locate(u2)) - angle(locate(u1));
+ a12 = a12%(sgnd(a12) * pi);
+ if (a12 <= -pi/2) {
+ a12 += pi;
+ } else if (a12 > pi/2) {
+ a12 -= pi;
+ }
+ return a12;
+}
+
+/*<asyxml><function type="real" signature="angle(line,line)"><code></asyxml>*/
+real angle(line l1, line l2)
+{/*<asyxml></code><documentation>Return the measure in radians of oriented angle (l1.u, l2.u).</documentation></function></asyxml>*/
+ return angle(locate(l2.u)) - angle(locate(l1.u));
+}
+
+/*<asyxml><function type="real" signature="degrees(line,line)"><code></asyxml>*/
+real degrees(line l1, line l2)
+{/*<asyxml></code><documentation>Return the measure in degrees of the
+ angle formed by the oriented lines 'l1' and 'l2'.</documentation></function></asyxml>*/
+ return degrees(angle(l1, l2));
+}
+
+/*<asyxml><function type="real" signature="sharpdegrees(line,line)"><code></asyxml>*/
+real sharpdegrees(line l1, line l2)
+{/*<asyxml></code><documentation>Return the measure in degrees of the sharp angle formed by 'l1' and 'l2'.</documentation></function></asyxml>*/
+ return degrees(sharpangle(l1, l2));
+}
+
+/*<asyxml><function type="line" signature="bisector(line,line,real,bool)"><code></asyxml>*/
+line bisector(line l1, line l2, real angle = 0, bool sharp = true)
+{/*<asyxml></code><documentation>Return the bisector of the angle formed by 'l1' and 'l2'
+ rotated by the angle 'angle' (in degrees) around intersection point of 'l1' with 'l2'.
+ If 'sharp' is true (the default), this routine returns the bisector of the sharp angle.
+ Note that the returned line inherit of coordinate system of 'l1'.</documentation></function></asyxml>*/
+ line ol;
+ if (l1 == l2) return l1;
+ point A = intersectionpoint(l1, l2);
+ if (finite(A)) {
+ if(sharp) ol = rotate(sharpdegrees(l1, l2)/2 + angle, A) * l1;
+ else {
+ coordsys R = coordsys(l1);
+ pair a = A, b = A + l1.u, c = A + l2.u;
+ pair pp = extension(a, a + dir(a--b, a--c), b, b + dir(b--a, b--c));
+ return rotate(angle, A) * line(A, point(R, pp/R));
+ }
+ } else {
+ ol = l1;
+ }
+ return ol;
+}
+
+/*<asyxml><function type="line" signature="sector(int,int,line,line,real,bool)"><code></asyxml>*/
+line sector(int n = 2, int p = 1, line l1, line l2, real angle = 0, bool sharp = true)
+{/*<asyxml></code><documentation>Return the p-th nth-sector of the angle
+ formed by the oriented line 'l1' and 'l2'
+ rotated by the angle 'angle' (in degrees) around the intersection point of 'l1' with 'l2'.
+ If 'sharp' is true (the default), this routine returns the bisector of the sharp angle.
+ Note that the returned line inherit of coordinate system of 'l1'.</documentation></function></asyxml>*/
+ line ol;
+ if (l1 == l2) return l1;
+ point A = intersectionpoint(l1, l2);
+ if (finite(A)) {
+ if(sharp) ol = rotate(p * sharpdegrees(l1, l2)/n + angle, A) * l1;
+ else {
+ ol = rotate(p * degrees(l1, l2)/n + angle, A) * l1;
+ }
+ } else {
+ ol = l1;
+ }
+ return ol;
+}
+
+/*<asyxml><function type="line" signature="bisector(point,point,point,point,real)"><code></asyxml>*/
+line bisector(point A, point B, point C, point D, real angle = 0, bool sharp = true)
+{/*<asyxml></code><documentation>Return the bisector of the angle formed by the lines (AB) and (CD).
+ <look href = "#bisector(line, line, real, bool)"/>.</documentation></function></asyxml>*/
+ point[] P = standardizecoordsys(A, B, C, D);
+ return bisector(line(P[0], P[1]), line(P[2], P[3]), angle, sharp);
+}
+
+/*<asyxml><function type="line" signature="bisector(segment,real)"><code></asyxml>*/
+line bisector(segment s, real angle = 0)
+{/*<asyxml></code><documentation>Return the bisector of the segment line 's' rotated by 'angle' (in degrees) around the
+ midpoint of 's'.</documentation></function></asyxml>*/
+ coordsys R = coordsys(s);
+ point m = midpoint(s);
+ vector dir = rotateO(90) * unit(s.A - m);
+ return rotate(angle, m) * line(m + dir, m - dir);
+}
+
+/*<asyxml><function type="line" signature="bisector(point,point,real)"><code></asyxml>*/
+line bisector(point A, point B, real angle = 0)
+{/*<asyxml></code><documentation>Return the bisector of the segment line [AB] rotated by 'angle' (in degrees) around the
+ midpoint of [AB].</documentation></function></asyxml>*/
+ point[] P = standardizecoordsys(A, B);
+ return bisector(segment(P[0], P[1]), angle);
+}
+
+/*<asyxml><function type="real" signature="distance(point,line)"><code></asyxml>*/
+real distance(point M, line l)
+{/*<asyxml></code><documentation>Return the distance from 'M' to 'l'.
+ distance(line, point) is also defined.</documentation></function></asyxml>*/
+ point A = changecoordsys(defaultcoordsys, l.A);
+ point B = changecoordsys(defaultcoordsys, l.B);
+ line ll = line(A, B);
+ pair m = locate(M);
+ return abs(ll.a * m.x + ll.b * m.y + ll.c)/sqrt(ll.a^2 + ll.b^2);
+}
+
+real distance(line l, point M)
+{
+ return distance(M, l);
+}
+
+/*<asyxml><function type="void" signature="draw(picture,Label,line,bool,bool,align,pen,arrowbar,Label,marker)"><code></asyxml>*/
+void draw(picture pic = currentpicture, Label L = "",
+ line l, bool dirA = l.extendA, bool dirB = l.extendB,
+ align align = NoAlign, pen p = currentpen,
+ arrowbar arrow = None,
+ Label legend = "", marker marker = nomarker,
+ pathModifier pathModifier = NoModifier)
+{/*<asyxml></code><documentation>Draw the line 'l' without altering the size of picture pic.
+ The boolean parameters control the infinite section.
+ The global variable 'linemargin' (default value is 0) allows to modify
+ the bounding box in which the line must be drawn.</documentation></function></asyxml>*/
+ if(!(dirA || dirB)) draw(l.A--l.B, invisible);// l is a segment.
+ Drawline(pic, L, l.A, dirP = dirA, l.B, dirQ = dirB,
+ align, p, arrow,
+ legend, marker, pathModifier);
+}
+
+/*<asyxml><function type="void" signature="draw(picture,Label[], line[], align,pen[], arrowbar,Label,marker)"><code></asyxml>*/
+void draw(picture pic = currentpicture, Label[] L = new Label[], line[] l,
+ align align = NoAlign, pen[] p = new pen[],
+ arrowbar arrow = None,
+ Label[] legend = new Label[], marker marker = nomarker,
+ pathModifier pathModifier = NoModifier)
+{/*<asyxml></code><documentation>Draw each lines with the corresponding pen.</documentation></function></asyxml>*/
+ for (int i = 0; i < l.length; ++i) {
+ draw(pic, L.length>0 ? L[i] : "", l[i],
+ align, p = p.length>0 ? p[i] : currentpen,
+ arrow, legend.length>0 ? legend[i] : "", marker,
+ pathModifier);
+ }
+}
+
+/*<asyxml><function type="void" signature="draw(picture,Label[], line[], align,pen,arrowbar,Label,marker)"><code></asyxml>*/
+void draw(picture pic = currentpicture, Label[] L = new Label[], line[] l,
+ align align = NoAlign, pen p,
+ arrowbar arrow = None,
+ Label[] legend = new Label[], marker marker = nomarker,
+ pathModifier pathModifier = NoModifier)
+{/*<asyxml></code><documentation>Draw each lines with the same pen 'p'.</documentation></function></asyxml>*/
+ pen[] tp = sequence(new pen(int i){return p;}, l.length);
+ draw(pic, L, l, align, tp, arrow, legend, marker, pathModifier);
+}
+
+/*<asyxml><function type="void" signature="show(picture,line,pen)"><code></asyxml>*/
+void show(picture pic = currentpicture, line l, pen p = red)
+{/*<asyxml></code><documentation>Draw some informations of 'l'.</documentation></function></asyxml>*/
+ dot("$A$", (pair)l.A, align = -locate(l.v), p);
+ dot("$B$", (pair)l.B, align = -locate(l.v), p);
+ draw(l, dotted);
+ draw("$\vec{u}$", locate(l.A)--locate(l.A + l.u), p, Arrow);
+ draw("$\vec{v}$", locate(l.A)--locate(l.A + l.v), p, Arrow);
+}
+
+/*<asyxml><function type="point[]" signature="sameside(point,line,line)"><code></asyxml>*/
+point[] sameside(point M, line l1, line l2)
+{/*<asyxml></code><documentation>Return two points on 'l1' and 'l2' respectively.
+ The first point is from the same side of M relatively to 'l2',
+ the second point is from the same side of M relatively to 'l1'.</documentation></function></asyxml>*/
+ point[] op;
+ coordsys R1 = coordsys(l1);
+ coordsys R2 = coordsys(l2);
+ if (parallel(l1, l2)) {
+ op.push(projection(l1) * M);
+ op.push(projection(l2) * M);
+ } else {
+ point O = intersectionpoint(l1, l2);
+ if (M @ l2) op.push((sameside(M, O + l1.u, l2)) ? O + l1.u : rotate(180, O) * (O + l1.u));
+ else op.push(projection(l1, l2) * M);
+ if (M @ l1) op.push((sameside(M, O + l2.u, l1)) ? O + l2.u : rotate(180, O) * (O + l2.u));
+ else {op.push(projection(l2, l1) * M);}
+ }
+ return op;
+}
+
+/*<asyxml><function type="void" signature="markangle(picture,Label,int,real,real,explicit line,explicit line,explicit pair,arrowbar,pen,filltype,margin,marker)"><code></asyxml>*/
+void markangle(picture pic = currentpicture,
+ Label L = "", int n = 1, real radius = 0, real space = 0,
+ explicit line l1, explicit line l2, explicit pair align = dir(1),
+ arrowbar arrow = None, pen p = currentpen,
+ filltype filltype = NoFill,
+ margin margin = NoMargin, marker marker = nomarker)
+{/*<asyxml></code><documentation>Mark the angle (l1, l2) aligned in the direction 'align' relative to 'l1'.
+ Commune values for 'align' are dir(real).</documentation></function></asyxml>*/
+ if (parallel(l1, l2, true)) return;
+ real al = degrees(l1, defaultcoordsys);
+ pair O, A, B;
+ if (radius == 0) radius = markangleradius(p);
+ real d = degrees(locate(l1.u));
+ align = rotate(d) * align;
+ if (l1 == l2) {
+ O = midpoint(segment(l1.A, l1.B));
+ A = l1.A;B = l1.B;
+ if (sameside(rotate(sgn(angle(B-A)) * 45, O) * A, O + align, l1)) {radius = -radius;}
+ } else {
+ O = intersectionpoint(extend(l1), extend(l2));
+ pair R = O + align;
+ point [] ss = sameside(point(coordsys(l1), R/coordsys(l1)), l1, l2);
+ A = ss[0];
+ B = ss[1];
+ }
+ markangle(pic = pic, L = L, n = n, radius = radius, space = space,
+ O = O, A = A, B = B,
+ arrow = arrow, p = p, filltype = filltype,
+ margin = margin, marker = marker);
+}
+
+/*<asyxml><function type="void" signature="markangle(picture,Label,int,real,real,explicit line,explicit line,explicit vector,arrowbar,pen,filltype,margin,marker)"><code></asyxml>*/
+void markangle(picture pic = currentpicture,
+ Label L = "", int n = 1, real radius = 0, real space = 0,
+ explicit line l1, explicit line l2, explicit vector align,
+ arrowbar arrow = None, pen p = currentpen,
+ filltype filltype = NoFill,
+ margin margin = NoMargin, marker marker = nomarker)
+{/*<asyxml></code><documentation>Mark the angle (l1, l2) in the direction 'dir' given relatively to 'l1'.</documentation></function></asyxml>*/
+ markangle(pic, L, n, radius, space, l1, l2, (pair)align, arrow,
+ p, filltype, margin, marker);
+}
+
+/*<asyxml><function type="void" signature="markangle(picture,Label,int,real,real,line,line,arrowbar,pen,filltype,margin,marker)"><code></asyxml>*/
+// void markangle(picture pic = currentpicture,
+// Label L = "", int n = 1, real radius = 0, real space = 0,
+// explicit line l1, explicit line l2,
+// arrowbar arrow = None, pen p = currentpen,
+// filltype filltype = NoFill,
+// margin margin = NoMargin, marker marker = nomarker)
+// {/*<asyxml></code><documentation>Mark the oriented angle (l1, l2).</documentation></function></asyxml>*/
+// if (parallel(l1, l2, true)) return;
+// real al = degrees(l1, defaultcoordsys);
+// pair O, A, B;
+// if (radius == 0) radius = markangleradius(p);
+// real d = degrees(locate(l1.u));
+// if (l1 == l2) {
+// O = midpoint(segment(l1.A, l1.B));
+// } else {
+// O = intersectionpoint(extend(l1), extend(l2));
+// }
+// A = O + locate(l1.u);
+// B = O + locate(l2.u);
+// markangle(pic = pic, L = L, n = n, radius = radius, space = space,
+// O = O, A = A, B = B,
+// arrow = arrow, p = p, filltype = filltype,
+// margin = margin, marker = marker);
+// }
+
+/*<asyxml><function type="void" signature="perpendicularmark(picture,line,line,real,pen,int,margin,filltype)"><code></asyxml>*/
+void perpendicularmark(picture pic = currentpicture, line l1, line l2,
+ real size = 0, pen p = currentpen, int quarter = 1,
+ margin margin = NoMargin, filltype filltype = NoFill)
+{/*<asyxml></code><documentation>Draw a right angle at the intersection point of lines and
+ aligned in the 'quarter' nth quarter of circle formed by 'l1.u' and
+ 'l2.u'.</documentation></function></asyxml>*/
+ point P = intersectionpoint(l1, l2);
+ pair align = rotate(90 * (quarter - 1)) * dir(45);
+ perpendicularmark(P, align, locate(l1.u), size, p, margin, filltype);
+}
+// *.........................LINES.........................*
+// *=======================================================*
+
+// *=======================================================*
+// *........................CONICS.........................*
+/*<asyxml><struct signature="bqe"><code></asyxml>*/
+struct bqe
+{/*<asyxml></code><documentation>Bivariate Quadratic Equation.</documentation></asyxml>*/
+ /*<asyxml><property type = "real[]" signature="a"><code></asyxml>*/
+ real[] a;/*<asyxml></code><documentation>a[0] * x^2 + a[1] * x * y + a[2] * y^2 + a[3] * x + a[4] * y + a[5] = 0</documentation></property><property type = "coordsys" signature="coordsys"><code></asyxml>*/
+ coordsys coordsys;/*<asyxml></code></property></asyxml>*/
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><function type="bqe" signature="bqe(coordsys,real,real,real,real,real,real)"><code></asyxml>*/
+bqe bqe(coordsys R = currentcoordsys,
+ real a, real b, real c, real d, real e, real f)
+{/*<asyxml></code><documentation>Return the bivariate quadratic equation
+ a[0] * x^2 + a[1] * x * y + a[2] * y^2 + a[3] * x + a[4] * y + a[5] = 0
+ relatively to the coordinate system R.</documentation></function></asyxml>*/
+ bqe obqe;
+ obqe.coordsys = R;
+ obqe.a = new real[] {a, b, c, d, e, f};
+ return obqe;
+}
+
+/*<asyxml><function type="bqe" signature="changecoordsys(coordsys,bqe)"><code></asyxml>*/
+bqe changecoordsys(coordsys R, bqe bqe)
+{/*<asyxml></code><documentation>Returns the bivariate quadratic equation relatively to 'R'.</documentation></function></asyxml>*/
+ pair i = coordinates(changecoordsys(R, vector(defaultcoordsys,
+ bqe.coordsys.i)));
+ pair j = coordinates(changecoordsys(R, vector(defaultcoordsys,
+ bqe.coordsys.j)));
+ pair O = coordinates(changecoordsys(R, point(defaultcoordsys,
+ bqe.coordsys.O)));
+ real a = bqe.a[0], b = bqe.a[1], c = bqe.a[2], d = bqe.a[3], f = bqe.a[4], g = bqe.a[5];
+ real ux = i.x, uy = i.y;
+ real vx = j.x, vy = j.y;
+ real ox = O.x, oy = O.y;
+ real D = ux * vy - uy * vx;
+ real ap = (a * vy^2 - b * uy * vy + c * uy^2)/D^2;
+ real bpp = (-2 * a * vx * vy + b * ux * vy + b * uy * vx - 2 * c * ux * uy)/D^2;
+ real cp = (a * vx^2 - b * ux * vx + c * ux^2)/D^2;
+ real dp = (-2a * ox * vy^2 + 2a * oy * vx * vy + 2b * ox * uy * vy-
+ b * oy * ux * vy - b * oy * uy * vx - 2c * ox * uy^2 + 2c * oy * uy * ux)/D^2+
+ (d * vy - f * uy)/D;
+ real fp = (2a * ox * vx * vy - b * ox * ux * vy - 2a * oy * vx^2-
+ b * ox * uy * vx + 2 * b * oy * ux * vx + 2c * ox * ux * uy - 2c * oy * ux^2)/D^2+
+ (f * ux - d * vx)/D;
+ g = (a * ox^2 * vy^2 - 2a * ox * oy * vx * vy - b * ox^2 * uy * vy + b * ox * oy * ux * vy+
+ a * oy^2 * vx^2 + b * ox * oy * uy * vx - b * oy^2 * ux * vx + c * ox^2 * uy^2-
+ 2 * c * ox * oy * ux * uy + c * oy^2 * ux^2)/D^2+
+ (d * oy * vx + f * ox * uy - d * ox * vy - f * oy * ux)/D + g;
+ bqe obqe;
+ obqe.a = approximate(new real[] {ap, bpp, cp, dp, fp, g});
+ obqe.coordsys = R;
+ return obqe;
+}
+
+/*<asyxml><function type="bqe" signature="bqe(point,point,point,point,point)"><code></asyxml>*/
+bqe bqe(point M1, point M2, point M3, point M4, point M5)
+{/*<asyxml></code><documentation>Return the bqe of conic passing through the five points (if possible).</documentation></function></asyxml>*/
+ coordsys R;
+ pair[] pts;
+ if (samecoordsys(M1, M2, M3, M4, M5)) {
+ R = M1.coordsys;
+ pts= new pair[] {M1.coordinates, M2.coordinates, M3.coordinates, M4.coordinates, M5.coordinates};
+ } else {
+ R = defaultcoordsys;
+ pts= new pair[] {M1, M2, M3, M4, M5};
+ }
+ real[][] M;
+ real[] x;
+ bqe bqe;
+ bqe.coordsys = R;
+ for (int i = 0; i < 5; ++i) {// Try a = -1
+ M[i] = new real[] {pts[i].x * pts[i].y, pts[i].y^2, pts[i].x, pts[i].y, 1};
+ x[i] = pts[i].x^2;
+ }
+ if(abs(determinant(M)) < 1e-5) {// Try c = -1
+ for (int i = 0; i < 5; ++i) {
+ M[i] = new real[] {pts[i].x^2, pts[i].x * pts[i].y, pts[i].x, pts[i].y, 1};
+ x[i] = pts[i].y^2;
+ }
+ real[] coef = solve(M, x);
+ bqe.a = new real[] {coef[0], coef[1], -1, coef[2], coef[3], coef[4]};
+ } else {
+ real[] coef = solve(M, x);
+ bqe.a = new real[] {-1, coef[0], coef[1], coef[2], coef[3], coef[4]};
+ }
+ bqe.a = approximate(bqe.a);
+ return bqe;
+}
+
+/*<asyxml><function type="bool" signature="samecoordsys(bool...bqe[])"><code></asyxml>*/
+bool samecoordsys(bool warn = true ... bqe[] bqes)
+{/*<asyxml></code><documentation>Return true if all the bivariate quadratic equations have the same coordinate system.</documentation></function></asyxml>*/
+ bool ret = true;
+ coordsys t = bqes[0].coordsys;
+ for (int i = 1; i < bqes.length; ++i) {
+ ret = (t == bqes[i].coordsys);
+ if(!ret) break;
+ t = bqes[i].coordsys;
+ }
+ if(warn && !ret)
+ warning("coodinatesystem",
+ "the coordinate system of two bivariate quadratic equations are not
+the same. The operation will be done relatively to the default coordinate
+system.");
+ return ret;
+}
+
+/*<asyxml><function type="real[]" signature="realquarticroots(real,real,real,real,real)"><code></asyxml>*/
+real[] realquarticroots(real a, real b, real c, real d, real e)
+{/*<asyxml></code><documentation>Return the real roots of the quartic equation ax^4 + b^x3 + cx^2 + dx = 0.</documentation></function></asyxml>*/
+ static real Fuzz = sqrt(realEpsilon);
+ pair[] zroots = quarticroots(a, b, c, d, e);
+ real[] roots;
+ real p(real x){return a * x^4 + b * x^3 + c * x^2 + d * x + e;}
+ real prime(real x){return 4 * a * x^3 + 3 * b * x^2 + 2 * c * x + d;}
+ real x;
+ bool search = true;
+ int n;
+ void addroot(real x)
+ {
+ bool exist = false;
+ for (int i = 0; i < roots.length; ++i) {
+ if(abs(roots[i]-x) < 1e-5) {exist = true; break;}
+ }
+ if(!exist) roots.push(x);
+ }
+ for(int i = 0; i < zroots.length; ++i) {
+ if(zroots[i].y == 0 || abs(p(zroots[i].x)) < Fuzz) addroot(zroots[i].x);
+ else {
+ if(abs(zroots[i].y) < 1e-3) {
+ x = zroots[i].x;
+ search = true;
+ n = 200;
+ while(search) {
+ real tx = abs(p(x)) < Fuzz ? x : newton(iterations = n, p, prime, x);
+ if(tx < realMax) {
+ if(abs(p(tx)) < Fuzz) {
+ addroot(tx);
+ search = false;
+ } else if(n < 200) n *=2;
+ else {
+ search = false;
+ }
+ } else search = false; //It's not a real root.
+ }
+ }
+ }
+ }
+ return roots;
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(bqe,bqe)"><code></asyxml>*/
+point[] intersectionpoints(bqe bqe1, bqe bqe2)
+{/*<asyxml></code><documentation>Return the interscetion of the two conic sections whose equations are 'bqe1' and 'bqe2'.</documentation></function></asyxml>*/
+ coordsys R = bqe1.coordsys;
+ bqe lbqe1, lbqe2;
+ real[] a, b;
+ if(R != bqe2.coordsys) {
+ R = currentcoordsys;
+ a = changecoordsys(R, bqe1).a;
+ b = changecoordsys(R, bqe2).a;
+ } else {
+ a = bqe1.a;
+ b = bqe2.a;
+ }
+ static real e = 100 * sqrt(realEpsilon);
+ real[] x, y, c;
+ point[] P;
+ if(abs(a[0]-b[0]) > e || abs(a[1]-b[1]) > e || abs(a[2]-b[2]) > e) {
+ c = new real[] {-2 * a[0]*a[2]*b[0]*b[2]+a[0]*a[2]*b[1]^2 - a[0]*a[1]*b[2]*b[1]+a[1]^2 * b[0]*b[2]-
+ a[2]*a[1]*b[0]*b[1]+a[0]^2 * b[2]^2 + a[2]^2 * b[0]^2,
+ -a[2]*a[1]*b[0]*b[4]-a[2]*a[4]*b[0]*b[1]-a[1]*a[3]*b[2]*b[1]+2 * a[0]*a[2]*b[1]*b[4]-
+ a[0]*a[1]*b[2]*b[4]+a[1]^2 * b[2]*b[3]-2 * a[2]*a[3]*b[0]*b[2]-2 * a[0]*a[2]*b[2]*b[3]+
+ a[2]*a[3]*b[1]^2 - a[2]*a[1]*b[1]*b[3]+2 * a[1]*a[4]*b[0]*b[2]+2 * a[2]^2 * b[0]*b[3]-
+ a[0]*a[4]*b[2]*b[1]+2 * a[0]*a[3]*b[2]^2,
+ -a[3]*a[4]*b[2]*b[1]+a[2]*a[5]*b[1]^2 - a[1]*a[5]*b[2]*b[1]-a[1]*a[3]*b[2]*b[4]+
+ a[1]^2 * b[2]*b[5]-2 * a[2]*a[3]*b[2]*b[3]+2 * a[2]^2 * b[0]*b[5]+2 * a[0]*a[5]*b[2]^2 + a[3]^2 * b[2]^2-
+ 2 * a[2]*a[5]*b[0]*b[2]+2 * a[1]*a[4]*b[2]*b[3]-a[2]*a[4]*b[1]*b[3]-2 * a[0]*a[2]*b[2]*b[5]+
+ a[2]^2 * b[3]^2 + 2 * a[2]*a[3]*b[1]*b[4]-a[2]*a[4]*b[0]*b[4]+a[4]^2 * b[0]*b[2]-a[2]*a[1]*b[3]*b[4]-
+ a[2]*a[1]*b[1]*b[5]-a[0]*a[4]*b[2]*b[4]+a[0]*a[2]*b[4]^2,
+ -a[4]*a[5]*b[2]*b[1]+a[2]*a[3]*b[4]^2 + 2 * a[3]*a[5]*b[2]^2 - a[2]*a[1]*b[4]*b[5]-
+ a[2]*a[4]*b[3]*b[4]+2 * a[2]^2 * b[3]*b[5]-2 * a[2]*a[3]*b[2]*b[5]-a[3]*a[4]*b[2]*b[4]-
+ 2 * a[2]*a[5]*b[2]*b[3]-a[2]*a[4]*b[1]*b[5]+2 * a[1]*a[4]*b[2]*b[5]-a[1]*a[5]*b[2]*b[4]+
+ a[4]^2 * b[2]*b[3]+2 * a[2]*a[5]*b[1]*b[4],
+ -2 * a[2]*a[5]*b[2]*b[5]+a[4]^2 * b[2]*b[5]+a[5]^2 * b[2]^2 - a[4]*a[5]*b[2]*b[4]+a[2]*a[5]*b[4]^2+
+ a[2]^2 * b[5]^2 - a[2]*a[4]*b[4]*b[5]};
+ x = realquarticroots(c[0], c[1], c[2], c[3], c[4]);
+ } else {
+ if(abs(b[4]-a[4]) > e){
+ real D = (b[4]-a[4])^2;
+ c = new real[] {(a[0]*b[4]^2 + (-a[1]*b[3]-2 * a[0]*a[4]+a[1]*a[3]) * b[4]+a[2]*b[3]^2+
+ (a[1]*a[4]-2 * a[2]*a[3]) * b[3]+a[0]*a[4]^2 - a[1]*a[3]*a[4]+a[2]*a[3]^2)/D,
+ -((a[1]*b[4]-2 * a[2]*b[3]-a[1]*a[4]+2 * a[2]*a[3]) * b[5]-a[3]*b[4]^2 + (a[4]*b[3]-a[1]*a[5]+a[3]*a[4]) * b[4]+(2 * a[2]*a[5]-a[4]^2) * b[3]+(a[1]*a[4]-2 * a[2]*a[3]) * a[5])/D,
+ a[2]*(a[5]-b[5])^2/D + a[4]*(a[5]-b[5])/(b[4]-a[4]) + a[5]};
+ x = quadraticroots(c[0], c[1], c[2]);
+ } else {
+ if(abs(a[3]-b[3]) > e) {
+ real D = b[3]-a[3];
+ c = new real[] {a[2], (-a[1]*b[5] + a[4]*b[3] + a[1]*a[5] - a[3]*a[4])/D,
+ a[0]*(a[5]-b[5])^2/D^2 + a[3]*(a[5]-b[5])/D + a[5]};
+ y = quadraticroots(c[0], c[1], c[2]);
+ for (int i = 0; i < y.length; ++i) {
+ c = new real[] {a[0], a[1]*y[i]+a[3], a[2]*y[i]^2 + a[4]*y[i]+a[5]};
+ x = quadraticroots(c[0], c[1], c[2]);
+ for (int j = 0; j < x.length; ++j) {
+ if(abs(b[0]*x[j]^2 + b[1]*x[j]*y[i]+b[2]*y[i]^2 + b[3]*x[j]+b[4]*y[i]+b[5]) < 1e-5)
+ P.push(point(R, (x[j], y[i])));
+ }
+ }
+ return P;
+ } else {
+ if(abs(a[5]-b[5]) < e) abort("intersectionpoints: intersection of identical conics.");
+ }
+ }
+ }
+ for (int i = 0; i < x.length; ++i) {
+ c = new real[] {a[2], a[1]*x[i]+a[4], a[0]*x[i]^2 + a[3]*x[i]+a[5]};
+ y = quadraticroots(c[0], c[1], c[2]);
+ for (int j = 0; j < y.length; ++j) {
+ if(abs(b[0]*x[i]^2 + b[1]*x[i]*y[j]+b[2]*y[j]^2 + b[3]*x[i]+b[4]*y[j]+b[5]) < 1e-5)
+ P.push(point(R, (x[i], y[j])));
+ }
+ }
+ return P;
+}
+
+/*<asyxml><struct signature="conic"><code></asyxml>*/
+struct conic
+{/*<asyxml></code><documentation></documentation><property type = "real" signature="e,p,h"><code></asyxml>*/
+ real e, p, h;/*<asyxml></code><documentation>BE CAREFUL: h = distance(F, D) and p = h * e (http://en.wikipedia.org/wiki/Ellipse)
+ While http://mathworld.wolfram.com/ takes p = distance(F,D).</documentation></property><property type = "point" signature="F"><code></asyxml>*/
+ point F;/*<asyxml></code><documentation>Focus.</documentation></property><property type = "line" signature="D"><code></asyxml>*/
+ line D;/*<asyxml></code><documentation>Directrix.</documentation></property><property type = "line" signature="l"><code></asyxml>*/
+ line[] l;/*<asyxml></code><documentation>Case of degenerated conic (not yet implemented !).</documentation></property></asyxml>*/
+}/*<asyxml></struct></asyxml>*/
+
+bool degenerate(conic c)
+{
+ return !finite(c.p) || !finite(c.h);
+}
+
+/*ANCconic conic(point, line, real)ANC*/
+conic conic(point F, line l, real e)
+{/*DOC
+ The conic section define by the eccentricity 'e', the focus 'F'
+ and the directrix 'l'.
+ Note that an eccentricity equal to 0 defines a circle centered at F,
+ with a radius equal at the distance from 'F' to 'l'.
+ If the coordinate system of 'F' and 'l' are not identical, the conic is
+ attached to 'defaultcoordsys'.
+ DOC*/
+ if(e < 0) abort("conic: 'e' can't be negative.");
+ conic oc;
+ point[] P = standardizecoordsys(F, l.A, l.B);
+ line ll;
+ ll = line(P[1], P[2]);
+ oc.e = e < epsgeo ? 0 : e; // Handle case of circle.
+ oc.F = P[0];
+ oc.D = ll;
+ oc.h = distance(P[0], ll);
+ oc.p = abs(e) < epsgeo ? oc.h : e * oc.h;
+ return oc;
+}
+
+/*<asyxml><struct signature="circle"><code></asyxml>*/
+struct circle
+{/*<asyxml></code><documentation>All the calculus with this structure will be as exact as Asymptote can do.
+ For a full precision, you must not cast 'circle' to 'path' excepted for drawing routines.</documentation></asyxml>*/
+ /*<asyxml><property type = "point" signature="C"><code></asyxml>*/
+ point C;/*<asyxml></code><documentation>Center</documentation></property><property><code></asyxml>*/
+ real r;/*<asyxml></code><documentation>Radius</documentation></property><property><code></asyxml>*/
+ line l;/*<asyxml></code><documentation>If the radius is infinite, this line is used instead of circle.</documentation></property></asyxml>*/
+}/*<asyxml></struct></asyxml>*/
+
+bool degenerate(circle c)
+{
+ return !finite(c.r);
+}
+
+line line(circle c){
+ if(finite(c.r)) abort("Circle can not be casted to line here.");
+ return c.l;
+}
+
+/*<asyxml><struct signature="ellipse"><code></asyxml>*/
+struct ellipse
+{/*<asyxml></code><documentation>Look at <html><a href = "http://mathworld.wolfram.com/Ellipse.html">http://mathworld.wolfram.com/Ellipse.html</a></html></documentation></asyxml>*/
+ /*<asyxml><property type = "point" signature="F1,F2,C"><code></asyxml>*/
+ restricted point F1,F2,C;/*<asyxml></code><documentation>Foci and center.</documentation></property><property type = "real" signature="a,b,c,e,p"><code></asyxml>*/
+ restricted real a,b,c,e,p;/*<asyxml></code></property><property type = "real" signature="angle"><code></asyxml>*/
+ restricted real angle;/*<asyxml></code><documentation>Value is degrees(F1 - F2).</documentation></property><property type = "line" signature="D1,D2"><code></asyxml>*/
+ restricted line D1,D2;/*<asyxml></code><documentation>Directrices.</documentation></property><property type = "line" signature="l"><code></asyxml>*/
+ line l;/*<asyxml></code><documentation>If one axis is infinite, this line is used instead of ellipse.</documentation></property></asyxml>*/
+ /*<asyxml><method type = "void" signature="init(point,point,real)"><code></asyxml>*/
+ void init(point f1, point f2, real a)
+ {/*<asyxml></code><documentation>Ellipse given by foci and semimajor axis</documentation></method></asyxml>*/
+ point[] P = standardizecoordsys(f1, f2);
+ this.F1 = P[0];
+ this.F2 = P[1];
+ this.angle = abs(P[1]-P[0]) < 10 * epsgeo ? 0 : degrees(P[1]-P[0]);
+ this.C = (P[0] + P[1])/2;
+ this.a = a;
+ if(!finite(a)) {
+ this.l = line(P[0], P[1]);
+ this.b = infinity;
+ this.e = 0;
+ this.c = 0;
+ } else {
+ this.c = abs(C - P[0]);
+ this.b = this.c < epsgeo ? a : sqrt(a^2 - c^2); // Handle case of circle.
+ this.e = this.c < epsgeo ? 0 : this.c/a; // Handle case of circle.
+ if(this.e >= 1) abort("ellipse.init: wrong parameter: e >= 1.");
+ this.p = a * (1 - this.e^2);
+ if (this.c != 0) {// directrix is not set for a circle.
+ point A = this.C + (a^2/this.c) * unit(P[0]-this.C);
+ this.D1 = line(A, A + rotateO(90) * unit(A - this.C));
+ this.D2 = reverse(rotate(180, C) * D1);
+ }
+ }
+ }
+}/*<asyxml></struct></asyxml>*/
+
+bool degenerate(ellipse el)
+{
+ return (!finite(el.a) || !finite(el.b));
+}
+
+/*<asyxml><struct signature="parabola"><code></asyxml>*/
+struct parabola
+{/*<asyxml></code><documentation>Look at <html><a href = "http://mathworld.wolfram.com/Parabola.html">http://mathworld.wolfram.com/Parabola.html</a></html></documentation><property type = "point" signature="F,V"><code></asyxml>*/
+ restricted point F,V;/*<asyxml></code><documentation>Focus and vertex</documentation></property><property type = "real" signature="a,p,e = 1"><code></asyxml>*/
+ restricted real a,p,e = 1;/*<asyxml></code></property><property type = "real" signature="angle"><code></asyxml>*/
+ restricted real angle;/*<asyxml></code><documentation>Angle, in degrees, of the line (FV).</documentation></property><property type = "line" signature="D"><code></asyxml>*/
+ restricted line D;/*<asyxml></code><documentation>Directrix</documentation></property><property type = "pair" signature="bmin,bmax"><code></asyxml>*/
+ pair bmin, bmax;/*<asyxml></code><documentation>The (left, bottom) and (right, top) coordinates of region bounding box for drawing the parabola.
+ If unset the current picture bounding box is used instead.</documentation></property></asyxml>*/
+
+ /*<asyxml><method type = "void" signature="init(point,line)"><code></asyxml>*/
+ void init(point F, line directrix)
+ {/*<asyxml></code><documentation>Parabola given by focus and directrix.</documentation></method></asyxml>*/
+ point[] P = standardizecoordsys(F, directrix.A, directrix.B);
+ line l = line(P[1], P[2]);
+ this.F = P[0];
+ this.D = l;
+ this.a = distance(P[0], l)/2;
+ this.p = 2 * a;
+ this.V = 0.5 * (F + projection(D) * P[0]);
+ this.angle = degrees(F - V);
+ }
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><struct signature="hyperbola"><code></asyxml>*/
+struct hyperbola
+{/*<asyxml></code><documentation><html>Look at <a href = "http://mathworld.wolfram.com/Hyperbola.html">http://mathworld.wolfram.com/Hyperbola.html</a></html></documentation><property type = "point" signature="F1,F2"><code></asyxml>*/
+ restricted point F1,F2;/*<asyxml></code><documentation>Foci.</documentation></property><property type = "point" signature="C,V1,V2"><code></asyxml>*/
+ restricted point C,V1,V2;/*<asyxml></code><documentation>Center and vertices.</documentation></property><property type = "real" signature="a,b,c,e,p"><code></asyxml>*/
+ restricted real a,b,c,e,p;/*<asyxml></code><documentation></documentation></property><property type = "real" signature="angle"><code></asyxml>*/
+ restricted real angle;/*<asyxml></code><documentation>Angle,in degrees,of the line (F1F2).</documentation></property><property type = "line" signature="D1,D2,A1,A2"><code></asyxml>*/
+ restricted line D1,D2,A1,A2;/*<asyxml></code><documentation>Directrices and asymptotes.</documentation></property><property type = "pair" signature="bmin,bmax"><code></asyxml>*/
+ pair bmin, bmax; /*<asyxml></code><documentation>The (left, bottom) and (right, top) coordinates of region bounding box for drawing the hyperbola.
+ If unset the current picture bounding box is used instead.</documentation></property></asyxml>*/
+
+ /*<asyxml><method type = "void" signature="init(point,point,real)"><code></asyxml>*/
+ void init(point f1, point f2, real a)
+ {/*<asyxml></code><documentation>Hyperbola given by foci and semimajor axis.</documentation></method></asyxml>*/
+ point[] P = standardizecoordsys(f1, f2);
+ this.F1 = P[0];
+ this.F2 = P[1];
+ this.angle = degrees(F2 - F1);
+ this.a = a;
+ this.C = (P[0] + P[1])/2;
+ this.c = abs(C - P[0]);
+ this.e = this.c/a;
+ if(this.e <= 1) abort("hyperbola.init: wrong parameter: e <= 1.");
+ this.b = a * sqrt(this.e^2 - 1);
+ this.p = a * (this.e^2 - 1);
+ point A = this.C + (a^2/this.c) * unit(P[0]-this.C);
+ this.D1 = line(A, A + rotateO(90) * unit(A - this.C));
+ this.D2 = reverse(rotate(180, C) * D1);
+ this.V1 = C + a * unit(F1 - C);
+ this.V2 = C + a * unit(F2 - C);
+ this.A1 = line(C, V1 + b * unit(rotateO(-90) * (C - V1)));
+ this.A2 = line(C, V1 + b * unit(rotateO(90) * (C - V1)));
+ }
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><variable type="int" signature="conicnodesfactor"><code></asyxml>*/
+int conicnodesfactor = 1;/*<asyxml></code><documentation>Factor for the node number of all conics.</documentation></variable></asyxml>*/
+
+/*<asyxml><variable type="int" signature="circlenodesnumberfactor"><code></asyxml>*/
+int circlenodesnumberfactor = 100;/*<asyxml></code><documentation>Factor for the node number of circles.</documentation></variable></asyxml>*/
+/*<asyxml><function type="int" signature="circlenodesnumber(real)"><code></asyxml>*/
+int circlenodesnumber(real r)
+{/*<asyxml></code><documentation>Return the number of nodes for drawing a circle of radius 'r'.</documentation></function></asyxml>*/
+ if (circlenodesnumberfactor < 100)
+ warning("circlenodesnumberfactor",
+ "variable 'circlenodesnumberfactor' may be too small.");
+ int oi = ceil(circlenodesnumberfactor * abs(r)^0.1);
+ oi = 45 * floor(oi/45);
+ return oi == 0 ? 4 : conicnodesfactor * oi;
+}
+
+/*<asyxml><function type="int" signature="circlenodesnumber(real,real,real)"><code></asyxml>*/
+int circlenodesnumber(real r, real angle1, real angle2)
+{/*<asyxml></code><documentation>Return the number of nodes to draw a circle arc.</documentation></function></asyxml>*/
+ return (r > 0) ?
+ ceil(circlenodesnumber(r) * abs(angle1 - angle2)/360) :
+ ceil(circlenodesnumber(r) * abs((1 - abs(angle1 - angle2)/360)));
+}
+
+/*<asyxml><variable type="int" signature="ellispenodesnumberfactor"><code></asyxml>*/
+int ellipsenodesnumberfactor = 250;/*<asyxml></code><documentation>Factor for the node number of ellispe (non-circle).</documentation></variable></asyxml>*/
+/*<asyxml><function type="int" signature="ellipsenodesnumber(real,real)"><code></asyxml>*/
+int ellipsenodesnumber(real a, real b)
+{/*<asyxml></code><documentation>Return the number of nodes to draw a ellipse of axis 'a' and 'b'.</documentation></function></asyxml>*/
+ if (ellipsenodesnumberfactor < 250)
+ write("ellipsenodesnumberfactor",
+ "variable 'ellipsenodesnumberfactor' maybe too small.");
+ int tmp = circlenodesnumberfactor;
+ circlenodesnumberfactor = ellipsenodesnumberfactor;
+ int oi = circlenodesnumber(max(abs(a), abs(b))/min(abs(a), abs(b)));
+ circlenodesnumberfactor = tmp;
+ return conicnodesfactor * oi;
+}
+
+/*<asyxml><function type="int" signature="ellipsenodesnumber(real,real,real)"><code></asyxml>*/
+int ellipsenodesnumber(real a, real b, real angle1, real angle2, bool dir)
+{/*<asyxml></code><documentation>Return the number of nodes to draw an ellipse arc.</documentation></function></asyxml>*/
+ real d;
+ real da = angle2 - angle1;
+ if(dir) {
+ d = angle1 < angle2 ? da : 360 + da;
+ } else {
+ d = angle1 < angle2 ? -360 + da : da;
+ }
+ int n = floor(ellipsenodesnumber(a, b) * abs(d)/360);
+ return n < 5 ? 5 : n;
+}
+
+/*<asyxml><variable type="int" signature="parabolanodesnumberfactor"><code></asyxml>*/
+int parabolanodesnumberfactor = 100;/*<asyxml></code><documentation>Factor for the number of nodes of parabolas.</documentation></variable></asyxml>*/
+/*<asyxml><function type="int" signature="parabolanodesnumber(parabola,real,real)"><code></asyxml>*/
+int parabolanodesnumber(parabola p, real angle1, real angle2)
+{/*<asyxml></code><documentation>Return the number of nodes for drawing a parabola.</documentation></function></asyxml>*/
+ return conicnodesfactor * floor(0.01 * parabolanodesnumberfactor * abs(angle1 - angle2));
+}
+
+/*<asyxml><variable type="int" signature="hyperbolanodesnumberfactor"><code></asyxml>*/
+int hyperbolanodesnumberfactor = 100;/*<asyxml></code><documentation>Factor for the number of nodes of hyperbolas.</documentation></variable></asyxml>*/
+/*<asyxml><function type="int" signature="hyperbolanodesnumber(hyperbola,real,real)"><code></asyxml>*/
+int hyperbolanodesnumber(hyperbola h, real angle1, real angle2)
+{/*<asyxml></code><documentation>Return the number of nodes for drawing an hyperbola.</documentation></function></asyxml>*/
+ return conicnodesfactor * floor(0.01 * hyperbolanodesnumberfactor * abs(angle1 - angle2)/h.e);
+}
+
+/*<asyxml><operator type = "conic" signature="+(conic,explicit point)"><code></asyxml>*/
+conic operator +(conic c, explicit point M)
+{/*<asyxml></code><documentation></documentation></operator></asyxml>*/
+ return conic(c.F + M, c.D + M, c.e);
+}
+/*<asyxml><operator type = "conic" signature="-(conic,explicit point)"><code></asyxml>*/
+conic operator -(conic c, explicit point M)
+{/*<asyxml></code><documentation></documentation></operator></asyxml>*/
+ return conic(c.F - M, c.D - M, c.e);
+}
+/*<asyxml><operator type = "conic" signature="+(conic,explicit pair)"><code></asyxml>*/
+conic operator +(conic c, explicit pair m)
+{/*<asyxml></code><documentation></documentation></operator></asyxml>*/
+ point M = point(c.F.coordsys, m);
+ return conic(c.F + M, c.D + M, c.e);
+}
+/*<asyxml><operator type = "conic" signature="-(conic,explicit pair)"><code></asyxml>*/
+conic operator -(conic c, explicit pair m)
+{/*<asyxml></code><documentation></documentation></operator></asyxml>*/
+ point M = point(c.F.coordsys, m);
+ return conic(c.F - M, c.D - M, c.e);
+}
+/*<asyxml><operator type = "conic" signature="+(conic,vector)"><code></asyxml>*/
+conic operator +(conic c, vector v)
+{/*<asyxml></code><documentation></documentation></operator></asyxml>*/
+ return conic(c.F + v, c.D + v, c.e);
+}
+/*<asyxml><operator type = "conic" signature="-(conic,vector)"><code></asyxml>*/
+conic operator -(conic c, vector v)
+{/*<asyxml></code><documentation></documentation></operator></asyxml>*/
+ return conic(c.F - v, c.D - v, c.e);
+}
+
+/*<asyxml><function type="coordsys" signature="coordsys(conic)"><code></asyxml>*/
+coordsys coordsys(conic co)
+{/*<asyxml></code><documentation>Return the coordinate system of 'co'.</documentation></function></asyxml>*/
+ return co.F.coordsys;
+}
+
+/*<asyxml><function type="conic" signature="changecoordsys(coordsys,conic)"><code></asyxml>*/
+conic changecoordsys(coordsys R, conic co)
+{/*<asyxml></code><documentation>Change the coordinate system of 'co' to 'R'</documentation></function></asyxml>*/
+ line l = changecoordsys(R, co.D);
+ point F = changecoordsys(R, co.F);
+ return conic(F, l, co.e);
+}
+
+/*<asyxml><typedef type = "polarconicroutine" return = "path" params = "conic, real, real, int, bool"><code></asyxml>*/
+typedef path polarconicroutine(conic co, real angle1, real angle2, int n, bool direction);/*<asyxml></code><documentation>Routine type used to draw conics from 'angle1' to 'angle2'</documentation></typedef></asyxml>*/
+
+/*<asyxml><function type="path" signature="arcfromfocus(conic,real,real,int,bool)"><code></asyxml>*/
+path arcfromfocus(conic co, real angle1, real angle2, int n = 400, bool direction = CCW)
+{/*<asyxml></code><documentation>Return the path of the conic section 'co' from angle1 to angle2 in degrees,
+ drawing in the given direction, with n nodes.</documentation></function></asyxml>*/
+ guide op;
+ if (n < 1) return op;
+ if (angle1 > angle2) {
+ path g = arcfromfocus(co, angle2, angle1, n, !direction);
+ return g == nullpath ? g : reverse(g);
+ }
+ point O = projection(co.D) * co.F;
+ pair i = unit(locate(co.F) - locate(O));
+ pair j = rotate(90) * i;
+ coordsys Rp = cartesiansystem(co.F, i, j);
+ real a1 = direction ? radians(angle1) : radians(angle2);
+ real a2 = direction ? radians(angle2) : radians(angle1) + 2 * pi;
+ real step = n == 1 ? 0 : (a2 - a1)/(n - 1);
+ real a, r;
+ for (int i = 0; i < n; ++i) {
+ a = a1 + i * step;
+ if(co.e >= 1) {
+ r = 1 - co.e * cos(a);
+ if(r > epsgeo) {
+ r = co.p/r;
+ op = op--Rp * Rp.polar(r, a);
+ }
+ } else {
+ r = co.p/(1 - co.e * cos(a));
+ op = op..Rp * Rp.polar(r, a);
+ }
+ }
+ if(co.e < 1 && abs(abs(a2 - a1) - 2 * pi) < epsgeo) op = (path)op..cycle;
+
+ return (direction ? op : op == nullpath ? op :reverse(op));
+}
+
+/*<asyxml><variable type="polarconicroutine" signature="currentpolarconicroutine"><code></asyxml>*/
+polarconicroutine currentpolarconicroutine = arcfromfocus;/*<asyxml></code><documentation>Default routine used to cast conic section to path.</documentation></variable></asyxml>*/
+
+/*<asyxml><function type="point" signature="angpoint(conic,real)"><code></asyxml>*/
+point angpoint(conic co, real angle)
+{/*<asyxml></code><documentation>Return the point of 'co' whose the angular (in degrees)
+ coordinate is 'angle' (mesured from the focus of 'co', relatively
+ to its 'natural coordinate system').</documentation></function></asyxml>*/
+ coordsys R = coordsys(co);
+ return point(R, point(arcfromfocus(co, angle, angle, 1, CCW), 0)/R);
+}
+
+/*<asyxml><operator type = "bool" signature="@(point,conic)"><code></asyxml>*/
+bool operator @(point M, conic co)
+{/*<asyxml></code><documentation>Return true iff 'M' on 'co'.</documentation></operator></asyxml>*/
+ if(co.e == 0) return abs(abs(co.F - M) - co.p) < 10 * epsgeo;
+ return abs(co.e * distance(M, co.D) - abs(co.F - M)) < 10 * epsgeo;
+}
+
+/*<asyxml><function type="coordsys" signature="coordsys(ellipse)"><code></asyxml>*/
+coordsys coordsys(ellipse el)
+{/*<asyxml></code><documentation>Return the coordinate system of 'el'.</documentation></function></asyxml>*/
+ return el.F1.coordsys;
+}
+
+/*<asyxml><function type="coordsys" signature="canonicalcartesiansystem(ellipse)"><code></asyxml>*/
+coordsys canonicalcartesiansystem(ellipse el)
+{/*<asyxml></code><documentation>Return the canonical cartesian system of the ellipse 'el'.</documentation></function></asyxml>*/
+ if(degenerate(el)) return cartesiansystem(el.l.A, el.l.u, el.l.v);
+ pair O = locate(el.C);
+ pair i = el.e == 0 ? el.C.coordsys.i : unit(locate(el.F1) - O);
+ pair j = rotate(90) * i;
+ return cartesiansystem(O, i, j);
+}
+
+/*<asyxml><function type="coordsys" signature="canonicalcartesiansystem(parabola)"><code></asyxml>*/
+coordsys canonicalcartesiansystem(parabola p)
+{/*<asyxml></code><documentation>Return the canonical cartesian system of a parabola,
+ so that Origin = vertex of 'p' and directrix: x = -a.</documentation></function></asyxml>*/
+ point A = projection(p.D) * p.F;
+ pair O = locate((A + p.F)/2);
+ pair i = unit(locate(p.F) - O);
+ pair j = rotate(90) * i;
+ return cartesiansystem(O, i, j);
+}
+
+/*<asyxml><function type="coordsys" signature="canonicalcartesiansystem(hyperbola)"><code></asyxml>*/
+coordsys canonicalcartesiansystem(hyperbola h)
+{/*<asyxml></code><documentation>Return the canonical cartesian system of an hyperbola.</documentation></function></asyxml>*/
+ pair O = locate(h.C);
+ pair i = unit(locate(h.F2) - O);
+ pair j = rotate(90) * i;
+ return cartesiansystem(O, i, j);
+}
+
+/*<asyxml><function type="ellipse" signature="ellipse(point,point,real)"><code></asyxml>*/
+ellipse ellipse(point F1, point F2, real a)
+{/*<asyxml></code><documentation>Return the ellipse whose the foci are 'F1' and 'F2'
+ and the semimajor axis is 'a'.</documentation></function></asyxml>*/
+ ellipse oe;
+ oe.init(F1, F2, a);
+ return oe;
+}
+
+/*<asyxml><constant type = "bool" signature="byfoci,byvertices"><code></asyxml>*/
+restricted bool byfoci = true, byvertices = false;/*<asyxml></code><documentation>Constants useful for the routine 'hyperbola(point P1, point P2, real ae, bool byfoci = byfoci)'</documentation></constant></asyxml>*/
+
+/*<asyxml><function type="hyperbola" signature="hyperbola(point,point,real,bool)"><code></asyxml>*/
+hyperbola hyperbola(point P1, point P2, real ae, bool byfoci = byfoci)
+{/*<asyxml></code><documentation>if 'byfoci = true':
+ return the hyperbola whose the foci are 'P1' and 'P2'
+ and the semimajor axis is 'ae'.
+ else return the hyperbola whose vertexes are 'P1' and 'P2' with eccentricity 'ae'.</documentation></function></asyxml>*/
+ hyperbola oh;
+ point[] P = standardizecoordsys(P1, P2);
+ if(byfoci) {
+ oh.init(P[0], P[1], ae);
+ } else {
+ real a = abs(P[0]-P[1])/2;
+ vector V = unit(P[0]-P[1]);
+ point F1 = P[0] + a * (ae - 1) * V;
+ point F2 = P[1]-a * (ae - 1) * V;
+ oh.init(F1, F2, a);
+ }
+ return oh;
+}
+
+/*<asyxml><function type="ellipse" signature="ellipse(point,point,point)"><code></asyxml>*/
+ellipse ellipse(point F1, point F2, point M)
+{/*<asyxml></code><documentation>Return the ellipse passing through 'M' whose the foci are 'F1' and 'F2'.</documentation></function></asyxml>*/
+ point P[] = standardizecoordsys(false, F1, F2, M);
+ real a = abs(F1 - M) + abs(F2 - M);
+ return ellipse(F1, F2, finite(a) ? a/2 : a);
+}
+
+/*<asyxml><function type="ellipse" signature="ellipse(point,real,real,real)"><code></asyxml>*/
+ellipse ellipse(point C, real a, real b, real angle = 0)
+{/*<asyxml></code><documentation>Return the ellipse centered at 'C' with semimajor axis 'a' along C--C + dir(angle),
+ semiminor axis 'b' along the perpendicular.</documentation></function></asyxml>*/
+ ellipse oe;
+ coordsys R = C.coordsys;
+ angle += degrees(R.i);
+ if(a < b) {angle += 90; real tmp = a; a = b; b = tmp;}
+ if(finite(a) && finite(b)) {
+ real c = sqrt(abs(a^2 - b^2));
+ point f1, f2;
+ if(abs(a - b) < epsgeo) {
+ f1 = C; f2 = C;
+ } else {
+ f1 = point(R, (locate(C) + rotate(angle) * (-c, 0))/R);
+ f2 = point(R, (locate(C) + rotate(angle) * (c, 0))/R);
+ }
+ oe.init(f1, f2, a);
+ } else {
+ if(finite(b) || !finite(a)) oe.init(C, C + R.polar(1, angle), infinity);
+ else oe.init(C, C + R.polar(1, 90 + angle), infinity);
+ }
+ return oe;
+}
+
+/*<asyxml><function type="ellipse" signature="ellipse(bqe)"><code></asyxml>*/
+ellipse ellipse(bqe bqe)
+{/*<asyxml></code><documentation>Return the ellipse a[0] * x^2 + a[1] * xy + a[2] * y^2 + a[3] * x + a[4] * y + a[5] = 0
+ given in the coordinate system of 'bqe' with a[i] = bque.a[i].
+ <url href = "http://mathworld.wolfram.com/QuadraticCurve.html"/>
+ <url href = "http://mathworld.wolfram.com/Ellipse.html"/>.</documentation></function></asyxml>*/
+ bqe lbqe = changecoordsys(defaultcoordsys, bqe);
+ real a = lbqe.a[0], b = lbqe.a[1]/2, c = lbqe.a[2], d = lbqe.a[3]/2, f = lbqe.a[4]/2, g = lbqe.a[5];
+ coordsys R = bqe.coordsys;
+ string message = "ellipse: the given equation is not an equation of an ellipse.";
+ real u = b^2 * g + d^2 * c + f^2 * a;
+ real delta = a * c * g + b * f * d + d * b * f - u;
+ if(abs(delta) < epsgeo) abort(message);
+ real j = b^2 - a * c;
+ real i = a + c;
+ real dd = j * (sgnd(c - a) * sqrt((a - c)^2 + 4 * (b^2)) - c-a);
+ real ddd = j * (-sgnd(c - a) * sqrt((a - c)^2 + 4 * (b^2)) - c-a);
+
+ if(abs(ddd) < epsgeo || abs(dd) < epsgeo ||
+ j >= -epsgeo || delta/sgnd(i) > 0) abort(message);
+
+ real x = (c * d - b * f)/j, y = (a * f - b * d)/j;
+ // real dir = abs(b) < epsgeo ? 0 : pi/2-0.5 * acot(0.5 * (c-a)/b);
+ real dir = abs(b) < epsgeo ? 0 : 0.5 * acot(0.5 * (c - a)/b);
+ if(dir * (c - a) * b < 0) dir = dir < 0 ? dir + pi/2 : dir - pi/2;
+ real cd = cos(dir), sd = sin(dir);
+ real t = a * cd^2 - 2 * b * cd * sd + c * sd^2;
+ real tt = a * sd^2 + 2 * b * cd * sd + c * cd^2;
+ real gg = -g + ((d * cd - f * sd)^2)/t + ((d * sd + f * cd)^2)/tt;
+ t = t/gg; tt = tt/gg;
+ // The equation of the ellipse is t * (x - center.x)^2 + tt * (y - center.y)^2 = 1;
+ real aa, bb;
+ aa = sqrt(2 * (u - 2 * b * d * f - a * c * g)/dd);
+ bb = sqrt(2 * (u - 2 * b * d * f - a * c * g)/ddd);
+ a = t > tt ? max(aa, bb) : min(aa, bb);
+ b = t > tt ? min(aa, bb) : max(aa, bb);
+ return ellipse(point(R, (x, y)/R),
+ a, b, degrees(pi/2 - dir - angle(R.i)));
+}
+
+/*<asyxml><function type="ellipse" signature="ellipse(point,point,point,point,point)"><code></asyxml>*/
+ellipse ellipse(point M1, point M2, point M3, point M4, point M5)
+{/*<asyxml></code><documentation>Return the ellipse passing through the five points (if possible)</documentation></function></asyxml>*/
+ return ellipse(bqe(M1, M2, M3, M4, M5));
+}
+
+/*<asyxml><function type="bool" signature="inside(ellipse,point)"><code></asyxml>*/
+bool inside(ellipse el, point M)
+{/*<asyxml></code><documentation>Return 'true' iff 'M' is inside 'el'.</documentation></function></asyxml>*/
+ return abs(el.F1 - M) + abs(el.F2 - M) - 2 * el.a < -epsgeo;
+}
+
+/*<asyxml><function type="bool" signature="inside(parabola,point)"><code></asyxml>*/
+bool inside(parabola p, point M)
+{/*<asyxml></code><documentation>Return 'true' if 'M' is inside 'p'.</documentation></function></asyxml>*/
+ return distance(p.D, M) - abs(p.F - M) > epsgeo;
+}
+
+/*<asyxml><function type="parabola" signature="parabola(point,line)"><code></asyxml>*/
+parabola parabola(point F, line l)
+{/*<asyxml></code><documentation>Return the parabola whose focus is 'F' and directrix is 'l'.</documentation></function></asyxml>*/
+ parabola op;
+ op.init(F, l);
+ return op;
+}
+
+/*<asyxml><function type="parabola" signature="parabola(point,point)"><code></asyxml>*/
+parabola parabola(point F, point vertex)
+{/*<asyxml></code><documentation>Return the parabola whose focus is 'F' and vertex is 'vertex'.</documentation></function></asyxml>*/
+ parabola op;
+ point[] P = standardizecoordsys(F, vertex);
+ point A = rotate(180, P[1]) * P[0];
+ point B = A + rotateO(90) * unit(P[1]-A);
+ op.init(P[0], line(A, B));
+ return op;
+}
+
+/*<asyxml><function type="parabola" signature="parabola(point,real,real)"><code></asyxml>*/
+parabola parabola(point F, real a, real angle)
+{/*<asyxml></code><documentation>Return the parabola whose focus is F, latus rectum is 4a and
+ the angle of the axis of symmetry (in the coordinate system of F) is 'angle'.</documentation></function></asyxml>*/
+ parabola op;
+ coordsys R = F.coordsys;
+ point A = F - point(R, R.polar(2a, radians(angle)));
+ point B = A + point(R, R.polar(1, radians(90 + angle)));
+ op.init(F, line(A, B));
+ return op;
+}
+
+/*<asyxml><function type="bool" signature="isparabola(bqe)"><code></asyxml>*/
+bool isparabola(bqe bqe)
+{/*<asyxml></code><documentation>Return true iff 'bqe' is the equation of a parabola.</documentation></function></asyxml>*/
+ bqe lbqe = changecoordsys(defaultcoordsys, bqe);
+ real a = lbqe.a[0], b = lbqe.a[1]/2, c = lbqe.a[2], d = lbqe.a[3]/2, f = lbqe.a[4]/2, g = lbqe.a[5];
+ real delta = a * c * g + b * f * d + d * b * f - (b^2 * g + d^2 * c + f^2 * a);
+ return (abs(delta) > epsgeo && abs(b^2 - a * c) < epsgeo);
+}
+
+/*<asyxml><function type="parabola" signature="parabola(bqe)"><code></asyxml>*/
+parabola parabola(bqe bqe)
+{/*<asyxml></code><documentation>Return the parabola a[0]x^2 + a[1]xy + a[2]y^2 + a[3]x + a[4]y + a[5]] = 0 (a[n] means bqe.a[n]).
+ <url href = "http://mathworld.wolfram.com/QuadraticCurve.html"/>
+ <url href = "http://mathworld.wolfram.com/Parabola.html"/></documentation></function></asyxml>*/
+ bqe lbqe = changecoordsys(defaultcoordsys, bqe);
+ real a = lbqe.a[0], b = lbqe.a[1]/2, c = lbqe.a[2], d = lbqe.a[3]/2, f = lbqe.a[4]/2, g = lbqe.a[5];
+ string message = "parabola: the given equation is not an equation of a parabola.";
+ real delta = a * c * g + b * f * d + d * b * f - (b^2 * g + d^2 * c + f^2 * a);
+ if(abs(delta) < 10 * epsgeo || abs(b^2 - a * c) > 10 * epsgeo) abort(message);
+ real dir = abs(b) < epsgeo ? 0 : 0.5 * acot(0.5 * (c - a)/b);
+ if(dir * (c - a) * b < 0) dir = dir < 0 ? dir + pi/2 : dir - pi/2;
+ real cd = cos(dir), sd = sin(dir);
+ real ap = a * cd^2 - 2 * b * cd * sd + c * sd^2;
+ real cp = a * sd^2 + 2 * b * cd * sd + c * cd^2;
+ real dp = d * cd - f * sd;
+ real fp = d * sd + f * cd;
+ real gp = g;
+ parabola op;
+ coordsys R = bqe.coordsys;
+ // The equation of the parabola is ap * x'^2 + cp * y'^2 + 2dp * x'+2fp * y'+gp = 0
+ if (abs(ap) < epsgeo) {/* directrix parallel to the rotated(dir) y-axis
+ equation: (y-vertex.y)^2 = 4 * a * (x-vertex)
+ */
+ pair pvertex = rotate(degrees(-dir)) * (0.5(-gp + fp^2/cp)/dp, -fp/cp);
+ real a = -0.5 * dp/cp;
+ point vertex = point(R, pvertex/R);
+ point focus = point(R, (pvertex + a * expi(-dir))/R);
+ op = parabola(focus, vertex);
+
+ } else {/* directrix parallel to the rotated(dir) x-axis
+ equation: (x-vertex)^2 = 4 * a * (y-vertex.y)
+ */
+ pair pvertex = rotate(degrees(-dir)) * (-dp/ap, 0.5 * (-gp + dp^2/ap)/fp);
+ real a = -0.5 * fp/ap;
+ point vertex = point(R, pvertex/R);
+ point focus = point(R, (pvertex + a * expi(pi/2 - dir))/R);
+ op = parabola(focus, vertex);
+ }
+ return op;
+}
+
+/*<asyxml><function type="parabola" signature="parabola(point,point,point,line)"><code></asyxml>*/
+parabola parabola(point M1, point M2, point M3, line l)
+{/*<asyxml></code><documentation>Return the parabola passing through the three points with its directix
+ parallel to the line 'l'.</documentation></function></asyxml>*/
+ coordsys R;
+ pair[] pts;
+ if (samecoordsys(M1, M2, M3)) {
+ R = M1.coordsys;
+ } else {
+ R = defaultcoordsys;
+ }
+ real gle = degrees(l);
+ coordsys Rp = cartesiansystem(R.O, rotate(gle) * R.i, rotate(gle) * R.j);
+ pts = new pair[] {coordinates(changecoordsys(Rp, M1)),
+ coordinates(changecoordsys(Rp, M2)),
+ coordinates(changecoordsys(Rp, M3))};
+ real[][] M;
+ real[] x;
+ for (int i = 0; i < 3; ++i) {
+ M[i] = new real[] {pts[i].x, pts[i].y, 1};
+ x[i] = -pts[i].x^2;
+ }
+ real[] coef = solve(M, x);
+ return parabola(changecoordsys(R, bqe(Rp, 1, 0, 0, coef[0], coef[1], coef[2])));
+}
+
+/*<asyxml><function type="parabola" signature="parabola(point,point,point,point,point)"><code></asyxml>*/
+parabola parabola(point M1, point M2, point M3, point M4, point M5)
+{/*<asyxml></code><documentation>Return the parabola passing through the five points.</documentation></function></asyxml>*/
+ return parabola(bqe(M1, M2, M3, M4, M5));
+}
+
+/*<asyxml><function type="hyperbola" signature="hyperbola(point,real,real,real)"><code></asyxml>*/
+hyperbola hyperbola(point C, real a, real b, real angle = 0)
+{/*<asyxml></code><documentation>Return the hyperbola centered at 'C' with semimajor axis 'a' along C--C + dir(angle),
+ semiminor axis 'b' along the perpendicular.</documentation></function></asyxml>*/
+ hyperbola oh;
+ coordsys R = C.coordsys;
+ angle += degrees(R.i);
+ real c = sqrt(a^2 + b^2);
+ point f1 = point(R, (locate(C) + rotate(angle) * (-c, 0))/R);
+ point f2 = point(R, (locate(C) + rotate(angle) * (c, 0))/R);
+ oh.init(f1, f2, a);
+ return oh;
+}
+
+/*<asyxml><function type="hyperbola" signature="hyperbola(bqe)"><code></asyxml>*/
+hyperbola hyperbola(bqe bqe)
+{/*<asyxml></code><documentation>Return the hyperbola a[0]x^2 + a[1]xy + a[2]y^2 + a[3]x + a[4]y + a[5]] = 0 (a[n] means bqe.a[n]).
+ <url href = "http://mathworld.wolfram.com/QuadraticCurve.html"/>
+ <url href = "http://mathworld.wolfram.com/Hyperbola.html"/></documentation></function></asyxml>*/
+ bqe lbqe = changecoordsys(defaultcoordsys, bqe);
+ real a = lbqe.a[0], b = lbqe.a[1]/2, c = lbqe.a[2], d = lbqe.a[3]/2, f = lbqe.a[4]/2, g = lbqe.a[5];
+ string message = "hyperbola: the given equation is not an equation of a hyperbola.";
+ real delta = a * c * g + b * f * d + d * b * f - (b^2 * g + d^2 * c + f^2 * a);
+ if(abs(delta) < 10 * epsgeo || abs(b^2 - a * c) < 0) abort(message);
+ real dir = abs(b) < epsgeo ? 0 : 0.5 * acot(0.5 * (c - a)/b);
+ real cd = cos(dir), sd = sin(dir);
+ real ap = a * cd^2 - 2 * b * cd * sd + c * sd^2;
+ real cp = a * sd^2 + 2 * b * cd * sd + c * cd^2;
+ real dp = d * cd - f * sd;
+ real fp = d * sd + f * cd;
+ real gp = -g + dp^2/ap + fp^2/cp;
+ hyperbola op;
+ coordsys R = bqe.coordsys;
+ real j = b^2 - a * c;
+ point C = point(R, ((c * d - b * f)/j, (a * f - b * d)/j)/R);
+ real aa = gp/ap, bb = gp/cp;
+ real a = sqrt(abs(aa)), b = sqrt(abs(bb));
+ if(aa < 0) {dir -= pi/2; aa = a; a = b; b = aa;}
+ return hyperbola(C, a, b, degrees(-dir - angle(R.i)));
+}
+
+/*<asyxml><function type="hyperbola" signature="hyperbola(point,point,point,point,point)"><code></asyxml>*/
+hyperbola hyperbola(point M1, point M2, point M3, point M4, point M5)
+{/*<asyxml></code><documentation>Return the hyperbola passing through the five points (if possible).</documentation></function></asyxml>*/
+ return hyperbola(bqe(M1, M2, M3, M4, M5));
+}
+
+/*<asyxml><function type="hyperbola" signature="conj(hyperbola)"><code></asyxml>*/
+hyperbola conj(hyperbola h)
+{/*<asyxml></code><documentation>Conjugate.</documentation></function></asyxml>*/
+ return hyperbola(h.C, h.b, h.a, 90 + h.angle);
+}
+
+/*<asyxml><function type="circle" signature="circle(explicit point,real)"><code></asyxml>*/
+circle circle(explicit point C, real r)
+{/*<asyxml></code><documentation>Circle given by center and radius.</documentation></function></asyxml>*/
+ circle oc = new circle;
+ oc.C = C;
+ oc.r = r;
+ if(!finite(r)) oc.l = line(C, C + vector(C.coordsys, (1, 0)));
+ return oc;
+}
+
+/*<asyxml><function type="circle" signature="circle(point,point)"><code></asyxml>*/
+circle circle(point A, point B)
+{/*<asyxml></code><documentation>Return the circle of diameter AB.</documentation></function></asyxml>*/
+ real r;
+ circle oc;
+ real a = abs(A), b = abs(B);
+ if(finite(a) && finite(b)) {
+ oc = circle((A + B)/2, abs(A - B)/2);
+ } else {
+ oc.r = infinity;
+ if(finite(abs(A))) oc.l = line(A, A + unit(B));
+ else {
+ if(finite(abs(B))) oc.l = line(B, B + unit(A));
+ else if(finite(abs(A - B)/2)) oc = circle((A + B)/2, abs(A - B)/2); else
+ oc.l = line(A, B);
+ }
+ }
+ return oc;
+}
+
+/*<asyxml><function type="circle" signature="circle(segment)"><code></asyxml>*/
+circle circle(segment s)
+{/*<asyxml></code><documentation>Return the circle of diameter 's'.</documentation></function></asyxml>*/
+ return circle(s.A, s.B);
+}
+
+/*<asyxml><function type="point" signature="circumcenter(point,point,point)"><code></asyxml>*/
+point circumcenter(point A, point B, point C)
+{/*<asyxml></code><documentation>Return the circumcenter of triangle ABC.</documentation></function></asyxml>*/
+ point[] P = standardizecoordsys(A, B, C);
+ coordsys R = P[0].coordsys;
+ pair a = A, b = B, c = C;
+ pair mAB = (a + b)/2;
+ pair mAC = (a + c)/2;
+ pair pp = extension(mAB, rotate(90, mAB) * a, mAC, rotate(90, mAC) * c);
+ return point(R, pp/R);
+}
+
+/*<asyxml><function type="circle" signature="circle(point,point,point)"><code></asyxml>*/
+circle circle(point A, point B, point C)
+{/*<asyxml></code><documentation>Return the circumcircle of the triangle ABC.</documentation></function></asyxml>*/
+ if(collinear(A - B, A - C)) {
+ circle oc;
+ oc.r = infinity;
+ oc.C = (A + B + C)/3;
+ oc.l = line(oc.C, oc.C == A ? B : A);
+ return oc;
+ }
+ point c = circumcenter(A, B, C);
+ return circle(c, abs(c - A));
+}
+
+/*<asyxml><function type="circle" signature="circumcircle(point,point,point)"><code></asyxml>*/
+circle circumcircle(point A, point B, point C)
+{/*<asyxml></code><documentation>Return the circumcircle of the triangle ABC.</documentation></function></asyxml>*/
+ return circle(A, B, C);
+}
+
+/*<asyxml><operator type = "circle" signature="*(real,explicit circle)"><code></asyxml>*/
+circle operator *(real x, explicit circle c)
+{/*<asyxml></code><documentation>Multiply the radius of 'c'.</documentation></operator></asyxml>*/
+ return finite(c.r) ? circle(c.C, x * c.r) : c;
+}
+circle operator *(int x, explicit circle c)
+{
+ return finite(c.r) ? circle(c.C, x * c.r) : c;
+}
+/*<asyxml><operator type = "circle" signature="/(explicit circle,real)"><code></asyxml>*/
+circle operator /(explicit circle c, real x)
+{/*<asyxml></code><documentation>Divide the radius of 'c'</documentation></operator></asyxml>*/
+ return finite(c.r) ? circle(c.C, c.r/x) : c;
+}
+circle operator /(explicit circle c, int x)
+{
+ return finite(c.r) ? circle(c.C, c.r/x) : c;
+}
+/*<asyxml><operator type = "circle" signature="+(explicit circle,explicit point)"><code></asyxml>*/
+circle operator +(explicit circle c, explicit point M)
+{/*<asyxml></code><documentation>Translation of 'c'.</documentation></operator></asyxml>*/
+ return circle(c.C + M, c.r);
+}
+/*<asyxml><operator type = "circle" signature="-(explicit circle,explicit point)"><code></asyxml>*/
+circle operator -(explicit circle c, explicit point M)
+{/*<asyxml></code><documentation>Translation of 'c'.</documentation></operator></asyxml>*/
+ return circle(c.C - M, c.r);
+}
+/*<asyxml><operator type = "circle" signature="+(explicit circle,pair)"><code></asyxml>*/
+circle operator +(explicit circle c, pair m)
+{/*<asyxml></code><documentation>Translation of 'c'.
+ 'm' represent coordinates in the coordinate system where 'c' is defined.</documentation></operator></asyxml>*/
+ return circle(c.C + m, c.r);
+}
+/*<asyxml><operator type = "circle" signature="-(explicit circle,pair)"><code></asyxml>*/
+circle operator -(explicit circle c, pair m)
+{/*<asyxml></code><documentation>Translation of 'c'.
+ 'm' represent coordinates in the coordinate system where 'c' is defined.</documentation></operator></asyxml>*/
+ return circle(c.C - m, c.r);
+}
+/*<asyxml><operator type = "circle" signature="+(explicit circle,vector)"><code></asyxml>*/
+circle operator +(explicit circle c, vector m)
+{/*<asyxml></code><documentation>Translation of 'c'.</documentation></operator></asyxml>*/
+ return circle(c.C + m, c.r);
+}
+/*<asyxml><operator type = "circle" signature="-(explicit circle,vector)"><code></asyxml>*/
+circle operator -(explicit circle c, vector m)
+{/*<asyxml></code><documentation>Translation of 'c'.</documentation></operator></asyxml>*/
+ return circle(c.C - m, c.r);
+}
+/*<asyxml><operator type = "real" signature="^(point,explicit circle)"><code></asyxml>*/
+real operator ^(point M, explicit circle c)
+{/*<asyxml></code><documentation>The power of 'M' with respect to the circle 'c'</documentation></operator></asyxml>*/
+ return xpart((abs(locate(M) - locate(c.C)), c.r)^2);
+}
+/*<asyxml><operator type = "bool" signature="@(point,explicit circle)"><code></asyxml>*/
+bool operator @(point M, explicit circle c)
+{/*<asyxml></code><documentation>Return true iff 'M' is on the circle 'c'.</documentation></operator></asyxml>*/
+ return finite(c.r) ?
+ abs(abs(locate(M) - locate(c.C)) - abs(c.r)) <= 10 * epsgeo :
+ M @ c.l;
+}
+
+/*<asyxml><operator type = "ellipse" signature="cast(circle)"><code></asyxml>*/
+ellipse operator cast(circle c)
+{/*<asyxml></code><documentation></documentation></operator></asyxml>*/
+ return finite(c.r) ? ellipse(c.C, c.r, c.r, 0) : ellipse(c.l.A, c.l.B, infinity);
+}
+
+/*<asyxml><operator type = "circle" signature="cast(ellipse)"><code></asyxml>*/
+circle operator cast(ellipse el)
+{/*<asyxml></code><documentation></documentation></operator></asyxml>*/
+ circle oc;
+ bool infb = (!finite(el.a) || !finite(el.b));
+ if(!infb && abs(el.a - el.b) > epsgeo)
+ abort("Can not cast ellipse with different axis values to circle");
+ oc = circle(el.C, infb ? infinity : el.a);
+ oc.l = el.l.copy();
+ return oc;
+}
+
+/*<asyxml><operator type = "ellipse" signature="cast(conic)"><code></asyxml>*/
+ellipse operator cast(conic co)
+{/*<asyxml></code><documentation>Cast a conic to an ellipse (can be a circle).</documentation></operator></asyxml>*/
+ if(degenerate(co) && co.e < 1) return ellipse(co.l[0].A, co.l[0].B, infinity);
+ ellipse oe;
+ if(co.e < 1) {
+ real a = co.p/(1 - co.e^2);
+ real c = co.e * a;
+ vector v = co.D.v;
+ if(!sameside(co.D.A + v, co.F, co.D)) v = -v;
+ point f2 = co.F + 2 * c * v;
+ f2 = changecoordsys(co.F.coordsys, f2);
+ oe = a == 0 ? ellipse(co.F, co.p, co.p, 0) : ellipse(co.F, f2, a);
+ } else
+ abort("casting: The conic section is not an ellipse.");
+ return oe;
+}
+
+/*<asyxml><operator type = "parabola" signature="cast(conic)"><code></asyxml>*/
+parabola operator cast(conic co)
+{/*<asyxml></code><documentation>Cast a conic to a parabola.</documentation></operator></asyxml>*/
+ parabola op;
+ if(abs(co.e - 1) > epsgeo) abort("casting: The conic section is not a parabola.");
+ op.init(co.F, co.D);
+ return op;
+}
+
+/*<asyxml><operator type = "conic" signature="cast(parabola)"><code></asyxml>*/
+conic operator cast(parabola p)
+{/*<asyxml></code><documentation>Cast a parabola to a conic section.</documentation></operator></asyxml>*/
+ return conic(p.F, p.D, 1);
+}
+
+/*<asyxml><operator type = "hyperbola" signature="cast(conic)"><code></asyxml>*/
+hyperbola operator cast(conic co)
+{/*<asyxml></code><documentation>Cast a conic section to an hyperbola.</documentation></operator></asyxml>*/
+ hyperbola oh;
+ if(co.e > 1) {
+ real a = co.p/(co.e^2 - 1);
+ real c = co.e * a;
+ vector v = co.D.v;
+ if(sameside(co.D.A + v, co.F, co.D)) v = -v;
+ point f2 = co.F + 2 * c * v;
+ f2 = changecoordsys(co.F.coordsys, f2);
+ oh = hyperbola(co.F, f2, a);
+ } else
+ abort("casting: The conic section is not an hyperbola.");
+ return oh;
+}
+
+/*<asyxml><operator type = "conic" signature="cast(hyperbola)"><code></asyxml>*/
+conic operator cast(hyperbola h)
+{/*<asyxml></code><documentation>Hyperbola to conic section.</documentation></operator></asyxml>*/
+ return conic(h.F1, h.D1, h.e);
+}
+
+/*<asyxml><operator type = "conic" signature="cast(ellipse)"><code></asyxml>*/
+conic operator cast(ellipse el)
+{/*<asyxml></code><documentation>Ellipse to conic section.</documentation></operator></asyxml>*/
+ conic oc;
+ if(abs(el.c) > epsgeo) {
+ real x = el.a^2/el.c;
+ point O = (el.F1 + el.F2)/2;
+ point A = O + x * unit(el.F1 - el.F2);
+ oc = conic(el.F1, perpendicular(A, line(el.F1, el.F2)), el.e);
+ } else {//The ellipse is a circle
+ coordsys R = coordsys(el);
+ point M = el.F1 + point(R, R.polar(el.a, 0));
+ line l = line(rotate(90, M) * el.F1, M);
+ oc = conic(el.F1, l, 0);
+ }
+ if(degenerate(el)) {
+ oc.p = infinity;
+ oc.h = infinity;
+ oc.l = new line[]{el.l};
+ }
+ return oc;
+}
+
+/*<asyxml><operator type = "conic" signature="cast(circle)"><code></asyxml>*/
+conic operator cast(circle c)
+{/*<asyxml></code><documentation>Circle to conic section.</documentation></operator></asyxml>*/
+ return (conic)((ellipse)c);
+}
+
+/*<asyxml><operator type = "circle" signature="cast(conic)"><code></asyxml>*/
+circle operator cast(conic c)
+{/*<asyxml></code><documentation>Conic section to circle.</documentation></operator></asyxml>*/
+ ellipse el = (ellipse)c;
+ circle oc;
+ if(abs(el.a - el.b) < epsgeo) {
+ oc = circle(el.C, el.a);
+ if(degenerate(c)) oc.l = c.l[0];
+ }
+ else abort("Can not cast this conic to a circle");
+ return oc;
+}
+
+/*<asyxml><operator type = "ellipse" signature="*(transform,ellipse)"><code></asyxml>*/
+ellipse operator *(transform t, ellipse el)
+{/*<asyxml></code><documentation>Provide transform * ellipse.</documentation></operator></asyxml>*/
+ if(!degenerate(el)) {
+ point[] ep;
+ for (int i = 0; i < 360; i += 72) {
+ ep.push(t * angpoint(el, i));
+ }
+ ellipse oe = ellipse(ep[0], ep[1], ep[2], ep[3], ep[4]);
+ if(angpoint(oe, 0) != ep[0]) return ellipse(oe.F2, oe.F1, oe.a);
+ return oe;
+ }
+ return ellipse(t * el.l.A, t * el.l.B, infinity);
+}
+
+/*<asyxml><operator type = "parabola" signature="*(transform,parabola)"><code></asyxml>*/
+parabola operator *(transform t, parabola p)
+{/*<asyxml></code><documentation>Provide transform * parabola.</documentation></operator></asyxml>*/
+ point[] P;
+ P.push(t * angpoint(p, 45));
+ P.push(t * angpoint(p, -45));
+ P.push(t * angpoint(p, 180));
+ parabola op = parabola(P[0], P[1], P[2], t * p.D);
+ op.bmin = p.bmin;
+ op.bmax = p.bmax;
+
+ return op;
+}
+
+/*<asyxml><operator type = "ellipse" signature="*(transform,circle)"><code></asyxml>*/
+ellipse operator *(transform t, circle c)
+{/*<asyxml></code><documentation>Provide transform * circle.
+ For example, 'circle C = scale(2) * circle' and 'ellipse E = xscale(2) * circle' are valid
+ but 'circle C = xscale(2) * circle' is invalid.</documentation></operator></asyxml>*/
+ return t * ((ellipse)c);
+}
+
+/*<asyxml><operator type = "hyperbola" signature="*(transform,hyperbola)"><code></asyxml>*/
+hyperbola operator *(transform t, hyperbola h)
+{/*<asyxml></code><documentation>Provide transform * hyperbola.</documentation></operator></asyxml>*/
+ if (t == identity()) {
+ return h;
+ }
+
+ point[] ep;
+ for (int i = 90; i <= 270; i += 45) {
+ ep.push(t * angpoint(h, i));
+ }
+
+ hyperbola oe = hyperbola(ep[0], ep[1], ep[2], ep[3], ep[4]);
+ if(angpoint(oe, 90) != ep[0]) {
+ oe = hyperbola(oe.F2, oe.F1, oe.a);
+ }
+
+ oe.bmin = h.bmin;
+ oe.bmax = h.bmax;
+
+ return oe;
+}
+
+/*<asyxml><operator type = "conic" signature="*(transform,conic)"><code></asyxml>*/
+conic operator *(transform t, conic co)
+{/*<asyxml></code><documentation>Provide transform * conic.</documentation></operator></asyxml>*/
+ if(co.e < 1) return (t * ((ellipse)co));
+ if(co.e == 1) return (t * ((parabola)co));
+ return (t * ((hyperbola)co));
+}
+
+/*<asyxml><operator type = "ellipse" signature="*(real,ellipse)"><code></asyxml>*/
+ellipse operator *(real x, ellipse el)
+{/*<asyxml></code><documentation>Identical but more efficient (rapid) than 'scale(x, el.C) * el'.</documentation></operator></asyxml>*/
+ return degenerate(el) ? el : ellipse(el.C, x * el.a, x * el.b, el.angle);
+}
+
+/*<asyxml><operator type = "ellipse" signature="/(ellipse,real)"><code></asyxml>*/
+ellipse operator /(ellipse el, real x)
+{/*<asyxml></code><documentation>Identical but more efficient (rapid) than 'scale(1/x, el.C) * el'.</documentation></operator></asyxml>*/
+ return degenerate(el) ? el : ellipse(el.C, el.a/x, el.b/x, el.angle);
+}
+
+/*<asyxml><function type="path" signature="arcfromcenter(ellipse,real,real,int,bool)"><code></asyxml>*/
+path arcfromcenter(ellipse el, real angle1, real angle2,
+ bool direction=CCW,
+ int n=ellipsenodesnumber(el.a,el.b,angle1,angle2,direction))
+{/*<asyxml></code><documentation>Return the path of the ellipse 'el' from angle1 to angle2 in degrees,
+ drawing in the given direction, with n nodes.
+ The angles are mesured relatively to the axis (C,x-axis) where C is
+ the center of the ellipse.</documentation></function></asyxml>*/
+ if(degenerate(el)) abort("arcfromcenter: can not convert degenerated ellipse to path.");
+ if (angle1 > angle2)
+ return reverse(arcfromcenter(el, angle2, angle1, !direction, n));
+
+ guide op;
+ coordsys Rp=coordsys(el);
+ if (n < 1) return op;
+
+ interpolate join = operator ..;
+ real stretch = max(el.a/el.b, el.b/el.a);
+
+ if (stretch > 10) {
+ n *= floor(stretch/5);
+ join = operator --;
+ }
+
+ real a1 = direction ? radians(angle1) : radians(angle2);
+ real a2 = direction ? radians(angle2) : radians(angle1) + 2 * pi;
+ real step=(a2 - a1)/(n != 1 ? n-1 : 1);
+ real a, r;
+ real da = radians(el.angle);
+
+ for (int i=0; i < n; ++i) {
+ a = a1 + i * step;
+ r = el.b/sqrt(1 - (el.e * cos(a))^2);
+ op = join(op, Rp*Rp.polar(r, da + a));
+ }
+
+ return shift(el.C.x*Rp.i + el.C.y*Rp.j) * (direction ? op : reverse(op));
+}
+
+/*<asyxml><function type="path" signature="arcfromcenter(hyperbola,real,real,int,bool)"><code></asyxml>*/
+path arcfromcenter(hyperbola h, real angle1, real angle2,
+ int n = hyperbolanodesnumber(h, angle1, angle2),
+ bool direction = CCW)
+{/*<asyxml></code><documentation>Return the path of the hyperbola 'h' from angle1 to angle2 in degrees,
+ drawing in the given direction, with n nodes.
+ The angles are mesured relatively to the axis (C, x-axis) where C is
+ the center of the hyperbola.</documentation></function></asyxml>*/
+ guide op;
+ coordsys Rp = coordsys(h);
+ if (n < 1) return op;
+ if (angle1 > angle2) {
+ path g = reverse(arcfromcenter(h, angle2, angle1, n, !direction));
+ return g == nullpath ? g : reverse(g);
+ }
+ real a1 = direction ? radians(angle1) : radians(angle2);
+ real a2 = direction ? radians(angle2) : radians(angle1) + 2 * pi;
+ real step = (a2 - a1)/(n != 1 ? n - 1 : 1);
+ real a, r;
+ typedef guide interpolate(... guide[]);
+ interpolate join = operator ..;
+ real da = radians(h.angle);
+ for (int i = 0; i < n; ++i) {
+ a = a1 + i * step;
+ r = (h.b * cos(a))^2 - (h.a * sin(a))^2;
+ if(r > epsgeo) {
+ r = sqrt(h.a^2 * h.b^2/r);
+ op = join(op, Rp * Rp.polar(r, a + da));
+ join = operator ..;
+ } else join = operator --;
+ }
+ return shift(h.C.x * Rp.i + h.C.y * Rp.j)*
+ (direction ? op : op == nullpath ? op : reverse(op));
+}
+
+/*<asyxml><function type="path" signature="arcfromcenter(explicit conic,real,real,int,bool)"><code></asyxml>*/
+path arcfromcenter(explicit conic co, real angle1, real angle2,
+ int n, bool direction = CCW)
+{/*<asyxml></code><documentation>Use arcfromcenter(ellipse, ...) or arcfromcenter(hyperbola, ...) depending of
+ the eccentricity of 'co'.</documentation></function></asyxml>*/
+ path g;
+ if(co.e < 1)
+ g = arcfromcenter((ellipse)co, angle1,
+ angle2, direction, n);
+ else if(co.e > 1)
+ g = arcfromcenter((hyperbola)co, angle1,
+ angle2, n, direction);
+ else abort("arcfromcenter: does not exist for a parabola.");
+ return g;
+}
+
+/*<asyxml><constant type = "polarconicroutine" signature="fromCenter"><code></asyxml>*/
+restricted polarconicroutine fromCenter = arcfromcenter;/*<asyxml></code><documentation></documentation></constant></asyxml>*/
+/*<asyxml><constant type = "polarconicroutine" signature="fromFocus"><code></asyxml>*/
+restricted polarconicroutine fromFocus = arcfromfocus;/*<asyxml></code><documentation></documentation></constant></asyxml>*/
+
+/*<asyxml><function type="bqe" signature="equation(ellipse)"><code></asyxml>*/
+bqe equation(ellipse el)
+{/*<asyxml></code><documentation>Return the coefficients of the equation of the ellipse in its coordinate system:
+ bqe.a[0] * x^2 + bqe.a[1] * x * y + bqe.a[2] * y^2 + bqe.a[3] * x + bqe.a[4] * y + bqe.a[5] = 0.
+ One can change the coordinate system of 'bqe' using the routine 'changecoordsys'.</documentation></function></asyxml>*/
+ pair[] pts;
+ for (int i = 0; i < 360; i += 72)
+ pts.push(locate(angpoint(el, i)));
+
+ real[][] M;
+ real[] x;
+ for (int i = 0; i < 5; ++i) {
+ M[i] = new real[] {pts[i].x * pts[i].y, pts[i].y^2, pts[i].x, pts[i].y, 1};
+ x[i] = -pts[i].x^2;
+ }
+ real[] coef = solve(M, x);
+ bqe bqe = changecoordsys(coordsys(el),
+ bqe(defaultcoordsys,
+ 1, coef[0], coef[1], coef[2], coef[3], coef[4]));
+ bqe.a = approximate(bqe.a);
+ return bqe;
+}
+
+/*<asyxml><function type="bqe" signature="equation(parabola)"><code></asyxml>*/
+bqe equation(parabola p)
+{/*<asyxml></code><documentation>Return the coefficients of the equation of the parabola in its coordinate system.
+ bqe.a[0] * x^2 + bqe.a[1] * x * y + bqe.a[2] * y^2 + bqe.a[3] * x + bqe.a[4] * y + bqe.a[5] = 0
+ One can change the coordinate system of 'bqe' using the routine 'changecoordsys'.</documentation></function></asyxml>*/
+ coordsys R = canonicalcartesiansystem(p);
+ parabola tp = changecoordsys(R, p);
+ point A = projection(tp.D) * point(R, (0, 0));
+ real a = abs(A);
+ return changecoordsys(coordsys(p),
+ bqe(R, 0, 0, 1, -4 * a, 0, 0));
+}
+
+/*<asyxml><function type="bqe" signature="equation(hyperbola)"><code></asyxml>*/
+bqe equation(hyperbola h)
+{/*<asyxml></code><documentation>Return the coefficients of the equation of the hyperbola in its coordinate system.
+ bqe.a[0] * x^2 + bqe.a[1] * x * y + bqe.a[2] * y^2 + bqe.a[3] * x + bqe.a[4] * y + bqe.a[5] = 0
+ One can change the coordinate system of 'bqe' using the routine 'changecoordsys'.</documentation></function></asyxml>*/
+ coordsys R = canonicalcartesiansystem(h);
+ return changecoordsys(coordsys(h),
+ bqe(R, 1/h.a^2, 0, -1/h.b^2, 0, 0, -1));
+}
+
+/*<asyxml><operator type = "path" signature="cast(ellipse)"><code></asyxml>*/
+path operator cast(ellipse el)
+{/*<asyxml></code><documentation>Cast ellipse to path.</documentation></operator></asyxml>*/
+ if(degenerate(el))
+ abort("Casting degenerated ellipse to path is not possible.");
+ int n = el.e == 0 ? circlenodesnumber(el.a) : ellipsenodesnumber(el.a, el.b);
+ return arcfromcenter(el, 0.0, 360, CCW, n)&cycle;
+}
+
+/*<asyxml><operator type = "path" signature="cast(circle)"><code></asyxml>*/
+path operator cast(circle c)
+{/*<asyxml></code><documentation>Cast circle to path.</documentation></operator></asyxml>*/
+ return (path)((ellipse)c);
+}
+
+/*<asyxml><function type="real[]" signature="bangles(picture,parabola)"><code></asyxml>*/
+real[] bangles(picture pic = currentpicture, parabola p)
+{/*<asyxml></code><documentation>Return the array {ma, Ma} where 'ma' and 'Ma' are respectively
+ the smaller and the larger angles for which the parabola 'p' is included
+ in the bounding box of the picture 'pic'.</documentation></function></asyxml>*/
+ pair bmin, bmax;
+ pair[] b;
+ if (p.bmin == p.bmax) {
+ bmin = pic.userMin();
+ bmax = pic.userMax();
+ } else {
+ bmin = p.bmin;bmax = p.bmax;
+ }
+ if(bmin.x == bmax.x || bmin.y == bmax.y || !finite(abs(bmin)) || !finite(abs(bmax)))
+ return new real[] {0, 0};
+ b[0] = bmin;
+ b[1] = (bmax.x, bmin.y);
+ b[2] = bmax;
+ b[3] = (bmin.x, bmax.y);
+ real[] eq = changecoordsys(defaultcoordsys, equation(p)).a;
+ pair[] inter;
+ for (int i = 0; i < 4; ++i) {
+ pair[] tmp = intersectionpoints(b[i], b[(i + 1)%4], eq);
+ for (int j = 0; j < tmp.length; ++j) {
+ if(dot(b[i]-tmp[j], b[(i + 1)%4]-tmp[j]) <= epsgeo)
+ inter.push(tmp[j]);
+ }
+ }
+ pair F = p.F, V = p.V;
+ real d = degrees(F - V);
+ real[] a = sequence(new real(int n){
+ return (360 - d + degrees(inter[n]-F))%360;
+ }, inter.length);
+ real ma = a.length != 0 ? min(a) : 0, Ma= a.length != 0 ? max(a) : 0;
+ return new real[] {ma, Ma};
+}
+
+/*<asyxml><function type="real[][]" signature="bangles(picture,hyperbola)"><code></asyxml>*/
+real[][] bangles(picture pic = currentpicture, hyperbola h)
+{/*<asyxml></code><documentation>Return the array {{ma1, Ma1}, {ma2, Ma2}} where 'maX' and 'MaX' are respectively
+ the smaller and the bigger angles (from h.FX) for which the hyperbola 'h' is included
+ in the bounding box of the picture 'pic'.</documentation></function></asyxml>*/
+ pair bmin, bmax;
+ pair[] b;
+ if (h.bmin == h.bmax) {
+ bmin = pic.userMin();
+ bmax = pic.userMax();
+ } else {
+ bmin = h.bmin;bmax = h.bmax;
+ }
+ if(bmin.x == bmax.x || bmin.y == bmax.y || !finite(abs(bmin)) || !finite(abs(bmax)))
+ return new real[][] {{0, 0}, {0, 0}};
+ b[0] = bmin;
+ b[1] = (bmax.x, bmin.y);
+ b[2] = bmax;
+ b[3] = (bmin.x, bmax.y);
+ real[] eq = changecoordsys(defaultcoordsys, equation(h)).a;
+ pair[] inter0, inter1;
+ pair C = locate(h.C);
+ pair F1 = h.F1;
+ for (int i = 0; i < 4; ++i) {
+ pair[] tmp = intersectionpoints(b[i], b[(i + 1)%4], eq);
+ for (int j = 0; j < tmp.length; ++j) {
+ if(dot(b[i]-tmp[j], b[(i + 1)%4]-tmp[j]) <= epsgeo) {
+ if(dot(F1 - C, tmp[j]-C) > 0) inter0.push(tmp[j]);
+ else inter1.push(tmp[j]);
+ }
+ }
+ }
+ real d = degrees(F1 - C);
+ real[] ma, Ma;
+ pair[][] inter = new pair[][] {inter0, inter1};
+ for (int i = 0; i < 2; ++i) {
+ real[] a = sequence(new real(int n){
+ return (360 - d + degrees(inter[i][n]-F1))%360;
+ }, inter[i].length);
+ ma[i] = a.length != 0 ? min(a) : 0;
+ Ma[i] = a.length != 0 ? max(a) : 0;
+ }
+ return new real[][] {{ma[0], Ma[0]}, {ma[1], Ma[1]}};
+}
+
+/*<asyxml><operator type = "path" signature="cast(parabola)"><code></asyxml>*/
+path operator cast(parabola p)
+{/*<asyxml></code><documentation>Cast parabola to path.
+ If possible, the returned path is restricted to the actual bounding box
+ of the current picture if the variables 'p.bmin' and 'p.bmax' are not set else
+ the bounding box of box(p.bmin, p.bmax) is used instead.</documentation></operator></asyxml>*/
+ real[] bangles = bangles(p);
+ int n = parabolanodesnumber(p, bangles[0], bangles[1]);
+ return arcfromfocus(p, bangles[0], bangles[1], n, CCW);
+}
+
+
+/*<asyxml><function type="void" signature="draw(picture,Label,circle,align,pen,arrowbar,arrowbar,margin,Label,marker)"><code></asyxml>*/
+void draw(picture pic = currentpicture, Label L = "", circle c,
+ align align = NoAlign, pen p = currentpen,
+ arrowbar arrow = None, arrowbar bar = None,
+ margin margin = NoMargin, Label legend = "", marker marker = nomarker)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ if(degenerate(c)) draw(pic, L, c.l, align, p, arrow, legend, marker);
+ else draw(pic, L, (path)c, align, p, arrow, bar, margin, legend, marker);
+}
+
+/*<asyxml><function type="void" signature="draw(picture,Label,ellipse,align,pen,arrowbar,arrowbar,margin,Label,marker)"><code></asyxml>*/
+void draw(picture pic = currentpicture, Label L = "", ellipse el,
+ align align = NoAlign, pen p = currentpen,
+ arrowbar arrow = None, arrowbar bar = None,
+ margin margin = NoMargin, Label legend = "", marker marker = nomarker)
+{/*<asyxml></code><documentation></documentation>Draw the ellipse 'el' if it is not degenerated else draw 'el.l'.</function></asyxml>*/
+ if(degenerate(el)) draw(pic, L, el.l, align, p, arrow, legend, marker);
+ else draw(pic, L, (path)el, align, p, arrow, bar, margin, legend, marker);
+}
+
+/*<asyxml><function type="void" signature="draw(picture,Label,parabola,align,pen,arrowbar,arrowbar,margin,Label,marker)"><code></asyxml>*/
+void draw(picture pic = currentpicture, Label L = "", parabola parabola,
+ align align = NoAlign, pen p = currentpen,
+ arrowbar arrow = None, arrowbar bar = None,
+ margin margin = NoMargin, Label legend = "", marker marker = nomarker)
+{/*<asyxml></code><documentation>Draw the parabola 'p' on 'pic' without (if possible) altering the
+ size of picture pic.</documentation></function></asyxml>*/
+ pic.add(new void (frame f, transform t, transform T, pair m, pair M) {
+ // Reduce the bounds by the size of the pen and the margins.
+ m -= min(p); M -= max(p);
+ parabola.bmin = inverse(t) * m;
+ parabola.bmax = inverse(t) * M;
+ picture tmp;
+ path pp = t * ((path) (T * parabola));
+
+ if (pp != nullpath) {
+ draw(tmp, L, pp, align, p, arrow, bar, NoMargin, legend, marker);
+ add(f, tmp.fit());
+ }
+ }, true);
+
+ pair m = pic.userMin(), M = pic.userMax();
+ if(m != M) {
+ pic.addBox(truepoint(SW), truepoint(NE));
+ }
+}
+
+/*<asyxml><operator type = "path" signature="cast(hyperbola)"><code></asyxml>*/
+path operator cast(hyperbola h)
+{/*<asyxml></code><documentation>Cast hyperbola to path.
+ If possible, the returned path is restricted to the actual bounding box
+ of the current picture unless the variables 'h.bmin' and 'h.bmax'
+ are set; in this case the bounding box of box(h.bmin, h.bmax) is used instead.
+ Only the branch on the side of 'h.F1' is considered.</documentation></operator></asyxml>*/
+ real[][] bangles = bangles(h);
+ int n = hyperbolanodesnumber(h, bangles[0][0], bangles[0][1]);
+ return arcfromfocus(h, bangles[0][0], bangles[0][1], n, CCW);
+}
+
+/*<asyxml><function type="void" signature="draw(picture,Label,hyperbola,align,pen,arrowbar,arrowbar,margin,Label,marker)"><code></asyxml>*/
+void draw(picture pic = currentpicture, Label L = "", hyperbola h,
+ align align = NoAlign, pen p = currentpen,
+ arrowbar arrow = None, arrowbar bar = None,
+ margin margin = NoMargin, Label legend = "", marker marker = nomarker)
+{/*<asyxml></code><documentation>Draw the hyperbola 'h' on 'pic' without (if possible) altering the
+ size of the picture pic.</documentation></function></asyxml>*/
+ pic.add(new void (frame f, transform t, transform T, pair m, pair M) {
+ // Reduce the bounds by the size of the pen and the margins.
+ m -= min(p); M -= max(p);
+ h.bmin = inverse(t) * m;
+ h.bmax = inverse(t) * M;
+ path hp;
+
+ picture tmp;
+ hp = t * ((path) (T * h));
+ if (hp != nullpath) {
+ draw(tmp, L, hp, align, p, arrow, bar, NoMargin, legend, marker);
+ }
+
+ hyperbola ht = hyperbola(h.F2, h.F1, h.a);
+ ht.bmin = h.bmin;
+ ht.bmax = h.bmax;
+
+ hp = t * ((path) (T * ht));
+ if (hp != nullpath) {
+ draw(tmp, "", hp, align, p, arrow, bar, NoMargin, marker);
+ }
+
+ add(f, tmp.fit());
+ }, true);
+
+ pair m = pic.userMin(), M = pic.userMax();
+ if(m != M)
+ pic.addBox(truepoint(SW), truepoint(NE));
+}
+
+/*<asyxml><function type="void" signature="draw(picture,Label,explicit conic,align,pen,arrowbar,arrowbar,margin,Label,marker)"><code></asyxml>*/
+void draw(picture pic = currentpicture, Label L = "", explicit conic co,
+ align align = NoAlign, pen p = currentpen,
+ arrowbar arrow = None, arrowbar bar = None,
+ margin margin = NoMargin, Label legend = "", marker marker = nomarker)
+{/*<asyxml></code><documentation>Use one of the routine 'draw(ellipse, ...)',
+ 'draw(parabola, ...)' or 'draw(hyperbola, ...)' depending of the value of eccentricity of 'co'.</documentation></function></asyxml>*/
+ if(co.e == 0)
+ draw(pic, L, (circle)co, align, p, arrow, bar, margin, legend, marker);
+ else
+ if(co.e < 1) draw(pic, L, (ellipse)co, align, p, arrow, bar, margin, legend, marker);
+ else
+ if(co.e == 1) draw(pic, L, (parabola)co, align, p, arrow, bar, margin, legend, marker);
+ else
+ if(co.e > 1) draw(pic, L, (hyperbola)co, align, p, arrow, bar, margin, legend, marker);
+ else abort("draw: unknown conic.");
+}
+
+/*<asyxml><function type="int" signature="conicnodesnumber(conic,real,real)"><code></asyxml>*/
+int conicnodesnumber(conic co, real angle1, real angle2, bool dir = CCW)
+{/*<asyxml></code><documentation>Return the number of node to draw a conic arc.</documentation></function></asyxml>*/
+ int oi;
+ if(co.e == 0) {
+ circle c = (circle)co;
+ oi = circlenodesnumber(c.r, angle1, angle2);
+ } else if(co.e < 1) {
+ ellipse el = (ellipse)co;
+ oi = ellipsenodesnumber(el.a, el.b, angle1, angle2, dir);
+ } else if(co.e == 1) {
+ parabola p = (parabola)co;
+ oi = parabolanodesnumber(p, angle1, angle2);
+ } else {
+ hyperbola h = (hyperbola)co;
+ oi = hyperbolanodesnumber(h, angle1, angle2);
+ }
+ return oi;
+}
+
+/*<asyxml><operator type = "path" signature="cast(conic)"><code></asyxml>*/
+path operator cast(conic co)
+{/*<asyxml></code><documentation>Cast conic section to path.</documentation></operator></asyxml>*/
+ if(co.e < 1) return (path)((ellipse)co);
+ if(co.e == 1) return (path)((parabola)co);
+ return (path)((hyperbola)co);
+}
+
+/*<asyxml><function type="bqe" signature="equation(explicit conic)"><code></asyxml>*/
+bqe equation(explicit conic co)
+{/*<asyxml></code><documentation>Return the coefficients of the equation of conic section in its coordinate system:
+ bqe.a[0] * x^2 + bqe.a[1] * x * y + bqe.a[2] * y^2 + bqe.a[3] * x + bqe.a[4] * y + bqe.a[5] = 0.
+ One can change the coordinate system of 'bqe' using the routine 'changecoordsys'.</documentation></function></asyxml>*/
+ bqe obqe;
+ if(co.e == 0)
+ obqe = equation((circle)co);
+ else
+ if(co.e < 1) obqe = equation((ellipse)co);
+ else
+ if(co.e == 1) obqe = equation((parabola)co);
+ else
+ if(co.e > 1) obqe = equation((hyperbola)co);
+ else abort("draw: unknown conic.");
+ return obqe;
+}
+
+/*<asyxml><function type="string" signature="conictype(bqe)"><code></asyxml>*/
+string conictype(bqe bqe)
+{/*<asyxml></code><documentation>Returned values are "ellipse" or "parabola" or "hyperbola"
+ depending of the conic section represented by 'bqe'.</documentation></function></asyxml>*/
+ bqe lbqe = changecoordsys(defaultcoordsys, bqe);
+ string os = "degenerated";
+ real a = lbqe.a[0], b = lbqe.a[1]/2, c = lbqe.a[2], d = lbqe.a[3]/2, f = lbqe.a[4]/2, g = lbqe.a[5];
+ real delta = a * c * g + b * f * d + d * b * f - (b^2 * g + d^2 * c + f^2 * a);
+ if(abs(delta) < 10 * epsgeo) return os;
+ real J = a * c - b^2;
+ real I = a + c;
+ if(J > epsgeo) {
+ if(delta/I < -epsgeo);
+ os = "ellipse";
+ } else {
+ if(abs(J) < epsgeo) os = "parabola"; else os = "hyperbola";
+ }
+ return os;
+}
+
+/*<asyxml><function type="conic" signature="conic(point,point,point,point,point)"><code></asyxml>*/
+conic conic(point M1, point M2, point M3, point M4, point M5)
+{/*<asyxml></code><documentation>Return the conic passing through 'M1', 'M2', 'M3', 'M4' and 'M5' if the conic is not degenerated.</documentation></function></asyxml>*/
+ bqe bqe = bqe(M1, M2, M3, M4, M5);
+ string ct = conictype(bqe);
+ if(ct == "degenerated") abort("conic: degenerated conic passing through five points.");
+ if(ct == "ellipse") return ellipse(bqe);
+ if(ct == "parabola") return parabola(bqe);
+ return hyperbola(bqe);
+}
+
+/*<asyxml><function type="coordsys" signature="canonicalcartesiansystem(hyperbola)"><code></asyxml>*/
+coordsys canonicalcartesiansystem(explicit conic co)
+{/*<asyxml></code><documentation>Return the canonical cartesian system of the conic 'co'.</documentation></function></asyxml>*/
+ if(co.e < 1) return canonicalcartesiansystem((ellipse)co);
+ else if(co.e == 1) return canonicalcartesiansystem((parabola)co);
+ return canonicalcartesiansystem((hyperbola)co);
+}
+
+/*<asyxml><function type="bqe" signature="canonical(bqe)"><code></asyxml>*/
+bqe canonical(bqe bqe)
+{/*<asyxml></code><documentation>Return the bivariate quadratic equation relative to the
+ canonical coordinate system of the conic section represented by 'bqe'.</documentation></function></asyxml>*/
+ string type = conictype(bqe);
+ if(type == "") abort("canonical: the equation can not be performed.");
+ bqe obqe;
+ if(type == "ellipse") {
+ ellipse el = ellipse(bqe);
+ obqe = changecoordsys(canonicalcartesiansystem(el), equation(el));
+ } else {
+ if(type == "parabola") {
+ parabola p = parabola(bqe);
+ obqe = changecoordsys(canonicalcartesiansystem(p), equation(p));
+ } else {
+ hyperbola h = hyperbola(bqe);
+ obqe = changecoordsys(canonicalcartesiansystem(h), equation(h));
+ }
+ }
+ return obqe;
+}
+
+/*<asyxml><function type="conic" signature="conic(bqe)"><code></asyxml>*/
+conic conic(bqe bqe)
+{/*<asyxml></code><documentation>Return the conic section represented by the bivariate quartic equation 'bqe'.</documentation></function></asyxml>*/
+ string type = conictype(bqe);
+ if(type == "") abort("canonical: the equation can not be performed.");
+ conic oc;
+ if(type == "ellipse") {
+ oc = ellipse(bqe);
+ } else {
+ if(type == "parabola") oc = parabola(bqe); else oc = hyperbola(bqe);
+ }
+ return oc;
+}
+
+/*<asyxml><function type="real" signature="arclength(circle)"><code></asyxml>*/
+real arclength(circle c)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return c.r * 2 * pi;
+}
+
+/*<asyxml><function type="real" signature="focusToCenter(ellipse,real)"><code></asyxml>*/
+real focusToCenter(ellipse el, real a)
+{/*<asyxml></code><documentation>Return the angle relatively to the center of 'el' for the angle 'a'
+ given relatively to the focus of 'el'.</documentation></function></asyxml>*/
+ pair p = point(fromFocus(el, a, a, 1, CCW), 0);
+ pair c = locate(el.C);
+ real d = degrees(p - c) - el.angle;
+ d = abs(d) < epsgeo ? 0 : d; // Avoid -1e-15
+ return d%(sgnd(a) * 360);
+}
+
+/*<asyxml><function type="real" signature="centerToFocus(ellipse,real)"><code></asyxml>*/
+real centerToFocus(ellipse el, real a)
+{/*<asyxml></code><documentation>Return the angle relatively to the focus of 'el' for the angle 'a'
+ given relatively to the center of 'el'.</documentation></function></asyxml>*/
+ pair P = point(fromCenter(el, a, a, 1, CCW), 0);
+ pair F1 = locate(el.F1);
+ pair F2 = locate(el.F2);
+ real d = degrees(P - F1) - degrees(F2 - F1);
+ d = abs(d) < epsgeo ? 0 : d; // Avoid -1e-15
+ return d%(sgnd(a) * 360);
+}
+
+/*<asyxml><function type="real" signature="arclength(ellipse)"><code></asyxml>*/
+real arclength(ellipse el)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return degenerate(el) ? infinity : 4 * el.a * elle(pi/2, el.e);
+}
+
+/*<asyxml><function type="real" signature="arclength(ellipse,real,real,bool,polarconicroutine)"><code></asyxml>*/
+real arclength(ellipse el, real angle1, real angle2,
+ bool direction = CCW,
+ polarconicroutine polarconicroutine = currentpolarconicroutine)
+{/*<asyxml></code><documentation>Return the length of the arc of the ellipse between 'angle1'
+ and 'angle2'.
+ 'angle1' and 'angle2' must be in the interval ]-360;+oo[ if polarconicroutine = fromFocus,
+ ]-oo;+oo[ if polarconicroutine = fromCenter.</documentation></function></asyxml>*/
+ if(degenerate(el)) return infinity;
+ if(angle1 > angle2) return arclength(el, angle2, angle1, !direction, polarconicroutine);
+ // path g;int n = 1000;
+ // if(el.e == 0) g = arcfromcenter(el, angle1, angle2, n, direction);
+ // if(el.e != 1) g = polarconicroutine(el, angle1, angle2, n, direction);
+ // write("with path = ", arclength(g));
+ if(polarconicroutine == fromFocus) {
+ // dot(point(fromFocus(el, angle1, angle1, 1, CCW), 0), 2mm + blue);
+ // dot(point(fromFocus(el, angle2, angle2, 1, CCW), 0), 2mm + blue);
+ // write("fromfocus1 = ", angle1);
+ // write("fromfocus2 = ", angle2);
+ real gle1 = focusToCenter(el, angle1);
+ real gle2 = focusToCenter(el, angle2);
+ if((gle1 - gle2) * (angle1 - angle2) > 0) {
+ angle1 = gle1; angle2 = gle2;
+ } else {
+ angle1 = gle2; angle2 = gle1;
+ }
+ // dot(point(fromCenter(el, angle1, angle1, 1, CCW), 0), 1mm + red);
+ // dot(point(fromCenter(el, angle2, angle2, 1, CCW), 0), 1mm + red);
+ // write("fromcenter1 = ", angle1);
+ // write("fromcenter2 = ", angle2);
+ }
+ if(angle1 < 0 || angle2 < 0) return arclength(el, 180 + angle1, 180 + angle2, direction, fromCenter);
+ real a1 = direction ? angle1 : angle2;
+ real a2 = direction ? angle2 : angle1 + 360;
+ real elleq = el.a * elle(pi/2, el.e);
+ real S(real a)
+ {//Return the arclength from 0 to the angle 'a' (in degrees)
+ // given form the center of the ellipse.
+ real gle = atan(el.a * tan(radians(a))/el.b)+
+ pi * (((a%90 == 0 && a != 0) ? floor(a/90) - 1 : floor(a/90)) -
+ ((a%180 == 0) ? 0 : floor(a/180)) -
+ (a%360 == 0 ? floor(a/(360)) : 0));
+ /* // Uncomment to visualize the used branches
+ unitsize(2cm, 1cm);
+ import graph;
+
+ real xmin = 0, xmax = 3pi;
+
+ xlimits( xmin, xmax);
+ ylimits( 0, 10);
+ yaxis( "y" , LeftRight(), RightTicks(pTick=.8red, ptick = lightgrey, extend = true));
+ xaxis( "x - value", BottomTop(), Ticks(Label("$%.2f$", red), Step = pi/2, step = pi/4, pTick=.8red, ptick = lightgrey, extend = true));
+
+ real p2 = pi/2;
+ real f(real t)
+ {
+ return atan(0.6 * tan(t))+
+ pi * ((t%p2 == 0 && t != 0) ? floor(t/p2) - 1 : floor(t/p2)) -
+ ((t%pi == 0) ? 0 : pi * floor(t/pi)) - (t%(2pi) == 0 ? pi * floor(t/(2 * pi)) : 0);
+ }
+
+ draw(graph(f, xmin, xmax, 100));
+ write(degrees(f(pi/2)));
+ write(degrees(f(pi)));
+ write(degrees(f(3pi/2)));
+ write(degrees(f(2pi)));
+ draw(graph(new real(real t){return t;}, xmin, xmax, 3));
+ */
+ return elleq - el.a * elle(pi/2 - gle, el.e);
+ }
+ return S(a2) - S(a1);
+}
+
+/*<asyxml><function type="real" signature="arclength(parabola,real)"><code></asyxml>*/
+real arclength(parabola p, real angle)
+{/*<asyxml></code><documentation>Return the arclength from 180 to 'angle' given from focus in the
+ canonical coordinate system of 'p'.</documentation></function></asyxml>*/
+ real a = p.a; /* In canonicalcartesiansystem(p) the equation of p
+ is x = y^2/(4a) */
+ // integrate(sqrt(1 + (x/(2 * a))^2), x);
+ real S(real t){return 0.5 * t * sqrt(1 + t^2/(4 * a^2)) + a * asinh(t/(2 * a));}
+ real R(real gle){return 2 * a/(1 - Cos(gle));}
+ real t = Sin(angle) * R(angle);
+ return S(t);
+}
+
+/*<asyxml><function type="real" signature="arclength(parabola,real,real)"><code></asyxml>*/
+real arclength(parabola p, real angle1, real angle2)
+{/*<asyxml></code><documentation>Return the arclength from 'angle1' to 'angle2' given from
+ focus in the canonical coordinate system of 'p'</documentation></function></asyxml>*/
+ return arclength(p, angle1) - arclength(p, angle2);
+}
+
+/*<asyxml><function type="real" signature="arclength(parabola p)"><code></asyxml>*/
+real arclength(parabola p)
+{/*<asyxml></code><documentation>Return the length of the arc of the parabola bounded to the bounding
+ box of the current picture.</documentation></function></asyxml>*/
+ real[] b = bangles(p);
+ return arclength(p, b[0], b[1]);
+}
+// *........................CONICS.........................*
+// *=======================================================*
+
+// *=======================================================*
+// *.......................ABSCISSA........................*
+/*<asyxml><struct signature="abscissa"><code></asyxml>*/
+struct abscissa
+{/*<asyxml></code><documentation>Provide abscissa structure on a curve used in the routine-like 'point(object, abscissa)'
+ where object can be 'line','segment','ellipse','circle','conic'...</documentation><property type = "real" signature="x"><code></asyxml>*/
+ real x;/*<asyxml></code><documentation>The abscissa value.</documentation></property><property type = "int" signature="system"><code></asyxml>*/
+ int system;/*<asyxml></code><documentation>0 = relativesystem; 1 = curvilinearsystem; 2 = angularsystem; 3 = nodesystem</documentation></property><property type = "polarconicroutine" signature="polarconicroutine"><code></asyxml>*/
+ polarconicroutine polarconicroutine = fromCenter;/*<asyxml></code><documentation>The routine used with angular system and two foci conic section.
+ Possible values are 'formCenter' and 'formFocus'.</documentation></property></asyxml>*/
+ /*<asyxml><method type = "abscissa" signature="copy()"><code></asyxml>*/
+ abscissa copy()
+ {/*<asyxml></code><documentation>Return a copy of this abscissa.</documentation></method></asyxml>*/
+ abscissa oa = new abscissa;
+ oa.x = this.x;
+ oa.system = this.system;
+ oa.polarconicroutine = this.polarconicroutine;
+ return oa;
+ }
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><constant type = "int" signature="relativesystem,curvilinearsystem,angularsystem,nodesystem"><code></asyxml>*/
+restricted int relativesystem = 0, curvilinearsystem = 1, angularsystem = 2, nodesystem = 3;/*<asyxml></code><documentation>Constant used to set the abscissa system.</documentation></constant></asyxml>*/
+
+/*<asyxml><operator type = "abscissa" signature="cast(explicit position)"><code></asyxml>*/
+abscissa operator cast(explicit position position)
+{/*<asyxml></code><documentation>Cast position to abscissa.
+ If 'position' is relative, the abscissa is relative else it's a curvilinear abscissa.</documentation></operator></asyxml>*/
+ abscissa oarcc;
+ oarcc.x = position.position.x;
+ oarcc.system = position.relative ? relativesystem : curvilinearsystem;
+ return oarcc;
+}
+
+/*<asyxml><operator type = "abscissa" signature="+(real,explicit abscissa)"><code></asyxml>*/
+abscissa operator +(real x, explicit abscissa a)
+{/*<asyxml></code><documentation>Provide 'real + abscissa'.
+ Return abscissa b so that b.x = a.x + x.
+ +(explicit abscissa, real), -(real, explicit abscissa) and -(explicit abscissa, real) are also defined.</documentation></operator></asyxml>*/
+ abscissa oa = a.copy();
+ oa.x = a.x + x;
+ return oa;
+}
+
+abscissa operator +(explicit abscissa a, real x)
+{
+ return x + a;
+}
+abscissa operator +(int x, explicit abscissa a)
+{
+ return ((real)x) + a;
+}
+
+/*<asyxml><operator type = "abscissa" signature="-(explicit abscissa a)"><code></asyxml>*/
+abscissa operator -(explicit abscissa a)
+{/*<asyxml></code><documentation>Return the abscissa b so that b.x = -a.x.</documentation></operator></asyxml>*/
+ abscissa oa;
+ oa.system = a.system;
+ oa.x = -a.x;
+ return oa;
+}
+
+abscissa operator -(real x, explicit abscissa a)
+{
+ abscissa oa;
+ oa.system = a.system;
+ oa.x = x - a.x;
+ return oa;
+}
+abscissa operator -(explicit abscissa a, real x)
+{
+ abscissa oa;
+ oa.system = a.system;
+ oa.x = a.x - x;
+ return oa;
+}
+abscissa operator -(int x, explicit abscissa a)
+{
+ return ((real)x) - a;
+}
+
+/*<asyxml><operator type = "abscissa" signature="*(real,abscissa)"><code></asyxml>*/
+abscissa operator *(real x, explicit abscissa a)
+{/*<asyxml></code><documentation>Provide 'real * abscissa'.
+ Return abscissa b so that b.x = x * a.x.
+ *(explicit abscissa, real), /(real, explicit abscissa) and /(explicit abscissa, real) are also defined.</documentation></operator></asyxml>*/
+ abscissa oa;
+ oa.system = a.system;
+ oa.x = a.x * x;
+ return oa;
+}
+abscissa operator *(explicit abscissa a, real x)
+{
+ return x * a;
+}
+
+abscissa operator /(real x, explicit abscissa a)
+{
+ abscissa oa;
+ oa.system = a.system;
+ oa.x = x/a.x;
+ return oa;
+}
+abscissa operator /(explicit abscissa a, real x)
+{
+ abscissa oa;
+ oa.system = a.system;
+ oa.x = a.x/x;
+ return oa;
+}
+
+abscissa operator /(int x, explicit abscissa a)
+{
+ return ((real)x)/a;
+}
+
+/*<asyxml><function type="abscissa" signature="relabscissa(real)"><code></asyxml>*/
+abscissa relabscissa(real x)
+{/*<asyxml></code><documentation>Return a relative abscissa.</documentation></function></asyxml>*/
+ return (abscissa)(Relative(x));
+}
+abscissa relabscissa(int x)
+{
+ return (abscissa)(Relative(x));
+}
+
+/*<asyxml><function type="abscissa" signature="curabscissa(real)"><code></asyxml>*/
+abscissa curabscissa(real x)
+{/*<asyxml></code><documentation>Return a curvilinear abscissa.</documentation></function></asyxml>*/
+ return (abscissa)((position)x);
+}
+abscissa curabscissa(int x)
+{
+ return (abscissa)((position)x);
+}
+
+/*<asyxml><function type="abscissa" signature="angabscissa(real,polarconicroutine)"><code></asyxml>*/
+abscissa angabscissa(real x, polarconicroutine polarconicroutine = currentpolarconicroutine)
+{/*<asyxml></code><documentation>Return a angular abscissa.</documentation></function></asyxml>*/
+ abscissa oarcc;
+ oarcc.x = x;
+ oarcc.polarconicroutine = polarconicroutine;
+ oarcc.system = angularsystem;
+ return oarcc;
+}
+abscissa angabscissa(int x, polarconicroutine polarconicroutine = currentpolarconicroutine)
+{
+ return angabscissa((real)x, polarconicroutine);
+}
+
+/*<asyxml><function type="abscissa" signature="nodabscissa(real)"><code></asyxml>*/
+abscissa nodabscissa(real x)
+{/*<asyxml></code><documentation>Return an abscissa as time on the path.</documentation></function></asyxml>*/
+ abscissa oarcc;
+ oarcc.x = x;
+ oarcc.system = nodesystem;
+ return oarcc;
+}
+abscissa nodabscissa(int x)
+{
+ return nodabscissa((real)x);
+}
+
+/*<asyxml><operator type = "abscissa" signature="cast(real)"><code></asyxml>*/
+abscissa operator cast(real x)
+{/*<asyxml></code><documentation>Cast real to abscissa, precisely 'nodabscissa'.</documentation></operator></asyxml>*/
+ return nodabscissa(x);
+}
+abscissa operator cast(int x)
+{
+ return nodabscissa((real)x);
+}
+
+/*<asyxml><function type="point" signature="point(circle,abscissa)"><code></asyxml>*/
+point point(circle c, abscissa l)
+{/*<asyxml></code><documentation>Return the point of 'c' which has the abscissa 'l.x'
+ according to the abscissa system 'l.system'.</documentation></function></asyxml>*/
+ coordsys R = c.C.coordsys;
+ if (l.system == nodesystem)
+ return point(R, point((path)c, l.x)/R);
+ if (l.system == relativesystem)
+ return c.C + point(R, R.polar(c.r, 2 * pi * l.x));
+ if (l.system == curvilinearsystem)
+ return c.C + point(R, R.polar(c.r, l.x/c.r));
+ if (l.system == angularsystem)
+ return c.C + point(R, R.polar(c.r, radians(l.x)));
+ abort("point: bad abscissa system.");
+ return (0, 0);
+}
+
+/*<asyxml><function type="point" signature="point(ellipse,abscissa)"><code></asyxml>*/
+point point(ellipse el, abscissa l)
+{/*<asyxml></code><documentation>Return the point of 'el' which has the abscissa 'l.x'
+ according to the abscissa system 'l.system'.</documentation></function></asyxml>*/
+ if(el.e == 0) return point((circle)el, l);
+ coordsys R = coordsys(el);
+ if (l.system == nodesystem)
+ return point(R, point((path)el, l.x)/R);
+ if (l.system == relativesystem) {
+ return point(el, curabscissa((l.x%1) * arclength(el)));
+ }
+ if (l.system == curvilinearsystem) {
+ real a1 = 0, a2 = 360, cx = 0;
+ real aout = a1;
+ real x = abs(l.x)%arclength(el);
+ while (abs(cx - x) > epsgeo) {
+ aout = (a1 + a2)/2;
+ cx = arclength(el, 0, aout, CCW, fromCenter); //fromCenter is speeder
+ if(cx > x) a2 = (a1 + a2)/2; else a1 = (a1 + a2)/2;
+ }
+ path pel = fromCenter(el, sgn(l.x) * aout, sgn(l.x) * aout, 1, CCW);
+ return point(R, point(pel, 0)/R);
+ }
+ if (l.system == angularsystem) {
+ return point(R, point(l.polarconicroutine(el, l.x, l.x, 1, CCW), 0)/R);
+ }
+ abort("point: bad abscissa system.");
+ return (0, 0);
+}
+
+/*<asyxml><function type="point" signature="point(parabola,abscissa)"><code></asyxml>*/
+point point(parabola p, abscissa l)
+{/*<asyxml></code><documentation>Return the point of 'p' which has the abscissa 'l.x'
+ according to the abscissa system 'l.system'.</documentation></function></asyxml>*/
+ coordsys R = coordsys(p);
+ if (l.system == nodesystem)
+ return point(R, point((path)p, l.x)/R);
+ if (l.system == relativesystem) {
+ real[] b = bangles(p);
+ real al = sgn(l.x) > 0 ? arclength(p, 180, b[1]) : arclength(p, 180, b[0]);
+ return point(p, curabscissa(abs(l.x) * al));
+ }
+ if (l.system == curvilinearsystem) {
+ real a1 = 1e-3, a2 = 360 - 1e-3, cx = infinity;
+ while (abs(cx - l.x) > epsgeo) {
+ cx = arclength(p, 180, (a1 + a2)/2);
+ if(cx > l.x) a2 = (a1 + a2)/2; else a1 = (a1 + a2)/2;
+ }
+ path pp = fromFocus(p, a1, a1, 1, CCW);
+ return point(R, point(pp, 0)/R);
+ }
+ if (l.system == angularsystem) {
+ return point(R, point(fromFocus(p, l.x, l.x, 1, CCW), 0)/R);
+ }
+ abort("point: bad abscissa system.");
+ return (0, 0);
+}
+
+/*<asyxml><function type="point" signature="point(hyperbola,abscissa)"><code></asyxml>*/
+point point(hyperbola h, abscissa l)
+{/*<asyxml></code><documentation>Return the point of 'h' which has the abscissa 'l.x'
+ according to the abscissa system 'l.system'.</documentation></function></asyxml>*/
+ coordsys R = coordsys(h);
+ if (l.system == nodesystem)
+ return point(R, point((path)h, l.x)/R);
+ if (l.system == relativesystem) {
+ abort("point(hyperbola, relativeSystem) is not implemented...
+Try relpoint((path)your_hyperbola, x);");
+ }
+ if (l.system == curvilinearsystem) {
+ abort("point(hyperbola, curvilinearSystem) is not implemented...");
+ }
+ if (l.system == angularsystem) {
+ return point(R, point(l.polarconicroutine(h, l.x, l.x, 1, CCW), 0)/R);
+ }
+ abort("point: bad abscissa system.");
+ return (0, 0);
+}
+
+/*<asyxml><function type="abscissa" signature="point(conic,point)"><code></asyxml>*/
+point point(explicit conic co, abscissa l)
+{/*<asyxml></code><documentation>Return the curvilinear abscissa of 'M' on the conic 'co'.</documentation></function></asyxml>*/
+ if(co.e == 0) return point((circle)co, l);
+ if(co.e < 1) return point((ellipse)co, l);
+ if(co.e == 1) return point((parabola)co, l);
+ return point((hyperbola)co, l);
+}
+
+
+/*<asyxml><function type="point" signature="point(line,abscissa)"><code></asyxml>*/
+point point(line l, abscissa x)
+{/*<asyxml></code><documentation>Return the point of 'l' which has the abscissa 'l.x' according to the abscissa system 'l.system'.
+ Note that the origin is l.A, and point(l, relabscissa(x)) returns l.A + x.x * vector(l.B - l.A).</documentation></function></asyxml>*/
+ coordsys R = l.A.coordsys;
+ if (x.system == nodesystem)
+ return l.A + (x.x < 0 ? 0 : x.x > 1 ? 1 : x.x) * vector(l.B - l.A);
+ if (x.system == relativesystem)
+ return l.A + x.x * vector(l.B - l.A);
+ if (x.system == curvilinearsystem)
+ return l.A + x.x * l.u;
+ if (x.system == angularsystem)
+ abort("point: what the meaning of angular abscissa on line ?.");
+ abort("point: bad abscissa system.");
+ return (0, 0);
+}
+
+/*<asyxml><function type="point" signature="point(line,real)"><code></asyxml>*/
+point point(line l, explicit real x)
+{/*<asyxml></code><documentation>Return the point between node l.A and l.B (x <= 0 means l.A, x >=1 means l.B).</documentation></function></asyxml>*/
+ return point(l, nodabscissa(x));
+}
+point point(line l, explicit int x)
+{
+ return point(l, nodabscissa(x));
+}
+
+/*<asyxml><function type="circle" signature="point(explicit circle,explicit real)"><code></asyxml>*/
+point point(explicit circle c, explicit real x)
+{/*<asyxml></code><documentation>Return the point between node floor(x) and floor(x) + 1.</documentation></function></asyxml>*/
+ return point(c, nodabscissa(x));
+}
+point point(explicit circle c, explicit int x)
+{
+ return point(c, nodabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="point(explicit ellipse,explicit real)"><code></asyxml>*/
+point point(explicit ellipse el, explicit real x)
+{/*<asyxml></code><documentation>Return the point between node floor(x) and floor(x) + 1.</documentation></function></asyxml>*/
+ return point(el, nodabscissa(x));
+}
+point point(explicit ellipse el, explicit int x)
+{
+ return point(el, nodabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="point(explicit parabola,explicit real)"><code></asyxml>*/
+point point(explicit parabola p, explicit real x)
+{/*<asyxml></code><documentation>Return the point between node floor(x) and floor(x) + 1.</documentation></function></asyxml>*/
+ return point(p, nodabscissa(x));
+}
+point point(explicit parabola p, explicit int x)
+{
+ return point(p, nodabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="point(explicit hyperbola,explicit real)"><code></asyxml>*/
+point point(explicit hyperbola h, explicit real x)
+{/*<asyxml></code><documentation>Return the point between node floor(x) and floor(x) + 1.</documentation></function></asyxml>*/
+ return point(h, nodabscissa(x));
+}
+point point(explicit hyperbola h, explicit int x)
+{
+ return point(h, nodabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="point(explicit conic,explicit real)"><code></asyxml>*/
+point point(explicit conic co, explicit real x)
+{/*<asyxml></code><documentation>Return the point between node floor(x) and floor(x) + 1.</documentation></function></asyxml>*/
+ point op;
+ if(co.e == 0) op = point((circle)co, nodabscissa(x));
+ else if(co.e < 1) op = point((ellipse)co, nodabscissa(x));
+ else if(co.e == 1) op = point((parabola)co, nodabscissa(x));
+ else op = point((hyperbola)co, nodabscissa(x));
+ return op;
+}
+point point(explicit conic co, explicit int x)
+{
+ return point(co, (real)x);
+}
+
+/*<asyxml><function type="point" signature="relpoint(line,real)"><code></asyxml>*/
+point relpoint(line l, real x)
+{/*<asyxml></code><documentation>Return the relative point of 'l' (0 means l.A,
+ 1 means l.B, x means l.A + x * vector(l.B - l.A) ).</documentation></function></asyxml>*/
+ return point(l, Relative(x));
+}
+
+/*<asyxml><function type="point" signature="relpoint(explicit circle,real)"><code></asyxml>*/
+point relpoint(explicit circle c, real x)
+{/*<asyxml></code><documentation>Return the relative point of 'c' (0 means origin, 1 means end).
+ Origin is c.center + c.r * (1, 0).</documentation></function></asyxml>*/
+ return point(c, Relative(x));
+}
+
+/*<asyxml><function type="point" signature="relpoint(explicit ellipse,real)"><code></asyxml>*/
+point relpoint(explicit ellipse el, real x)
+{/*<asyxml></code><documentation>Return the relative point of 'el' (0 means origin, 1 means end).</documentation></function></asyxml>*/
+ return point(el, Relative(x));
+}
+
+/*<asyxml><function type="point" signature="relpoint(explicit parabola,real)"><code></asyxml>*/
+point relpoint(explicit parabola p, real x)
+{/*<asyxml></code><documentation>Return the relative point of the path of the parabola
+ bounded by the bounding box of the current picture.
+ 0 means origin, 1 means end, where the origin is the vertex of 'p'.</documentation></function></asyxml>*/
+ return point(p, Relative(x));
+}
+
+/*<asyxml><function type="point" signature="relpoint(explicit hyperbola,real)"><code></asyxml>*/
+point relpoint(explicit hyperbola h, real x)
+{/*<asyxml></code><documentation>Not yet implemented... <look href = "point(hyperbola, abscissa)"/></documentation></function></asyxml>*/
+ return point(h, Relative(x));
+}
+
+/*<asyxml><function type="point" signature="relpoint(explicit conic,explicit real)"><code></asyxml>*/
+point relpoint(explicit conic co, explicit real x)
+{/*<asyxml></code><documentation>Return the relative point of 'co' (0 means origin, 1 means end).</documentation></function></asyxml>*/
+ point op;
+ if(co.e == 0) op = point((circle)co, Relative(x));
+ else if(co.e < 1) op = point((ellipse)co, Relative(x));
+ else if(co.e == 1) op = point((parabola)co, Relative(x));
+ else op = point((hyperbola)co, Relative(x));
+ return op;
+}
+point relpoint(explicit conic co, explicit int x)
+{
+ return relpoint(co, (real)x);
+}
+
+/*<asyxml><function type="point" signature="angpoint(explicit circle,real)"><code></asyxml>*/
+point angpoint(explicit circle c, real x)
+{/*<asyxml></code><documentation>Return the point of 'c' in the direction 'x' measured in degrees.</documentation></function></asyxml>*/
+ return point(c, angabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="angpoint(explicit ellipse,real,polarconicroutine)"><code></asyxml>*/
+point angpoint(explicit ellipse el, real x,
+ polarconicroutine polarconicroutine = currentpolarconicroutine)
+{/*<asyxml></code><documentation>Return the point of 'el' in the direction 'x'
+ measured in degrees according to 'polarconicroutine'.</documentation></function></asyxml>*/
+ return el.e == 0 ? angpoint((circle) el, x) : point(el, angabscissa(x, polarconicroutine));
+}
+
+/*<asyxml><function type="point" signature="angpoint(explicit parabola,real)"><code></asyxml>*/
+point angpoint(explicit parabola p, real x)
+{/*<asyxml></code><documentation>Return the point of 'p' in the direction 'x' measured in degrees.</documentation></function></asyxml>*/
+ return point(p, angabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="angpoint(explicit hyperbola,real,polarconicroutine)"><code></asyxml>*/
+point angpoint(explicit hyperbola h, real x,
+ polarconicroutine polarconicroutine = currentpolarconicroutine)
+{/*<asyxml></code><documentation>Return the point of 'h' in the direction 'x'
+ measured in degrees according to 'polarconicroutine'.</documentation></function></asyxml>*/
+ return point(h, angabscissa(x, polarconicroutine));
+}
+
+/*<asyxml><function type="point" signature="curpoint(line,real)"><code></asyxml>*/
+point curpoint(line l, real x)
+{/*<asyxml></code><documentation>Return the point of 'l' which has the curvilinear abscissa 'x'.
+ Origin is l.A.</documentation></function></asyxml>*/
+ return point(l, curabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="curpoint(explicit circle,real)"><code></asyxml>*/
+point curpoint(explicit circle c, real x)
+{/*<asyxml></code><documentation>Return the point of 'c' which has the curvilinear abscissa 'x'.
+ Origin is c.center + c.r * (1, 0).</documentation></function></asyxml>*/
+ return point(c, curabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="curpoint(explicit ellipse,real)"><code></asyxml>*/
+point curpoint(explicit ellipse el, real x)
+{/*<asyxml></code><documentation>Return the point of 'el' which has the curvilinear abscissa 'el'.</documentation></function></asyxml>*/
+ return point(el, curabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="curpoint(explicit parabola,real)"><code></asyxml>*/
+point curpoint(explicit parabola p, real x)
+{/*<asyxml></code><documentation>Return the point of 'p' which has the curvilinear abscissa 'x'.
+ Origin is the vertex of 'p'.</documentation></function></asyxml>*/
+ return point(p, curabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="curpoint(conic,real)"><code></asyxml>*/
+point curpoint(conic co, real x)
+{/*<asyxml></code><documentation>Return the point of 'co' which has the curvilinear abscissa 'x'.</documentation></function></asyxml>*/
+ point op;
+ if(co.e == 0) op = point((circle)co, curabscissa(x));
+ else if(co.e < 1) op = point((ellipse)co, curabscissa(x));
+ else if(co.e == 1) op = point((parabola)co, curabscissa(x));
+ else op = point((hyperbola)co, curabscissa(x));
+ return op;
+}
+
+/*<asyxml><function type="abscissa" signature="angabscissa(circle,point)"><code></asyxml>*/
+abscissa angabscissa(circle c, point M)
+{/*<asyxml></code><documentation>Return the angular abscissa of 'M' on the circle 'c'.</documentation></function></asyxml>*/
+ if(!(M @ c)) abort("angabscissa: the point is not on the circle.");
+ abscissa oa;
+ oa.system = angularsystem;
+ oa.x = degrees(M - c.C);
+ if(oa.x < 0) oa.x+=360;
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="angabscissa(ellipse,point,polarconicroutine)"><code></asyxml>*/
+abscissa angabscissa(ellipse el, point M,
+ polarconicroutine polarconicroutine = currentpolarconicroutine)
+{/*<asyxml></code><documentation>Return the angular abscissa of 'M' on the ellipse 'el' according to 'polarconicroutine'.</documentation></function></asyxml>*/
+ if(!(M @ el)) abort("angabscissa: the point is not on the ellipse.");
+ abscissa oa;
+ oa.system = angularsystem;
+ oa.polarconicroutine = polarconicroutine;
+ oa.x = polarconicroutine == fromCenter ? degrees(M - el.C) : degrees(M - el.F1);
+ oa.x -= el.angle;
+ if(oa.x < 0) oa.x += 360;
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="angabscissa(hyperbola,point,polarconicroutine)"><code></asyxml>*/
+abscissa angabscissa(hyperbola h, point M,
+ polarconicroutine polarconicroutine = currentpolarconicroutine)
+{/*<asyxml></code><documentation>Return the angular abscissa of 'M' on the hyperbola 'h' according to 'polarconicroutine'.</documentation></function></asyxml>*/
+ if(!(M @ h)) abort("angabscissa: the point is not on the hyperbola.");
+ abscissa oa;
+ oa.system = angularsystem;
+ oa.polarconicroutine = polarconicroutine;
+ oa.x = polarconicroutine == fromCenter ? degrees(M - h.C) : degrees(M - h.F1) + 180;
+ oa.x -= h.angle;
+ if(oa.x < 0) oa.x += 360;
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="angabscissa(parabola,point)"><code></asyxml>*/
+abscissa angabscissa(parabola p, point M)
+{/*<asyxml></code><documentation>Return the angular abscissa of 'M' on the parabola 'p'.</documentation></function></asyxml>*/
+ if(!(M @ p)) abort("angabscissa: the point is not on the parabola.");
+ abscissa oa;
+ oa.system = angularsystem;
+ oa.polarconicroutine = fromFocus;// Not used
+ oa.x = degrees(M - p.F);
+ oa.x -= p.angle;
+ if(oa.x < 0) oa.x += 360;
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="angabscissa(conic,point)"><code></asyxml>*/
+abscissa angabscissa(explicit conic co, point M)
+{/*<asyxml></code><documentation>Return the angular abscissa of 'M' on the conic 'co'.</documentation></function></asyxml>*/
+ if(co.e == 0) return angabscissa((circle)co, M);
+ if(co.e < 1) return angabscissa((ellipse)co, M);
+ if(co.e == 1) return angabscissa((parabola)co, M);
+ return angabscissa((hyperbola)co, M);
+}
+
+/*<asyxml><function type="abscissa" signature="curabscissa(line,point)"><code></asyxml>*/
+abscissa curabscissa(line l, point M)
+{/*<asyxml></code><documentation>Return the curvilinear abscissa of 'M' on the line 'l'.</documentation></function></asyxml>*/
+ if(!(M @ extend(l))) abort("curabscissa: the point is not on the line.");
+ abscissa oa;
+ oa.system = curvilinearsystem;
+ oa.x = sgn(dot(M - l.A, l.B - l.A)) * abs(M - l.A);
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="curabscissa(circle,point)"><code></asyxml>*/
+abscissa curabscissa(circle c, point M)
+{/*<asyxml></code><documentation>Return the curvilinear abscissa of 'M' on the circle 'c'.</documentation></function></asyxml>*/
+ if(!(M @ c)) abort("curabscissa: the point is not on the circle.");
+ abscissa oa;
+ oa.system = curvilinearsystem;
+ oa.x = pi * angabscissa(c, M).x * c.r/180;
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="curabscissa(ellipse,point)"><code></asyxml>*/
+abscissa curabscissa(ellipse el, point M)
+{/*<asyxml></code><documentation>Return the curvilinear abscissa of 'M' on the ellipse 'el'.</documentation></function></asyxml>*/
+ if(!(M @ el)) abort("curabscissa: the point is not on the ellipse.");
+ abscissa oa;
+ oa.system = curvilinearsystem;
+ real a = angabscissa(el, M, fromCenter).x;
+ oa.x = arclength(el, 0, a, fromCenter);
+ oa.polarconicroutine = fromCenter;
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="curabscissa(parabola,point)"><code></asyxml>*/
+abscissa curabscissa(parabola p, point M)
+{/*<asyxml></code><documentation>Return the curvilinear abscissa of 'M' on the parabola 'p'.</documentation></function></asyxml>*/
+ if(!(M @ p)) abort("curabscissa: the point is not on the parabola.");
+ abscissa oa;
+ oa.system = curvilinearsystem;
+ real a = angabscissa(p, M).x;
+ oa.x = arclength(p, 180, a);
+ oa.polarconicroutine = fromFocus; // Not used.
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="curabscissa(conic,point)"><code></asyxml>*/
+abscissa curabscissa(conic co, point M)
+{/*<asyxml></code><documentation>Return the curvilinear abscissa of 'M' on the conic 'co'.</documentation></function></asyxml>*/
+ if(co.e > 1) abort("curabscissa: not implemented for this hyperbola.");
+ if(co.e == 0) return curabscissa((circle)co, M);
+ if(co.e < 1) return curabscissa((ellipse)co, M);
+ return curabscissa((parabola)co, M);
+}
+
+/*<asyxml><function type="abscissa" signature="nodabscissa(line,point)"><code></asyxml>*/
+abscissa nodabscissa(line l, point M)
+{/*<asyxml></code><documentation>Return the node abscissa of 'M' on the line 'l'.</documentation></function></asyxml>*/
+ if(!(M @ (segment)l)) abort("nodabscissa: the point is not on the segment.");
+ abscissa oa;
+ oa.system = nodesystem;
+ oa.x = abs(M - l.A)/abs(l.A - l.B);
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="nodabscissa(circle,point)"><code></asyxml>*/
+abscissa nodabscissa(circle c, point M)
+{/*<asyxml></code><documentation>Return the node abscissa of 'M' on the circle 'c'.</documentation></function></asyxml>*/
+ if(!(M @ c)) abort("nodabscissa: the point is not on the circle.");
+ abscissa oa;
+ oa.system = nodesystem;
+ oa.x = intersect((path)c, locate(M))[0];
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="nodabscissa(ellipse,point)"><code></asyxml>*/
+abscissa nodabscissa(ellipse el, point M)
+{/*<asyxml></code><documentation>Return the node abscissa of 'M' on the ellipse 'el'.</documentation></function></asyxml>*/
+ if(!(M @ el)) abort("nodabscissa: the point is not on the ellipse.");
+ abscissa oa;
+ oa.system = nodesystem;
+ oa.x = intersect((path)el, M)[0];
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="nodabscissa(parabola,point)"><code></asyxml>*/
+abscissa nodabscissa(parabola p, point M)
+{/*<asyxml></code><documentation>Return the node abscissa OF 'M' on the parabola 'p'.</documentation></function></asyxml>*/
+ if(!(M @ p)) abort("nodabscissa: the point is not on the parabola.");
+ abscissa oa;
+ oa.system = nodesystem;
+ path pg = p;
+ real[] t = intersect(pg, M, 1e-5);
+ if(t.length == 0) abort("nodabscissa: the point is not on the path of the parabola.");
+ oa.x = t[0];
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="nodabscissa(conic,point)"><code></asyxml>*/
+abscissa nodabscissa(conic co, point M)
+{/*<asyxml></code><documentation>Return the node abscissa of 'M' on the conic 'co'.</documentation></function></asyxml>*/
+ if(co.e > 1) abort("nodabscissa: not implemented for hyperbola.");
+ if(co.e == 0) return nodabscissa((circle)co, M);
+ if(co.e < 1) return nodabscissa((ellipse)co, M);
+ return nodabscissa((parabola)co, M);
+}
+
+
+/*<asyxml><function type="abscissa" signature="relabscissa(line,point)"><code></asyxml>*/
+abscissa relabscissa(line l, point M)
+{/*<asyxml></code><documentation>Return the relative abscissa of 'M' on the line 'l'.</documentation></function></asyxml>*/
+ if(!(M @ extend(l))) abort("relabscissa: the point is not on the line.");
+ abscissa oa;
+ oa.system = relativesystem;
+ oa.x = sgn(dot(M - l.A, l.B - l.A)) * abs(M - l.A)/abs(l.A - l.B);
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="relabscissa(circle,point)"><code></asyxml>*/
+abscissa relabscissa(circle c, point M)
+{/*<asyxml></code><documentation>Return the relative abscissa of 'M' on the circle 'c'.</documentation></function></asyxml>*/
+ if(!(M @ c)) abort("relabscissa: the point is not on the circle.");
+ abscissa oa;
+ oa.system = relativesystem;
+ oa.x = angabscissa(c, M).x/360;
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="relabscissa(ellipse,point)"><code></asyxml>*/
+abscissa relabscissa(ellipse el, point M)
+{/*<asyxml></code><documentation>Return the relative abscissa of 'M' on the ellipse 'el'.</documentation></function></asyxml>*/
+ if(!(M @ el)) abort("relabscissa: the point is not on the ellipse.");
+ abscissa oa;
+ oa.system = relativesystem;
+ oa.x = curabscissa(el, M).x/arclength(el);
+ oa.polarconicroutine = fromFocus;
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="relabscissa(conic,point)"><code></asyxml>*/
+abscissa relabscissa(conic co, point M)
+{/*<asyxml></code><documentation>Return the relative abscissa of 'M'
+ on the conic 'co'.</documentation></function></asyxml>*/
+ if(co.e > 1) abort("relabscissa: not implemented for hyperbola and parabola.");
+ if(co.e == 1) return relabscissa((parabola)co, M);
+ if(co.e == 0) return relabscissa((circle)co, M);
+ return relabscissa((ellipse)co, M);
+}
+// *.......................ABSCISSA........................*
+// *=======================================================*
+
+// *=======================================================*
+// *.........................ARCS..........................*
+/*<asyxml><struct signature="arc"><code></asyxml>*/
+struct arc {
+ /*<asyxml></code><documentation>Implement oriented ellipse (included circle) arcs.
+ All the calculus with this structure will be as exact as Asymptote can do.
+ For a full precision, you must not cast 'arc' to 'path' excepted for drawing routines.
+ </documentation><property type = "ellipse" signature="el"><code></asyxml>*/
+ ellipse el;/*<asyxml></code><documentation>The support of the arc.</documentation></property><property type = "real" signature="angle0"><code></asyxml>*/
+ restricted real angle0 = 0;/*<asyxml></code><documentation>Internal use: rotating a circle does not modify the origin point,this variable stocks the eventual angle rotation. This value is not used for ellipses which are not circles.</documentation></property><property type = "real" signature="angle1,angle2"><code></asyxml>*/
+ restricted real angle1, angle2;/*<asyxml></code><documentation>Values (in degrees) in ]-360, 360[.</documentation></property><property type = "bool" signature="direction"><code></asyxml>*/
+ bool direction = CCW;/*<asyxml></code><documentation>The arc will be drawn from 'angle1' to 'angle2' rotating in the direction 'direction'.</documentation></property><property type = "polarconicroutine" signature="polarconicroutine"><code></asyxml>*/
+ polarconicroutine polarconicroutine = currentpolarconicroutine;/*<asyxml></code><documentation>The routine to which the angles refer.
+ If 'el' is a circle 'fromCenter' is always used.</documentation></property></asyxml>*/
+
+ /*<asyxml><method type = "void" signature="setangles(real,real,real)"><code></asyxml>*/
+ void setangles(real a0, real a1, real a2)
+ {/*<asyxml></code><documentation>Set the angles.</documentation></method></asyxml>*/
+ if (a1 < 0 && a2 < 0) {
+ a1 += 360;
+ a2 += 360;
+ }
+ this.angle0 = a0%(sgnd(a0) * 360);
+ this.angle1 = a1%(sgnd(a1) * 360);
+ this.angle2 = a2%(sgnd(2) * 360);
+ }
+
+ /*<asyxml><method type = "void" signature="init(ellipse,real,real,real,polarconicroutine,bool)"><code></asyxml>*/
+ void init(ellipse el, real angle0 = 0, real angle1, real angle2,
+ polarconicroutine polarconicroutine,
+ bool direction = CCW)
+ {/*<asyxml></code><documentation>Constructor.</documentation></method></asyxml>*/
+ if(abs(angle1 - angle2) > 360) abort("arc: |angle1 - angle2| > 360.");
+ this.el = el;
+ this.setangles(angle0, angle1, angle2);
+ this.polarconicroutine = polarconicroutine;
+ this.direction = direction;
+ }
+
+ /*<asyxml><method type = "arc" signature="copy()"><code></asyxml>*/
+ arc copy()
+ {/*<asyxml></code><documentation>Copy the arc.</documentation></method></asyxml>*/
+ arc oa = new arc;
+ oa.el = this.el;
+ oa.direction = this.direction;
+ oa.polarconicroutine = this.polarconicroutine;
+ oa.angle1 = this.angle1;
+ oa.angle2 = this.angle2;
+ oa.angle0 = this.angle0;
+ return oa;
+ }
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><function type="polarconicroutine" signature="polarconicroutine(ellipse)"><code></asyxml>*/
+polarconicroutine polarconicroutine(conic co)
+{/*<asyxml></code><documentation>Return the default routine used to draw a conic.</documentation></function></asyxml>*/
+ if(co.e == 0) return fromCenter;
+ if(co.e == 1) return fromFocus;
+ return currentpolarconicroutine;
+}
+
+/*<asyxml><function type="arc" signature="arc(ellipse,real,real,polarconicroutine,bool)"><code></asyxml>*/
+arc arc(ellipse el, real angle1, real angle2,
+ polarconicroutine polarconicroutine = polarconicroutine(el),
+ bool direction = CCW)
+{/*<asyxml></code><documentation>Return the ellipse arc from 'angle1' to 'angle2' with respect to 'polarconicroutine' and rotating in the direction 'direction'.</documentation></function></asyxml>*/
+ arc oa;
+ oa.init(el, 0, angle1, angle2, polarconicroutine, direction);
+ return oa;
+}
+
+/*<asyxml><function type="arc" signature="complementary(arc)"><code></asyxml>*/
+arc complementary(arc a)
+{/*<asyxml></code><documentation>Return the complementary of 'a'.</documentation></function></asyxml>*/
+ arc oa;
+ oa.init(a.el, a.angle0, a.angle2, a.angle1, a.polarconicroutine, a.direction);
+ return oa;
+}
+
+/*<asyxml><function type="arc" signature="reverse(arc)"><code></asyxml>*/
+arc reverse(arc a)
+{/*<asyxml></code><documentation>Return arc 'a' oriented in reverse direction.</documentation></function></asyxml>*/
+ arc oa;
+ oa.init(a.el, a.angle0, a.angle2, a.angle1, a.polarconicroutine, !a.direction);
+ return oa;
+}
+
+/*<asyxml><function type="real" signature="degrees(arc)"><code></asyxml>*/
+real degrees(arc a)
+{/*<asyxml></code><documentation>Return the measure in degrees of the oriented arc 'a'.</documentation></function></asyxml>*/
+ real or;
+ real da = a.angle2 - a.angle1;
+ if(a.direction) {
+ or = a.angle1 < a.angle2 ? da : 360 + da;
+ } else {
+ or = a.angle1 < a.angle2 ? -360 + da : da;
+ }
+ return or;
+}
+
+/*<asyxml><function type="real" signature="angle(a)"><code></asyxml>*/
+real angle(arc a)
+{/*<asyxml></code><documentation>Return the measure in radians of the oriented arc 'a'.</documentation></function></asyxml>*/
+ return radians(degrees(a));
+}
+
+/*<asyxml><function type="int" signature="arcnodesnumber(explicit arc)"><code></asyxml>*/
+int arcnodesnumber(explicit arc a)
+{/*<asyxml></code><documentation>Return the number of nodes to draw the arc 'a'.</documentation></function></asyxml>*/
+ return ellipsenodesnumber(a.el.a, a.el.b, a.angle1, a.angle2, a.direction);
+}
+
+private path arctopath(arc a, int n)
+{
+ if(a.el.e == 0) return arcfromcenter(a.el, a.angle0 + a.angle1, a.angle0 + a.angle2, a.direction, n);
+ if(a.el.e != 1) return a.polarconicroutine(a.el, a.angle1, a.angle2, n, a.direction);
+ return arcfromfocus(a.el, a.angle1, a.angle2, n, a.direction);
+}
+
+/*<asyxml><function type="point" signature="angpoint(arc,real)"><code></asyxml>*/
+point angpoint(arc a, real angle)
+{/*<asyxml></code><documentation>Return the point given by its angular position (in degrees) relative to the arc 'a'.
+ If 'angle > degrees(a)' or 'angle < 0' the returned point is on the extended arc.</documentation></function></asyxml>*/
+ pair p;
+ if(a.el.e == 0) {
+ real gle = a.angle0 + a.angle1 + (a.direction ? angle : -angle);
+ p = point(arcfromcenter(a.el, gle, gle, CCW, 1), 0);
+ }
+ else {
+ real gle = a.angle1 + (a.direction ? angle : -angle);
+ p = point(a.polarconicroutine(a.el, gle, gle, 1, CCW), 0);
+ }
+ return point(coordsys(a.el), p/coordsys(a.el));
+}
+
+/*<asyxml><operator type = "path" signature="cast(explicit arc)"><code></asyxml>*/
+path operator cast(explicit arc a)
+{/*<asyxml></code><documentation>Cast arc to path.</documentation></operator></asyxml>*/
+ return arctopath(a, arcnodesnumber(a));
+}
+
+/*<asyxml><operator type = "guide" signature="cast(explicit arc)"><code></asyxml>*/
+guide operator cast(explicit arc a)
+{/*<asyxml></code><documentation>Cast arc to guide.</documentation></operator></asyxml>*/
+ return arctopath(a, arcnodesnumber(a));
+}
+
+/*<asyxml><operator type = "arc" signature="*(transform,explicit arc)"><code></asyxml>*/
+arc operator *(transform t, explicit arc a)
+{/*<asyxml></code><documentation>Provide transform * arc.</documentation></operator></asyxml>*/
+ pair[] P, PP;
+ path g = arctopath(a, 3);
+ real a0, a1 = a.angle1, a2 = a.angle2, ap1, ap2;
+ bool dir = a.direction;
+ P[0] = t * point(g, 0);
+ P[1] = t * point(g, 2);
+ ellipse el = t * a.el;
+ arc oa;
+ a0 = (a.angle0 + angle(shiftless(t)))%360;
+ pair C;
+ if(a.polarconicroutine == fromCenter) C = el.C; else C = el.F1;
+ real d = abs(locate(el.F2 - el.F1)) > epsgeo ?
+ degrees(locate(el.F2 - el.F1)) : a0 + degrees(el.C.coordsys.i);
+ ap1 = (degrees(P[0]-C, false) - d)%360;
+ ap2 = (degrees(P[1]-C, false) - d)%360;
+ oa.init(el, a0, ap1, ap2, a.polarconicroutine, dir);
+ g = arctopath(oa, 3);
+ PP[0] = point(g, 0);
+ PP[1] = point(g, 2);
+ if((a1 - a2) * (ap1 - ap2) < 0) {// Handle reflection.
+ dir=!a.direction;
+ oa.init(el, a0, ap1, ap2, a.polarconicroutine, dir);
+ }
+ return oa;
+}
+
+/*<asyxml><operator type = "arc" signature="*(real,explicit arc)"><code></asyxml>*/
+arc operator *(real x, explicit arc a)
+{/*<asyxml></code><documentation>Provide real * arc.
+ Return the arc subtracting and adding '(x - 1) * degrees(a)/2' to 'a.angle1' and 'a.angle2' respectively.</documentation></operator></asyxml>*/
+ real a1, a2, gle;
+ gle = (x - 1) * degrees(a)/2;
+ a1 = a.angle1 - gle;
+ a2 = a.angle2 + gle;
+ arc oa;
+ oa.init(a.el, a.angle0, a1, a2, a.polarconicroutine, a.direction);
+ return oa;
+}
+arc operator *(int x, explicit arc a){return (real)x * a;}
+/*<asyxml><operator type = "arc" signature="/(real,explicit arc)"><code></asyxml>*/
+arc operator /(explicit arc a, real x)
+{/*<asyxml></code><documentation>Provide arc/real.
+ Return the arc subtracting and adding '(1/x - 1) * degrees(a)/2' to 'a.angle1' and 'a.angle2' respectively.</documentation></operator></asyxml>*/
+ return (1/x) * a;
+}
+/*<asyxml><operator type = "arc" signature="+(explicit arc,point)"><code></asyxml>*/
+arc operator +(explicit arc a, point M)
+{/*<asyxml></code><documentation>Provide arc + point.
+ Return shifted arc.
+ 'operator +(explicit arc, point)', 'operator +(explicit arc, vector)' and 'operator -(explicit arc, vector)' are also defined.</documentation></operator></asyxml>*/
+ return shift(M) * a;
+}
+arc operator -(explicit arc a, point M){return a + (-M);}
+arc operator +(explicit arc a, vector v){return shift(locate(v)) * a;}
+arc operator -(explicit arc a, vector v){return a + (-v);}
+
+
+/*<asyxml><operator type = "bool" signature="@(point,arc)"><code></asyxml>*/
+bool operator @(point M, arc a)
+{/*<asyxml></code><documentation>Return true iff 'M' is on the arc 'a'.</documentation></operator></asyxml>*/
+ if (!(M @ a.el)) return false;
+ coordsys R = defaultcoordsys;
+ path ap = arctopath(a, 3);
+ line l = line(point(R, point(ap, 0)), point(R, point(ap, 2)));
+ return sameside(M, point(R, point(ap, 1)), l);
+}
+
+/*<asyxml><function type="void" signature="draw(picture,Label,arc,align,pen,arrowbar,arrowbar,margin,Label,marker)"><code></asyxml>*/
+void draw(picture pic = currentpicture, Label L = "", arc a,
+ align align = NoAlign, pen p = currentpen,
+ arrowbar arrow = None, arrowbar bar = None, margin margin = NoMargin,
+ Label legend = "", marker marker = nomarker)
+{/*<asyxml></code><documentation>Draw 'arc' adding the pen returned by 'addpenarc(p)' to the pen 'p'.
+ <look href = "#addpenarc"/></documentation></function></asyxml>*/
+ draw(pic, L, (path)a, align, addpenarc(p), arrow, bar, margin, legend, marker);
+}
+
+/*<asyxml><function type="real" signature="arclength(arc)"><code></asyxml>*/
+real arclength(arc a)
+{/*<asyxml></code><documentation>The arc length of 'a'.</documentation></function></asyxml>*/
+ return arclength(a.el, a.angle1, a.angle2, a.direction, a.polarconicroutine);
+}
+
+private point ppoint(arc a, real x)
+{// Return the point of the arc proportionally to its length.
+ point oP;
+ if(a.el.e == 0) { // Case of circle.
+ oP = angpoint(a, x * abs(degrees(a)));
+ } else { // Ellipse and not circle.
+ if(!a.direction) {
+ transform t = reflect(line(a.el.F1, a.el.F2));
+ return t * ppoint(t * a, x);
+ }
+
+ real angle1 = a.angle1, angle2 = a.angle2;
+ if(a.polarconicroutine == fromFocus) {
+ // dot(point(fromFocus(a.el, angle1, angle1, 1, CCW), 0), 2mm + blue);
+ // dot(point(fromFocus(a.el, angle2, angle2, 1, CCW), 0), 2mm + blue);
+ // write("fromfocus1 = ", angle1);
+ // write("fromfocus2 = ", angle2);
+ real gle1 = focusToCenter(a.el, angle1);
+ real gle2 = focusToCenter(a.el, angle2);
+ if((gle1 - gle2) * (angle1 - angle2) > 0) {
+ angle1 = gle1; angle2 = gle2;
+ } else {
+ angle1 = gle2; angle2 = gle1;
+ }
+ // write("fromcenter1 = ", angle1);
+ // write("fromcenter2 = ", angle2);
+ // dot(point(fromCenter(a.el, angle1, angle1, 1, CCW), 0), 1mm + red);
+ // dot(point(fromCenter(a.el, angle2, angle2, 1, CCW), 0), 1mm + red);
+ }
+
+ if(angle1 > angle2) {
+ arc ta = a.copy();
+ ta.polarconicroutine = fromCenter;
+ ta.setangles(a0 = a.angle0, a1 = angle1 - 360, a2 = angle2);
+ return ppoint(ta, x);
+ }
+ ellipse co = a.el;
+ real gle, a1, a2, cx = 0;
+ bool direction;
+ if(x >= 0) {
+ a1 = angle1;
+ a2 = a1 + 360;
+ direction = CCW;
+ } else {
+ a1 = angle1 - 360;
+ a2 = a1 - 360;
+ direction = CW;
+ }
+ gle = a1;
+ real L = arclength(co, angle1, angle2, a.direction, fromCenter);
+ real tx = L * abs(x)%arclength(co);
+ real aout = a1;
+ while(abs(cx - tx) > epsgeo) {
+ aout = (a1 + a2)/2;
+ cx = abs(arclength(co, gle, aout, direction, fromCenter));
+ if(cx > tx) a2 = (a1 + a2)/2 ; else a1 = (a1 + a2)/2;
+ }
+ pair p = point(arcfromcenter(co, aout, aout, CCW, 1), 0);
+ oP = point(coordsys(co), p/coordsys(co));
+ }
+ return oP;
+}
+
+/*<asyxml><function type="point" signature="point(arc,abscissa)"><code></asyxml>*/
+point point(arc a, abscissa l)
+{/*<asyxml></code><documentation>Return the point of 'a' which has the abscissa 'l.x'
+ according to the abscissa system 'l.system'.
+ Note that 'a.polarconicroutine' is used instead of 'l.polarconicroutine'.
+ <look href = "#struct abscissa"/></documentation></function></asyxml>*/
+ real posx;
+ arc ta = a.copy();
+ ellipse co = a.el;
+ if (l.system == relativesystem) {
+ posx = l.x;
+ } else
+ if (l.system == curvilinearsystem) {
+ real tl;
+ if(co.e == 0) {
+ tl = curabscissa(a.el, angpoint(a.el, a.angle0 + a.angle1)).x;
+ return curpoint(a.el, tl + (a.direction ? l.x : -l.x));
+ } else {
+ tl = curabscissa(a.el, angpoint(a.el, a.angle1, a.polarconicroutine)).x;
+ return curpoint(a.el, tl + (a.direction ? l.x : -l.x));
+ }
+ } else
+ if (l.system == nodesystem) {
+ coordsys R = coordsys(co);
+ return point(R, point((path)a, l.x)/R);
+ } else
+ if (l.system == angularsystem) {
+ return angpoint(a, l.x);
+ } else abort("point: bad abscissa system.");
+ return ppoint(ta, posx);
+}
+
+
+/*<asyxml><function type="point" signature="point(arc,real)"><code></asyxml>*/
+point point(arc a, real x)
+{/*<asyxml></code><documentation>Return the point between node floor(t) and floor(t) + 1.</documentation></function></asyxml>*/
+ return point(a, nodabscissa(x));
+}
+pair point(explicit arc a, int x)
+{
+ return point(a, nodabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="relpoint(arc,real)"><code></asyxml>*/
+point relpoint(arc a, real x)
+{/*<asyxml></code><documentation>Return the relative point of 'a'.
+ If x > 1 or x < 0, the returned point is on the extended arc.</documentation></function></asyxml>*/
+ return point(a, relabscissa(x));
+}
+
+/*<asyxml><function type="point" signature="curpoint(arc,real)"><code></asyxml>*/
+point curpoint(arc a, real x)
+{/*<asyxml></code><documentation>Return the point of 'a' which has the curvilinear abscissa 'x'.
+ If x < 0 or x > arclength(a), the returned point is on the extended arc.</documentation></function></asyxml>*/
+ return point(a, curabscissa(x));
+}
+
+/*<asyxml><function type="abscissa" signature="angabscissa(arc,point)"><code></asyxml>*/
+abscissa angabscissa(arc a, point M)
+{/*<asyxml></code><documentation>Return the angular abscissa of 'M' according to the arc 'a'.</documentation></function></asyxml>*/
+ if(!(M @ a.el))
+ abort("angabscissa: the point is not on the extended arc.");
+ abscissa oa;
+ oa.system = angularsystem;
+ oa.polarconicroutine = a.polarconicroutine;
+ real am = angabscissa(a.el, M, a.polarconicroutine).x;
+ oa.x = (am - a.angle1 - (a.el.e == 0 ? a.angle0 : 0))%360;
+ oa.x = a.direction ? oa.x : 360 - oa.x;
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="curabscissa(arc,point)"><code></asyxml>*/
+abscissa curabscissa(arc a, point M)
+{/*<asyxml></code><documentation>Return the curvilinear abscissa according to the arc 'a'.</documentation></function></asyxml>*/
+ ellipse el = a.el;
+ if(!(M @ el))
+ abort("angabscissa: the point is not on the extended arc.");
+ abscissa oa;
+ oa.system = curvilinearsystem;
+ real xm = curabscissa(el, M).x;
+ real a0 = el.e == 0 ? a.angle0 : 0;
+ real am = curabscissa(el, angpoint(el, a.angle1 + a0, a.polarconicroutine)).x;
+ real l = arclength(el);
+ oa.x = (xm - am)%l;
+ oa.x = a.direction ? oa.x : l - oa.x;
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="nodabscissa(arc,point)"><code></asyxml>*/
+abscissa nodabscissa(arc a, point M)
+{/*<asyxml></code><documentation>Return the node abscissa according to the arc 'a'.</documentation></function></asyxml>*/
+ if(!(M @ a))
+ abort("nodabscissa: the point is not on the arc.");
+ abscissa oa;
+ oa.system = nodesystem;
+ oa.x = intersect((path)a, M)[0];
+ return oa;
+}
+
+/*<asyxml><function type="abscissa" signature="relabscissa(arc,point)"><code></asyxml>*/
+abscissa relabscissa(arc a, point M)
+{/*<asyxml></code><documentation>Return the relative abscissa according to the arc 'a'.</documentation></function></asyxml>*/
+ ellipse el = a.el;
+ if(!( M @ el))
+ abort("relabscissa: the point is not on the prolonged arc.");
+ abscissa oa;
+ oa.system = relativesystem;
+ oa.x = curabscissa(a, M).x/arclength(a);
+ return oa;
+}
+
+/*<asyxml><function type="void" signature="markarc(picture,Label,int,real,real,arc,arrowbar,pen,pen,margin,marker)"><code></asyxml>*/
+void markarc(picture pic = currentpicture,
+ Label L = "",
+ int n = 1, real radius = 0, real space = 0,
+ arc a,
+ pen sectorpen = currentpen,
+ pen markpen = sectorpen,
+ margin margin = NoMargin,
+ arrowbar arrow = None,
+ marker marker = nomarker)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ real Da = degrees(a);
+ pair p1 = point(a, 0);
+ pair p2 = relpoint(a, 1);
+ pair c = a.polarconicroutine == fromCenter ? locate(a.el.C) : locate(a.el.F1);
+ if(radius == 0) radius = markangleradius(markpen);
+ if(abs(Da) > 180) radius = -radius;
+ radius = (a.direction ? 1 : -1) * sgnd(Da) * radius;
+ draw(c--p1^^c--p2, sectorpen);
+ markangle(pic = pic, L = L, n = n, radius = radius, space = space,
+ A = p1, O = c, B = p2,
+ arrow = arrow, p = markpen, margin = margin,
+ marker = marker);
+}
+// *.........................ARCS..........................*
+// *=======================================================*
+
+// *=======================================================*
+// *........................MASSES.........................*
+/*<asyxml><struct signature="mass"><code></asyxml>*/
+struct mass {/*<asyxml></code><documentation></documentation><property type = "point" signature="M"><code></asyxml>*/
+ point M;/*<asyxml></code><documentation></documentation></property><property type = "real" signature="m"><code></asyxml>*/
+ real m;/*<asyxml></code><documentation></documentation></property></asyxml>*/
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><function type="mass" signature="mass(point,real)"><code></asyxml>*/
+mass mass(point M, real m)
+{/*<asyxml></code><documentation>Constructor of mass point.</documentation></function></asyxml>*/
+ mass om;
+ om.M = M;
+ om.m = m;
+ return om;
+}
+
+/*<asyxml><operator type = "point" signature="cast(mass)"><code></asyxml>*/
+point operator cast(mass m)
+{/*<asyxml></code><documentation>Cast mass point to point.</documentation></operator></asyxml>*/
+ point op;
+ op = m.M;
+ op.m = m.m;
+ return op;
+}
+/*<asyxml><function type="point" signature="point(explicit mass)"><code></asyxml>*/
+point point(explicit mass m){return m;}/*<asyxml></code><documentation>Cast
+ 'm' to point</documentation></function></asyxml>*/
+
+/*<asyxml><operator type = "mass" signature="cast(point)"><code></asyxml>*/
+mass operator cast(point M)
+{/*<asyxml></code><documentation>Cast point to mass point.</documentation></operator></asyxml>*/
+ mass om;
+ om.M = M;
+ om.m = M.m;
+ return om;
+}
+/*<asyxml><function type="mass" signature="mass(explicit point)"><code></asyxml>*/
+mass mass(explicit point P)
+{/*<asyxml></code><documentation>Cast 'P' to mass.</documentation></function></asyxml>*/
+ return mass(P, P.m);
+}
+
+/*<asyxml><operator type = "point[]" signature="cast(mass[])"><code></asyxml>*/
+point[] operator cast(mass[] m)
+{/*<asyxml></code><documentation>Cast mass[] to point[].</documentation></operator></asyxml>*/
+ point[] op;
+ for(mass am : m) op.push(point(am));
+ return op;
+}
+
+/*<asyxml><operator type = "mass[]" signature="cast(point[])"><code></asyxml>*/
+mass[] operator cast(point[] P)
+{/*<asyxml></code><documentation>Cast point[] to mass[].</documentation></operator></asyxml>*/
+ mass[] om;
+ for(point op : P) om.push(mass(op));
+ return om;
+}
+
+/*<asyxml><function type="mass" signature="mass(coordsys,explicit pair,real)"><code></asyxml>*/
+mass mass(coordsys R, explicit pair p, real m)
+{/*<asyxml></code><documentation>Return the mass which has coordinates
+ 'p' with respect to 'R' and weight 'm'.</documentation></function></asyxml>*/
+ return point(R, p, m);// Using casting.
+}
+
+/*<asyxml><operator type = "mass" signature="cast(pair)"><code></asyxml>*/
+mass operator cast(pair m){return mass((point)m, 1);}/*<asyxml></code><documentation>Cast pair to mass point.</documentation></operator></asyxml>*/
+/*<asyxml><operator type = "path" signature="cast(mass)"><code></asyxml>*/
+path operator cast(mass M){return M.M;}/*<asyxml></code><documentation>Cast mass point to path.</documentation></operator></asyxml>*/
+/*<asyxml><operator type = "guide" signature="cast(mass)"><code></asyxml>*/
+guide operator cast(mass M){return M.M;}/*<asyxml></code><documentation>Cast mass to guide.</documentation></operator></asyxml>*/
+
+/*<asyxml><operator type = "mass" signature="+(mass,mass)"><code></asyxml>*/
+mass operator +(mass M1, mass M2)
+{/*<asyxml></code><documentation>Provide mass + mass.
+ mass - mass is also defined.</documentation></operator></asyxml>*/
+ return mass(M1.M + M2.M, M1.m + M2.m);
+}
+mass operator -(mass M1, mass M2)
+{
+ return mass(M1.M - M2.M, M1.m - M2.m);
+}
+
+/*<asyxml><operator type = "mass" signature="*(real,mass)"><code></asyxml>*/
+mass operator *(real x, explicit mass M)
+{/*<asyxml></code><documentation>Provide real * mass.
+ The resulted mass is the mass of 'M' multiplied by 'x' .
+ mass/real, mass + real and mass - real are also defined.</documentation></operator></asyxml>*/
+ return mass(M.M, x * M.m);
+}
+mass operator *(int x, explicit mass M){return mass(M.M, x * M.m);}
+mass operator /(explicit mass M, real x){return mass(M.M, M.m/x);}
+mass operator /(explicit mass M, int x){return mass(M.M, M.m/x);}
+mass operator +(explicit mass M, real x){return mass(M.M, M.m + x);}
+mass operator +(explicit mass M, int x){return mass(M.M, M.m + x);}
+mass operator -(explicit mass M, real x){return mass(M.M, M.m - x);}
+mass operator -(explicit mass M, int x){return mass(M.M, M.m - x);}
+/*<asyxml><operator type = "mass" signature="*(transform,mass)"><code></asyxml>*/
+mass operator *(transform t, mass M)
+{/*<asyxml></code><documentation>Provide transform * mass.</documentation></operator></asyxml>*/
+ return mass(t * M.M, M.m);
+}
+
+/*<asyxml><function type="mass" signature="masscenter(... mass[])"><code></asyxml>*/
+mass masscenter(... mass[] M)
+{/*<asyxml></code><documentation>Return the center of the masses 'M'.</documentation></function></asyxml>*/
+ point[] P;
+ for (int i = 0; i < M.length; ++i)
+ P.push(M[i].M);
+ P = standardizecoordsys(currentcoordsys, true ... P);
+ real m = M[0].m;
+ point oM = M[0].m * P[0];
+ for (int i = 1; i < M.length; ++i) {
+ oM += M[i].m * P[i];
+ m += M[i].m;
+ }
+ if (m == 0) abort("masscenter: the sum of masses is null.");
+ return mass(oM/m, m);
+}
+
+/*<asyxml><function type="string" signature="massformat(string,string,mass)"><code></asyxml>*/
+string massformat(string format = defaultmassformat,
+ string s, mass M)
+{/*<asyxml></code><documentation>Return the string formated by 'format' with the mass value.
+ In the parameter 'format', %L will be replaced by 's'.
+ <look href = "#defaultmassformat"/>.</documentation></function></asyxml>*/
+ return format == "" ? s :
+ format(replace(format, "%L", replace(s, "$", "")), M.m);
+}
+
+/*<asyxml><function type="void" signature="label(picture,Label,explicit mass,align,string,pen,filltype)"><code></asyxml>*/
+void label(picture pic = currentpicture, Label L, explicit mass M,
+ align align = NoAlign, string format = defaultmassformat,
+ pen p = nullpen, filltype filltype = NoFill)
+{/*<asyxml></code><documentation>Draw label returned by massformat(format, L, M) at coordinates of M.
+ <look href = "#massformat(string, string, mass)"/>.</documentation></function></asyxml>*/
+ Label lL = L.copy();
+ lL.s = massformat(format, lL.s, M);
+ Label L = Label(lL, M.M, align, p, filltype);
+ add(pic, L);
+}
+
+/*<asyxml><function type="void" signature="dot(picture,Label,explicit mass,align,string,pen)"><code></asyxml>*/
+void dot(picture pic = currentpicture, Label L, explicit mass M, align align = NoAlign,
+ string format = defaultmassformat, pen p = currentpen)
+{/*<asyxml></code><documentation>Draw a dot with label 'L' as
+ label(picture, Label, explicit mass, align, string, pen, filltype) does.
+ <look href = "#label(picture, Label, mass, align, string, pen, filltype)"/>.</documentation></function></asyxml>*/
+ Label lL = L.copy();
+ lL.s = massformat(format, lL.s, M);
+ lL.position(locate(M.M));
+ lL.align(align, E);
+ lL.p(p);
+ dot(pic, M.M, p);
+ add(pic, lL);
+}
+// *........................MASSES.........................*
+// *=======================================================*
+
+// *=======================================================*
+// *.......................TRIANGLES.......................*
+/*<asyxml><function type="point" signature="orthocentercenter(point,point,point)"><code></asyxml>*/
+point orthocentercenter(point A, point B, point C)
+{/*<asyxml></code><documentation>Return the orthocenter of the triangle ABC.</documentation></function></asyxml>*/
+ point[] P = standardizecoordsys(A, B, C);
+ coordsys R = P[0].coordsys;
+ pair pp = extension(A, projection(P[1], P[2]) * P[0], B, projection(P[0], P[2]) * P[1]);
+ return point(R, pp/R);
+}
+
+/*<asyxml><function type="point" signature="centroid(point,point,point)"><code></asyxml>*/
+point centroid(point A, point B, point C)
+{/*<asyxml></code><documentation>Return the centroid of the triangle ABC.</documentation></function></asyxml>*/
+ return (A + B + C)/3;
+}
+
+/*<asyxml><function type="point" signature="incenter(point,point,point)"><code></asyxml>*/
+point incenter(point A, point B, point C)
+{/*<asyxml></code><documentation>Return the center of the incircle of the triangle ABC.</documentation></function></asyxml>*/
+ point[] P = standardizecoordsys(A, B, C);
+ coordsys R = P[0].coordsys;
+ pair a = A, b = B, c = C;
+ pair pp = extension(a, a + dir(a--b, a--c), b, b + dir(b--a, b--c));
+ return point(R, pp/R);
+}
+
+/*<asyxml><function type="real" signature="inradius(point,point,point)"><code></asyxml>*/
+real inradius(point A, point B, point C)
+{/*<asyxml></code><documentation>Return the radius of the incircle of the triangle ABC.</documentation></function></asyxml>*/
+ point IC = incenter(A, B, C);
+ return abs(IC - projection(A, B) * IC);
+}
+
+/*<asyxml><function type="circle" signature="incircle(point,point,point)"><code></asyxml>*/
+circle incircle(point A, point B, point C)
+{/*<asyxml></code><documentation>Return the incircle of the triangle ABC.</documentation></function></asyxml>*/
+ point IC = incenter(A, B, C);
+ return circle(IC, abs(IC - projection(A, B) * IC));
+}
+
+/*<asyxml><function type="point" signature="excenter(point,point,point)"><code></asyxml>*/
+point excenter(point A, point B, point C)
+{/*<asyxml></code><documentation>Return the center of the excircle of the triangle tangent with (AB).</documentation></function></asyxml>*/
+ point[] P = standardizecoordsys(A, B, C);
+ coordsys R = P[0].coordsys;
+ pair a = A, b = B, c = C;
+ pair pp = extension(a, a + rotate(90) * dir(a--b, a--c), b, b + rotate(90) * dir(b--a, b--c));
+ return point(R, pp/R);
+}
+
+/*<asyxml><function type="real" signature="exradius(point,point,point)"><code></asyxml>*/
+real exradius(point A, point B, point C)
+{/*<asyxml></code><documentation>Return the radius of the excircle of the triangle ABC with (AB).</documentation></function></asyxml>*/
+ point EC = excenter(A, B, C);
+ return abs(EC - projection(A, B) * EC);
+}
+
+/*<asyxml><function type="circle" signature="excircle(point,point,point)"><code></asyxml>*/
+circle excircle(point A, point B, point C)
+{/*<asyxml></code><documentation>Return the excircle of the triangle ABC tangent with (AB).</documentation></function></asyxml>*/
+ point center = excenter(A, B, C);
+ real radius = abs(center - projection(B, C) * center);
+ return circle(center, radius);
+}
+
+private int[] numarray = {1, 2, 3};
+numarray.cyclic = true;
+
+/*<asyxml><struct signature="triangle"><code></asyxml>*/
+struct triangle {/*<asyxml></code><documentation></documentation></asyxml>*/
+
+ /*<asyxml><struct signature="vertex"><code></asyxml>*/
+ struct vertex {/*<asyxml></code><documentation>Structure used to communicate the vertex of a triangle.</documentation><property type = "int" signature="n"><code></asyxml>*/
+ int n;/*<asyxml></code><documentation>1 means VA,2 means VB,3 means VC,4 means VA etc...</documentation></property><property type = "triangle" signature="triangle"><code></asyxml>*/
+ triangle t;/*<asyxml></code><documentation>The triangle to which the vertex refers.</documentation></property></asyxml>*/
+ }/*<asyxml></struct></asyxml>*/
+
+ /*<asyxml><property type = "point" signature="A,B,C"><code></asyxml>*/
+ restricted point A, B, C;/*<asyxml></code><documentation>The vertices of the triangle (as point).</documentation></property><property type = "vertex" signature="VA, VB, VC"><code></asyxml>*/
+ restricted vertex VA, VB, VC;/*<asyxml></code><documentation>The vertices of the triangle (as vertex).
+ Note that the vertex structure contains the triangle to wish it refers.</documentation></property></asyxml>*/
+ VA.n = 1;VB.n = 2;VC.n = 3;
+
+ /*<asyxml><method type = "vertex" signature="vertex(int)"><code></asyxml>*/
+ vertex vertex(int n)
+ {/*<asyxml></code><documentation>Return numbered vertex.
+ 'n' is 1 means VA, 2 means VB, 3 means VC, 4 means VA etc...</documentation></method></asyxml>*/
+ n = numarray[n - 1];
+ if(n == 1) return VA;
+ else if(n == 2) return VB;
+ return VC;
+ }
+
+ /*<asyxml><method type = "point" signature="point(int)"><code></asyxml>*/
+ point point(int n)
+ {/*<asyxml></code><documentation>Return numbered point.
+ n is 1 means A, 2 means B, 3 means C, 4 means A etc...</documentation></method></asyxml>*/
+ n = numarray[n - 1];
+ if(n == 1) return A;
+ else if(n == 2) return B;
+ return C;
+ }
+
+ /*<asyxml><method type = "void" signature="init(point,point,point)"><code></asyxml>*/
+ void init(point A, point B, point C)
+ {/*<asyxml></code><documentation>Constructor.</documentation></method></asyxml>*/
+ point[] P = standardizecoordsys(A, B, C);
+ this.A = P[0];
+ this.B = P[1];
+ this.C = P[2];
+ VA.t = this; VB.t = this; VC.t = this;
+ }
+
+ /*<asyxml><method type = "void" signature="operator init(point,point,point)"><code></asyxml>*/
+ void operator init(point A, point B, point C)
+ {/*<asyxml></code><documentation>For backward compatibility.
+ Provide the routine 'triangle(point A, point B, point C)'.</documentation></method></asyxml>*/
+ this.init(A, B, C);
+ }
+
+ /*<asyxml><method type = "void" signature="init(real,real,real,real,point)"><code></asyxml>*/
+ void operator init(real b, real alpha, real c, real angle = 0, point A = (0, 0))
+ {/*<asyxml></code><documentation>For backward compatibility.
+ Provide the routine 'triangle(real b, real alpha, real c, real angle = 0, point A = (0, 0))
+ which returns the triangle ABC rotated by 'angle' (in degrees) and where b = AC, degrees(A) = alpha, AB = c.</documentation></method></asyxml>*/
+ coordsys R = A.coordsys;
+ this.init(A, A + R.polar(c, radians(angle)), A + R.polar(b, radians(angle + alpha)));
+ }
+
+ /*<asyxml><method type = "real" signature="a(),b(),c()"><code></asyxml>*/
+ real a()
+ {/*<asyxml></code><documentation>Return the length BC.
+ b() and c() are also defined and return the length AC and AB respectively.</documentation></method></asyxml>*/
+ return length(C - B);
+ }
+ real b() {return length(A - C);}
+ real c() {return length(B - A);}
+
+ private real det(pair a, pair b) {return a.x * b.y - a.y * b.x;}
+
+ /*<asyxml><method type = "real" signature="area()"><code></asyxml>*/
+ real area()
+ {/*<asyxml></code><documentation></documentation></method></asyxml>*/
+ pair a = locate(A), b = locate(B), c = locate(C);
+ return 0.5 * abs(det(a, b) + det(b, c) + det(c, a));
+ }
+
+ /*<asyxml><method type = "real" signature="alpha(),beta(),gamma()"><code></asyxml>*/
+ real alpha()
+ {/*<asyxml></code><documentation>Return the measure (in degrees) of the angle A.
+ beta() and gamma() are also defined and return the measure of the angles B and C respectively.</documentation></method></asyxml>*/
+ return degrees(acos((b()^2 + c()^2 - a()^2)/(2b() * c())));
+ }
+ real beta() {return degrees(acos((c()^2 + a()^2 - b()^2)/(2c() * a())));}
+ real gamma() {return degrees(acos((a()^2 + b()^2 - c()^2)/(2a() * b())));}
+
+ /*<asyxml><method type = "path" signature="Path()"><code></asyxml>*/
+ path Path()
+ {/*<asyxml></code><documentation>The path of the triangle.</documentation></method></asyxml>*/
+ return A--C--B--cycle;
+ }
+
+ /*<asyxml><struct signature="side"><code></asyxml>*/
+ struct side
+ {/*<asyxml></code><documentation>Structure used to communicate the side of a triangle.</documentation><property type = "int" signature="n"><code></asyxml>*/
+ int n;/*<asyxml></code><documentation>1 or 0 means [AB],-1 means [BA],2 means [BC],-2 means [CB] etc.</documentation></property><property type = "triangle" signature="triangle"><code></asyxml>*/
+ triangle t;/*<asyxml></code><documentation>The triangle to which the side refers.</documentation></property></asyxml>*/
+ }/*<asyxml></struct></asyxml>*/
+
+ /*<asyxml><property type = "side" signature="AB"><code></asyxml>*/
+ side AB;/*<asyxml></code><documentation>For the routines using the structure 'side', triangle.AB means 'side AB'.
+ BA, AC, CA etc are also defined.</documentation></property></asyxml>*/
+ AB.n = 1; AB.t = this;
+ side BA; BA.n = -1; BA.t = this;
+ side BC; BC.n = 2; BC.t = this;
+ side CB; CB.n = -2; CB.t = this;
+ side CA; CA.n = 3; CA.t = this;
+ side AC; AC.n = -3; AC.t = this;
+
+ /*<asyxml><method type = "side" signature="side(int)"><code></asyxml>*/
+ side side(int n)
+ {/*<asyxml></code><documentation>Return numbered side.
+ n is 1 means AB, -1 means BA, 2 means BC, -2 means CB, etc.</documentation></method></asyxml>*/
+ if(n == 0) abort('Invalid side number.');
+ int an = numarray[abs(n)-1];
+ if(an == 1) return n > 0 ? AB : BA;
+ else if(an == 2) return n > 0 ? BC : CB;
+ return n > 0 ? CA : AC;
+ }
+
+ /*<asyxml><method type = "line" signature="line(int)"><code></asyxml>*/
+ line line(int n)
+ {/*<asyxml></code><documentation>Return the numbered line.</documentation></method></asyxml>*/
+ if(n == 0) abort('Invalid line number.');
+ int an = numarray[abs(n)-1];
+ if(an == 1) return n > 0 ? line(A, B) : line(B, A);
+ else if(an == 2) return n > 0 ? line(B, C) : line(C, B);
+ return n > 0 ? line(C, A) : line(A, C);
+ }
+
+}/*<asyxml></struct></asyxml>*/
+
+from triangle unravel side; // The structure 'side' is now available outside the triangle structure.
+from triangle unravel vertex; // The structure 'vertex' is now available outside the triangle structure.
+
+triangle[] operator ^^(triangle[] t1, triangle t2)
+{
+ triangle[] T;
+ for (int i = 0; i < t1.length; ++i) T.push(t1[i]);
+ T.push(t2);
+ return T;
+}
+
+triangle[] operator ^^(... triangle[] t)
+{
+ triangle[] T;
+ for (int i = 0; i < t.length; ++i) {
+ T.push(t[i]);
+ }
+ return T;
+}
+
+/*<asyxml><operator type = "line" signature="cast(side)"><code></asyxml>*/
+line operator cast(side side)
+{/*<asyxml></code><documentation>Cast side to (infinite) line.
+ Most routine with line parameters works with side parameters.
+ One can use the code 'segment(a_side)' to obtain a line segment.</documentation></operator></asyxml>*/
+ triangle t = side.t;
+ return t.line(side.n);
+}
+
+/*<asyxml><function type="line" signature="line(explicit side)"><code></asyxml>*/
+line line(explicit side side)
+{/*<asyxml></code><documentation>Return 'side' as line.</documentation></function></asyxml>*/
+ return (line)side;
+}
+
+/*<asyxml><function type="segment" signature="segment(explicit side)"><code></asyxml>*/
+segment segment(explicit side side)
+{/*<asyxml></code><documentation>Return 'side' as segment.</documentation></function></asyxml>*/
+ return (segment)(line)side;
+}
+
+/*<asyxml><operator type = "point" signature="cast(vertex)"><code></asyxml>*/
+point operator cast(vertex V)
+{/*<asyxml></code><documentation>Cast vertex to point.
+ Most routine with point parameters works with vertex parameters.</documentation></operator></asyxml>*/
+ return V.t.point(V.n);
+}
+
+/*<asyxml><function type="point" signature="point(explicit vertex)"><code></asyxml>*/
+point point(explicit vertex V)
+{/*<asyxml></code><documentation>Return the point corresponding to the vertex 'V'.</documentation></function></asyxml>*/
+ return (point)V;
+}
+
+/*<asyxml><function type="side" signature="opposite(vertex)"><code></asyxml>*/
+side opposite(vertex V)
+{/*<asyxml></code><documentation>Return the opposite side of vertex 'V'.</documentation></function></asyxml>*/
+ return V.t.side(numarray[abs(V.n)]);
+}
+
+/*<asyxml><function type="vertex" signature="opposite(side)"><code></asyxml>*/
+vertex opposite(side side)
+{/*<asyxml></code><documentation>Return the opposite vertex of side 'side'.</documentation></function></asyxml>*/
+ return side.t.vertex(numarray[abs(side.n) + 1]);
+}
+
+/*<asyxml><function type="point" signature="midpoint(side)"><code></asyxml>*/
+point midpoint(side side)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return midpoint(segment(side));
+}
+
+/*<asyxml><operator type = "triangle" signature="*(transform,triangle)"><code></asyxml>*/
+triangle operator *(transform T, triangle t)
+{/*<asyxml></code><documentation>Provide transform * triangle.</documentation></operator></asyxml>*/
+ return triangle(T * t.A, T * t.B, T * t.C);
+}
+
+/*<asyxml><function type="triangle" signature="triangleAbc(real,real,real,real,point)"><code></asyxml>*/
+triangle triangleAbc(real alpha, real b, real c, real angle = 0, point A = (0, 0))
+{/*<asyxml></code><documentation>Return the triangle ABC rotated by 'angle' with BAC = alpha, AC = b and AB = c.</documentation></function></asyxml>*/
+ triangle T;
+ coordsys R = A.coordsys;
+ T.init(A, A + R.polar(c, radians(angle)), A + R.polar(b, radians(angle + alpha)));
+ return T;
+}
+
+/*<asyxml><function type="triangle" signature="triangleabc(real,real,real,real,point)"><code></asyxml>*/
+triangle triangleabc(real a, real b, real c, real angle = 0, point A = (0, 0))
+{/*<asyxml></code><documentation>Return the triangle ABC rotated by 'angle' with BC = a, AC = b and AB = c.</documentation></function></asyxml>*/
+ triangle T;
+ coordsys R = A.coordsys;
+ T.init(A, A + R.polar(c, radians(angle)), A + R.polar(b, radians(angle) + acos((b^2 + c^2 - a^2)/(2 * b * c))));
+ return T;
+}
+
+/*<asyxml><function type="triangle" signature="triangle(line,line,line)"><code></asyxml>*/
+triangle triangle(line l1, line l2, line l3)
+{/*<asyxml></code><documentation>Return the triangle defined by three line.</documentation></function></asyxml>*/
+ point P1, P2, P3;
+ P1 = intersectionpoint(l1, l2);
+ P2 = intersectionpoint(l1, l3);
+ P3 = intersectionpoint(l2, l3);
+ if(!(defined(P1) && defined(P2) && defined(P3))) abort("triangle: two lines are parallel.");
+ return triangle(P1, P2, P3);
+}
+
+/*<asyxml><function type="point" signature="foot(vertex)"><code></asyxml>*/
+point foot(vertex V)
+{/*<asyxml></code><documentation>Return the endpoint of the altitude from V.</documentation></function></asyxml>*/
+ return projection((line)opposite(V)) * ((point)V);
+}
+
+/*<asyxml><function type="point" signature="foot(side)"><code></asyxml>*/
+point foot(side side)
+{/*<asyxml></code><documentation>Return the endpoint of the altitude on 'side'.</documentation></function></asyxml>*/
+ return projection((line)side) * point(opposite(side));
+}
+
+/*<asyxml><function type="line" signature="altitude(vertex)"><code></asyxml>*/
+line altitude(vertex V)
+{/*<asyxml></code><documentation>Return the altitude passing through 'V'.</documentation></function></asyxml>*/
+ return line(point(V), foot(V));
+}
+
+/*<asyxml><function type="line" signature="altitude(vertex)"><code></asyxml>*/
+line altitude(side side)
+{/*<asyxml></code><documentation>Return the altitude cutting 'side'.</documentation></function></asyxml>*/
+ return altitude(opposite(side));
+}
+
+/*<asyxml><function type="point" signature="orthocentercenter(triangle)"><code></asyxml>*/
+point orthocentercenter(triangle t)
+{/*<asyxml></code><documentation>Return the orthocenter of the triangle t.</documentation></function></asyxml>*/
+ return orthocentercenter(t.A, t.B, t.C);
+}
+
+/*<asyxml><function type="point" signature="centroid(triangle)"><code></asyxml>*/
+point centroid(triangle t)
+{/*<asyxml></code><documentation>Return the centroid of the triangle 't'.</documentation></function></asyxml>*/
+ return (t.A + t.B + t.C)/3;
+}
+
+/*<asyxml><function type="point" signature="circumcenter(triangle)"><code></asyxml>*/
+point circumcenter(triangle t)
+{/*<asyxml></code><documentation>Return the circumcenter of the triangle 't'.</documentation></function></asyxml>*/
+ return circumcenter(t.A, t.B, t.C);
+}
+
+/*<asyxml><function type="circle" signature="circle(triangle)"><code></asyxml>*/
+circle circle(triangle t)
+{/*<asyxml></code><documentation>Return the circumcircle of the triangle 't'.</documentation></function></asyxml>*/
+ return circle(t.A, t.B, t.C);
+}
+
+/*<asyxml><function type="circle" signature="circumcircle(triangle)"><code></asyxml>*/
+circle circumcircle(triangle t)
+{/*<asyxml></code><documentation>Return the circumcircle of the triangle 't'.</documentation></function></asyxml>*/
+ return circle(t.A, t.B, t.C);
+}
+
+/*<asyxml><function type="point" signature="incenter(triangle)"><code></asyxml>*/
+point incenter(triangle t)
+{/*<asyxml></code><documentation>Return the center of the incircle of the triangle 't'.</documentation></function></asyxml>*/
+ return incenter(t.A, t.B, t.C);
+}
+
+/*<asyxml><function type="real" signature="inradius(triangle)"><code></asyxml>*/
+real inradius(triangle t)
+{/*<asyxml></code><documentation>Return the radius of the incircle of the triangle 't'.</documentation></function></asyxml>*/
+ return inradius(t.A, t.B, t.C);
+}
+
+/*<asyxml><function type="circle" signature="incircle(triangle)"><code></asyxml>*/
+circle incircle(triangle t)
+{/*<asyxml></code><documentation>Return the the incircle of the triangle 't'.</documentation></function></asyxml>*/
+ return incircle(t.A, t.B, t.C);
+}
+
+/*<asyxml><function type="point" signature="excenter(side,triangle)"><code></asyxml>*/
+point excenter(side side)
+{/*<asyxml></code><documentation>Return the center of the excircle tangent with the side 'side' of its triangle.
+ side = 0 means AB, 1 means AC, other means BC.
+ One must use the predefined sides t.AB, t.AC where 't' is a triangle....</documentation></function></asyxml>*/
+ point op;
+ triangle t = side.t;
+ int n = numarray[abs(side.n) - 1];
+ if(n == 1) op = excenter(t.A, t.B, t.C);
+ else if(n == 2) op = excenter(t.B, t.C, t.A);
+ else op = excenter(t.C, t.A, t.B);
+ return op;
+}
+
+/*<asyxml><function type="real" signature="exradius(side,triangle)"><code></asyxml>*/
+real exradius(side side)
+{/*<asyxml></code><documentation>Return radius of the excircle tangent with the side 'side' of its triangle.
+ side = 0 means AB, 1 means BC, other means CA.
+ One must use the predefined sides t.AB, t.AC where 't' is a triangle....</documentation></function></asyxml>*/
+ real or;
+ triangle t = side.t;
+ int n = numarray[abs(side.n) - 1];
+ if(n == 1) or = exradius(t.A, t.B, t.C);
+ else if(n == 2) or = exradius(t.B, t.C, t.A);
+ else or = exradius(t.A, t.C, t.B);
+ return or;
+}
+
+/*<asyxml><function type="circle" signature="excircle(side,triangle)"><code></asyxml>*/
+circle excircle(side side)
+{/*<asyxml></code><documentation>Return the excircle tangent with the side 'side' of its triangle.
+ side = 0 means AB, 1 means AC, other means BC.
+ One must use the predefined sides t.AB, t.AC where 't' is a triangle....</documentation></function></asyxml>*/
+ circle oc;
+ int n = numarray[abs(side.n) - 1];
+ triangle t = side.t;
+ if(n == 1) oc = excircle(t.A, t.B, t.C);
+ else if(n == 2) oc = excircle(t.B, t.C, t.A);
+ else oc = excircle(t.A, t.C, t.B);
+ return oc;
+}
+
+/*<asyxml><struct signature="trilinear"><code></asyxml>*/
+struct trilinear
+{/*<asyxml></code><documentation>Trilinear coordinates 'a:b:c' relative to triangle 't'.
+ <url href = "http://mathworld.wolfram.com/TrilinearCoordinates.html"/></documentation><property type = "real" signature="a,b,c"><code></asyxml>*/
+ real a,b,c;/*<asyxml></code><documentation>The trilinear coordinates.</documentation></property><property type = "triangle" signature="t"><code></asyxml>*/
+ triangle t;/*<asyxml></code><documentation>The reference triangle.</documentation></property></asyxml>*/
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><function type="trilinear" signature="trilinear(triangle,real,real,real)"><code></asyxml>*/
+trilinear trilinear(triangle t, real a, real b, real c)
+{/*<asyxml></code><documentation>Return the trilinear coordinates relative to 't'.
+ <url href = "http://mathworld.wolfram.com/TrilinearCoordinates.html"/></documentation></function></asyxml>*/
+ trilinear ot;
+ ot.a = a; ot.b = b; ot.c = c;
+ ot.t = t;
+ return ot;
+}
+
+/*<asyxml><function type="trilinear" signature="trilinear(triangle,point)"><code></asyxml>*/
+trilinear trilinear(triangle t, point M)
+{/*<asyxml></code><documentation>Return the trilinear coordinates of 'M' relative to 't'.
+ <url href = "http://mathworld.wolfram.com/TrilinearCoordinates.html"/></documentation></function></asyxml>*/
+ trilinear ot;
+ pair m = locate(M);
+ int sameside(pair A, pair B, pair m, pair p)
+ {// Return 1 if 'm' and 'p' are same side of line (AB) else return -1.
+ pair mil = (A + B)/2;
+ pair mA = rotate(90, mil) * A;
+ pair mB = rotate(-90, mil) * A;
+ return (abs(m - mA) <= abs(m - mB)) == (abs(p - mA) <= abs(p - mB)) ? 1 : -1;
+ }
+ real det(pair a, pair b) {return a.x * b.y - a.y * b.x;}
+ real area(pair a, pair b, pair c){return 0.5 * abs(det(a, b) + det(b, c) + det(c, a));}
+ pair A = t.A, B = t.B, C = t.C;
+ real t1 = area(B, C, m), t2 = area(C, A, m), t3 = area(A, B, m);
+ ot.a = sameside(B, C, A, m) * t1/t.a();
+ ot.b = sameside(A, C, B, m) * t2/t.b();
+ ot.c = sameside(A, B, C, m) * t3/t.c();
+ ot.t = t;
+ return ot;
+}
+
+/*<asyxml><function type="void" signature="write(trilinear)"><code></asyxml>*/
+void write(trilinear tri)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ write(format("%f : ", tri.a) + format("%f : ", tri.b) + format("%f", tri.c));
+}
+
+/*<asyxml><function type="point" signature="trilinear(triangle,real,real,real)"><code></asyxml>*/
+point point(trilinear tri)
+{/*<asyxml></code><documentation>Return the trilinear coordinates relative to 't'.
+ <url href = "http://mathworld.wolfram.com/TrilinearCoordinates.html"/></documentation></function></asyxml>*/
+ triangle t = tri.t;
+ return masscenter(0.5 * t.a() * mass(t.A, tri.a),
+ 0.5 * t.b() * mass(t.B, tri.b),
+ 0.5 * t.c() * mass(t.C, tri.c));
+}
+
+/*<asyxml><function type="int[]" signature="tricoef(side)"><code></asyxml>*/
+int[] tricoef(side side)
+{/*<asyxml></code><documentation>Return an array of integer (values are 0 or 1) which represents 'side'.
+ For example, side = t.BC will be represented by {0, 1, 1}.</documentation></function></asyxml>*/
+ int[] oi;
+ int n = numarray[abs(side.n) - 1];
+ oi.push((n == 1 || n == 3) ? 1 : 0);
+ oi.push((n == 1 || n == 2) ? 1 : 0);
+ oi.push((n == 2 || n == 3) ? 1 : 0);
+ return oi;
+}
+
+/*<asyxml><operator type = "point" signature="cast(trilinear)"><code></asyxml>*/
+point operator cast(trilinear tri)
+{/*<asyxml></code><documentation>Cast trilinear to point.
+ One may use the routine 'point(trilinear)' to force the casting.</documentation></operator></asyxml>*/
+ return point(tri);
+}
+
+/*<asyxml><typedef type = "centerfunction" return = "real" params = "real, real, real"><code></asyxml>*/
+typedef real centerfunction(real, real, real);/*<asyxml></code><documentation><url href = "http://mathworld.wolfram.com/TriangleCenterFunction.html"/></documentation></typedef></asyxml>*/
+
+/*<asyxml><function type="trilinear" signature="trilinear(triangle,centerfunction,real,real,real)"><code></asyxml>*/
+trilinear trilinear(triangle t, centerfunction f, real a = t.a(), real b = t.b(), real c = t.c())
+{/*<asyxml></code><documentation><url href = "http://mathworld.wolfram.com/TriangleCenterFunction.html"/></documentation></function></asyxml>*/
+ return trilinear(t, f(a, b, c), f(b, c, a), f(c, a, b));
+}
+
+/*<asyxml><function type="point" signature="symmedian(triangle)"><code></asyxml>*/
+point symmedian(triangle t)
+{/*<asyxml></code><documentation>Return the symmedian point of 't'.</documentation></function></asyxml>*/
+ point A, B, C;
+ real a = t.a(), b = t.b(), c = t.c();
+ A = trilinear(t, 0, b, c);
+ B = trilinear(t, a, 0, c);
+ return intersectionpoint(line(t.A, A), line(t.B, B));
+}
+
+/*<asyxml><function type="point" signature="symmedian(side)"><code></asyxml>*/
+point symmedian(side side)
+{/*<asyxml></code><documentation>The symmedian point on the side 'side'.</documentation></function></asyxml>*/
+ triangle t = side.t;
+ int n = numarray[abs(side.n) - 1];
+ if(n == 1) return trilinear(t, t.a(), t.b(), 0);
+ if(n == 2) return trilinear(t, 0, t.b(), t.c());
+ return trilinear(t, t.a(), 0, t.c());
+}
+
+/*<asyxml><function type="line" signature="symmedian(vertex)"><code></asyxml>*/
+line symmedian(vertex V)
+{/*<asyxml></code><documentation>Return the symmedian passing through 'V'.</documentation></function></asyxml>*/
+ return line(point(V), symmedian(V.t));
+}
+
+/*<asyxml><function type="triangle" signature="cevian(triangle,point)"><code></asyxml>*/
+triangle cevian(triangle t, point P)
+{/*<asyxml></code><documentation>Return the Cevian triangle with respect of 'P'
+ <url href = "http://mathworld.wolfram.com/CevianTriangle.html"/>.</documentation></function></asyxml>*/
+ trilinear tri = trilinear(t, locate(P));
+ point A = point(trilinear(t, 0, tri.b, tri.c));
+ point B = point(trilinear(t, tri.a, 0, tri.c));
+ point C = point(trilinear(t, tri.a, tri.b, 0));
+ return triangle(A, B, C);
+}
+
+/*<asyxml><function type="point" signature="cevian(side,point)"><code></asyxml>*/
+point cevian(side side, point P)
+{/*<asyxml></code><documentation>Return the Cevian point on 'side' with respect of 'P'.</documentation></function></asyxml>*/
+ triangle t = side.t;
+ trilinear tri = trilinear(t, locate(P));
+ int[] s = tricoef(side);
+ return point(trilinear(t, s[0] * tri.a, s[1] * tri.b, s[2] * tri.c));
+}
+
+/*<asyxml><function type="line" signature="cevian(vertex,point)"><code></asyxml>*/
+line cevian(vertex V, point P)
+{/*<asyxml></code><documentation>Return line passing through 'V' and its Cevian image with respect of 'P'.</documentation></function></asyxml>*/
+ return line(point(V), cevian(opposite(V), P));
+}
+
+/*<asyxml><function type="point" signature="gergonne(triangle)"><code></asyxml>*/
+point gergonne(triangle t)
+{/*<asyxml></code><documentation>Return the Gergonne point of 't'.</documentation></function></asyxml>*/
+ real f(real a, real b, real c){return 1/(a * (b + c - a));}
+ return point(trilinear(t, f));
+}
+
+/*<asyxml><function type="point[]" signature="fermat(triangle)"><code></asyxml>*/
+point[] fermat(triangle t)
+{/*<asyxml></code><documentation>Return the Fermat points of 't'.</documentation></function></asyxml>*/
+ point[] P;
+ real A = t.alpha(), B = t.beta(), C = t.gamma();
+ P.push(point(trilinear(t, 1/Sin(A + 60), 1/Sin(B + 60), 1/Sin(C + 60))));
+ P.push(point(trilinear(t, 1/Sin(A - 60), 1/Sin(B - 60), 1/Sin(C - 60))));
+ return P;
+}
+
+/*<asyxml><function type="point" signature="isotomicconjugate(triangle,point)"><code></asyxml>*/
+point isotomicconjugate(triangle t, point M)
+{/*<asyxml></code><documentation><url href = "http://mathworld.wolfram.com/IsotomicConjugate.html"/></documentation></function></asyxml>*/
+ if(!inside(t.Path(), locate(M))) abort("isotomic: the point must be inside the triangle.");
+ trilinear tr = trilinear(t, M);
+ return point(trilinear(t, 1/(t.a()^2 * tr.a), 1/(t.b()^2 * tr.b), 1/(t.c()^2 * tr.c)));
+}
+
+/*<asyxml><function type="line" signature="isotomic(vertex,point)"><code></asyxml>*/
+line isotomic(vertex V, point M)
+{/*<asyxml></code><documentation><url href = "http://mathworld.wolfram.com/IsotomicConjugate.html"/>.</documentation></function></asyxml>*/
+ side op = opposite(V);
+ return line(V, rotate(180, midpoint(op)) * cevian(op, M));
+}
+
+/*<asyxml><function type="point" signature="isotomic(side,point)"><code></asyxml>*/
+point isotomic(side side, point M)
+{/*<asyxml></code><documentation><url href = "http://mathworld.wolfram.com/IsotomicConjugate.html"/></documentation></function></asyxml>*/
+ return intersectionpoint(isotomic(opposite(side), M), side);
+}
+
+/*<asyxml><function type="triangle" signature="isotomic(triangle,point)"><code></asyxml>*/
+triangle isotomic(triangle t, point M)
+{/*<asyxml></code><documentation><url href = "http://mathworld.wolfram.com/IsotomicConjugate.html"/></documentation></function></asyxml>*/
+ return triangle(isotomic(t.BC, M), isotomic(t.CA, M), isotomic(t.AB, M));
+}
+
+/*<asyxml><function type="point" signature="isogonalconjugate(triangle,point)"><code></asyxml>*/
+point isogonalconjugate(triangle t, point M)
+{/*<asyxml></code><documentation><url href = "http://mathworld.wolfram.com/IsogonalConjugate.html"/></documentation></function></asyxml>*/
+ trilinear tr = trilinear(t, M);
+ return point(trilinear(t, 1/tr.a, 1/tr.b, 1/tr.c));
+}
+
+/*<asyxml><function type="point" signature="isogonal(side,point)"><code></asyxml>*/
+point isogonal(side side, point M)
+{/*<asyxml></code><documentation><url href = "http://mathworld.wolfram.com/IsogonalConjugate.html"/></documentation></function></asyxml>*/
+ return cevian(side, isogonalconjugate(side.t, M));
+}
+
+/*<asyxml><function type="line" signature="isogonal(vertex,point)"><code></asyxml>*/
+line isogonal(vertex V, point M)
+{/*<asyxml></code><documentation><url href = "http://mathworld.wolfram.com/IsogonalConjugate.html"/></documentation></function></asyxml>*/
+ return line(V, isogonal(opposite(V), M));
+}
+
+/*<asyxml><function type="triangle" signature="isogonal(triangle,point)"><code></asyxml>*/
+triangle isogonal(triangle t, point M)
+{/*<asyxml></code><documentation><url href = "http://mathworld.wolfram.com/IsogonalConjugate.html"/></documentation></function></asyxml>*/
+ return triangle(isogonal(t.BC, M), isogonal(t.CA, M), isogonal(t.AB, M));
+}
+
+/*<asyxml><function type="triangle" signature="pedal(triangle,point)"><code></asyxml>*/
+triangle pedal(triangle t, point M)
+{/*<asyxml></code><documentation>Return the pedal triangle of 'M' in 't'.
+ <url href = "http://mathworld.wolfram.com/PedalTriangle.html"/></documentation></function></asyxml>*/
+ return triangle(projection(t.BC) * M, projection(t.AC) * M, projection(t.AB) * M);
+}
+
+/*<asyxml><function type="triangle" signature="pedal(triangle,point)"><code></asyxml>*/
+line pedal(side side, point M)
+{/*<asyxml></code><documentation>Return the pedal line of 'M' cutting 'side'.
+ <url href = "http://mathworld.wolfram.com/PedalTriangle.html"/></documentation></function></asyxml>*/
+ return line(M, projection(side) * M);
+}
+
+/*<asyxml><function type="triangle" signature="antipedal(triangle,point)"><code></asyxml>*/
+triangle antipedal(triangle t, point M)
+{/*<asyxml></code><documentation><url href = "http://mathworld.wolfram.com/AntipedalTriangle.html"/></documentation></function></asyxml>*/
+ trilinear Tm = trilinear(t, M);
+ real a = Tm.a, b = Tm.b, c = Tm.c;
+ real CA = Cos(t.alpha()), CB = Cos(t.beta()), CC = Cos(t.gamma());
+ point A = trilinear(t, -(b + a * CC) * (c + a * CB), (c + a * CB) * (a + b * CC), (b + a * CC) * (a + c * CB));
+ point B = trilinear(t, (c + b * CA) * (b + a * CC), -(c + b * CA) * (a + b * CC), (a + b * CC) * (b + c * CA));
+ point C = trilinear(t, (b + c * CA) * (c + a * CB), (a + c * CB) * (c + b * CA), -(a + c * CB) * (b + c * CA));
+ return triangle(A, B, C);
+}
+
+/*<asyxml><function type="triangle" signature="extouch(triangle)"><code></asyxml>*/
+triangle extouch(triangle t)
+{/*<asyxml></code><documentation>Return the extouch triangle of the triangle 't'.
+ The extouch triangle of 't' is the triangle formed by the points
+ of tangency of a triangle 't' with its excircles.</documentation></function></asyxml>*/
+ point A, B, C;
+ real a = t.a(), b = t.b(), c = t.c();
+ A = trilinear(t, 0, (a - b + c)/b, (a + b - c)/c);
+ B = trilinear(t, (-a + b + c)/a, 0, (a + b - c)/c);
+ C = trilinear(t, (-a + b + c)/a, (a - b + c)/b, 0);
+ return triangle(A, B, C);
+}
+
+/*<asyxml><function type="triangle" signature="extouch(triangle)"><code></asyxml>*/
+triangle incentral(triangle t)
+{/*<asyxml></code><documentation>Return the incentral triangle of the triangle 't'.
+ It is the triangle whose vertices are determined by the intersections of the
+ reference triangle's angle bisectors with the respective opposite sides.</documentation></function></asyxml>*/
+ point A, B, C;
+ // real a = t.a(), b = t.b(), c = t.c();
+ A = trilinear(t, 0, 1, 1);
+ B = trilinear(t, 1, 0, 1);
+ C = trilinear(t, 1, 1, 0);
+ return triangle(A, B, C);
+}
+
+/*<asyxml><function type="triangle" signature="extouch(side)"><code></asyxml>*/
+triangle extouch(side side)
+{/*<asyxml></code><documentation>Return the triangle formed by the points of tangency of the triangle referenced by 'side' with its excircles.
+ One vertex of the returned triangle is on the segment 'side'.</documentation></function></asyxml>*/
+ triangle t = side.t;
+ transform p1 = projection((line)t.AB);
+ transform p2 = projection((line)t.AC);
+ transform p3 = projection((line)t.BC);
+ point EP = excenter(side);
+ return triangle(p3 * EP, p2 * EP, p1 * EP);
+}
+
+/*<asyxml><function type="point" signature="bisectorpoint(side)"><code></asyxml>*/
+point bisectorpoint(side side)
+{/*<asyxml></code><documentation>The intersection point of the angle bisector from the
+ opposite point of 'side' with the side 'side'.</documentation></function></asyxml>*/
+ triangle t = side.t;
+ int n = numarray[abs(side.n) - 1];
+ if(n == 1) return trilinear(t, 1, 1, 0);
+ if(n == 2) return trilinear(t, 0, 1, 1);
+ return trilinear(t, 1, 0, 1);
+}
+
+/*<asyxml><function type="line" signature="bisector(vertex,real)"><code></asyxml>*/
+line bisector(vertex V, real angle = 0)
+{/*<asyxml></code><documentation>Return the interior bisector passing through 'V' rotated by angle (in degrees)
+ around 'V'.</documentation></function></asyxml>*/
+ return rotate(angle, point(V)) * line(point(V), incenter(V.t));
+}
+
+/*<asyxml><function type="line" signature="bisector(side)"><code></asyxml>*/
+line bisector(side side)
+{/*<asyxml></code><documentation>Return the bisector of the line segment 'side'.</documentation></function></asyxml>*/
+ return bisector(segment(side));
+}
+
+/*<asyxml><function type="point" signature="intouch(side)"><code></asyxml>*/
+point intouch(side side)
+{/*<asyxml></code><documentation>The point of tangency on the side 'side' of its incircle.</documentation></function></asyxml>*/
+ triangle t = side.t;
+ real a = t.a(), b = t.b(), c = t.c();
+ int n = numarray[abs(side.n) - 1];
+ if(n == 1) return trilinear(t, b * c/(-a + b + c), a * c/(a - b + c), 0);
+ if(n == 2) return trilinear(t, 0, a * c/(a - b + c), a * b/(a + b - c));
+ return trilinear(t, b * c/(-a + b + c), 0, a * b/(a + b - c));
+}
+
+/*<asyxml><function type="triangle" signature="intouch(triangle)"><code></asyxml>*/
+triangle intouch(triangle t)
+{/*<asyxml></code><documentation>Return the intouch triangle of the triangle 't'.
+ The intouch triangle of 't' is the triangle formed by the points
+ of tangency of a triangle 't' with its incircles.</documentation></function></asyxml>*/
+ point A, B, C;
+ real a = t.a(), b = t.b(), c = t.c();
+ A = trilinear(t, 0, a * c/(a - b + c), a * b/(a + b - c));
+ B = trilinear(t, b * c/(-a + b + c), 0, a * b/(a + b - c));
+ C = trilinear(t, b * c/(-a + b + c), a * c/(a - b + c), 0);
+ return triangle(A, B, C);
+}
+
+/*<asyxml><function type="triangle" signature="tangential(triangle)"><code></asyxml>*/
+triangle tangential(triangle t)
+{/*<asyxml></code><documentation>Return the tangential triangle of the triangle 't'.
+ The tangential triangle of 't' is the triangle formed by the lines
+ tangent to the circumcircle of the given triangle 't' at its vertices.</documentation></function></asyxml>*/
+ point A, B, C;
+ real a = t.a(), b = t.b(), c = t.c();
+ A = trilinear(t, -a, b, c);
+ B = trilinear(t, a, -b, c);
+ C = trilinear(t, a, b, -c);
+ return triangle(A, B, C);
+}
+
+/*<asyxml><function type="triangle" signature="medial(triangle t)"><code></asyxml>*/
+triangle medial(triangle t)
+{/*<asyxml></code><documentation>Return the triangle whose vertices are midpoints of the sides of 't'.</documentation></function></asyxml>*/
+ return triangle(midpoint(t.BC), midpoint(t.AC), midpoint(t.AB));
+}
+
+/*<asyxml><function type="line" signature="median(vertex)"><code></asyxml>*/
+line median(vertex V)
+{/*<asyxml></code><documentation>Return median from 'V'.</documentation></function></asyxml>*/
+ return line(point(V), midpoint(segment(opposite(V))));
+}
+
+/*<asyxml><function type="line" signature="median(side)"><code></asyxml>*/
+line median(side side)
+{/*<asyxml></code><documentation>Return median from the opposite vertex of 'side'.</documentation></function></asyxml>*/
+ return median(opposite(side));
+}
+
+/*<asyxml><function type="triangle" signature="orthic(triangle)"><code></asyxml>*/
+triangle orthic(triangle t)
+{/*<asyxml></code><documentation>Return the triangle whose vertices are endpoints of the altitudes from each of the vertices of 't'.</documentation></function></asyxml>*/
+ return triangle(foot(t.BC), foot(t.AC), foot(t.AB));
+}
+
+/*<asyxml><function type="triangle" signature="symmedial(triangle)"><code></asyxml>*/
+triangle symmedial(triangle t)
+{/*<asyxml></code><documentation>Return the symmedial triangle of 't'.</documentation></function></asyxml>*/
+ point A, B, C;
+ real a = t.a(), b = t.b(), c = t.c();
+ A = trilinear(t, 0, b, c);
+ B = trilinear(t, a, 0, c);
+ C = trilinear(t, a, b, 0);
+ return triangle(A, B, C);
+}
+
+/*<asyxml><function type="triangle" signature="anticomplementary(triangle)"><code></asyxml>*/
+triangle anticomplementary(triangle t)
+{/*<asyxml></code><documentation>Return the triangle which has the given triangle 't' as its medial triangle.</documentation></function></asyxml>*/
+ real a = t.a(), b = t.b(), c = t.c();
+ real ab = a * b, bc = b * c, ca = c * a;
+ point A = trilinear(t, -bc, ca, ab);
+ point B = trilinear(t, bc, -ca, ab);
+ point C = trilinear(t, bc, ca, -ab);
+ return triangle(A, B, C);
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(triangle,line,bool)"><code></asyxml>*/
+point[] intersectionpoints(triangle t, line l, bool extended = false)
+{/*<asyxml></code><documentation>Return the intersection points.
+ If 'extended' is true, the sides are lines else the sides are segments.
+ intersectionpoints(line, triangle, bool) is also defined.</documentation></function></asyxml>*/
+ point[] OP;
+ void addpoint(point P)
+ {
+ if(defined(P)) {
+ bool exist = false;
+ for (int i = 0; i < OP.length; ++i) {
+ if(P == OP[i]) {exist = true; break;}
+ }
+ if(!exist) OP.push(P);
+ }
+ }
+ if(extended) {
+ for (int i = 1; i <= 3; ++i) {
+ addpoint(intersectionpoint(t.line(i), l));
+ }
+ } else {
+ for (int i = 1; i <= 3; ++i) {
+ addpoint(intersectionpoint((segment)t.line(i), l));
+ }
+ }
+ return OP;
+}
+
+point[] intersectionpoints(line l, triangle t, bool extended = false)
+{
+ return intersectionpoints(t, l, extended);
+}
+
+/*<asyxml><function type="vector" signature="dir(vertex)"><code></asyxml>*/
+vector dir(vertex V)
+{/*<asyxml></code><documentation>The direction (towards the outside of the triangle) of the interior angle bisector of 'V'.</documentation></function></asyxml>*/
+ triangle t = V.t;
+ if(V.n == 1) return vector(defaultcoordsys, (-dir(t.A--t.B, t.A--t.C)));
+ if(V.n == 2) return vector(defaultcoordsys, (-dir(t.B--t.A, t.B--t.C)));
+ return vector(defaultcoordsys, (-dir(t.C--t.A, t.C--t.B)));
+}
+
+/*<asyxml><function type="void" signature="lvoid label(picture,Label,vertex,pair,real,pen,filltype)"><code></asyxml>*/
+void label(picture pic = currentpicture, Label L, vertex V,
+ pair align = dir(V),
+ real alignFactor = 1,
+ pen p = nullpen, filltype filltype = NoFill)
+{/*<asyxml></code><documentation>Draw 'L' on picture 'pic' at vertex 'V' aligned by 'alignFactor * align'.</documentation></function></asyxml>*/
+ label(pic, L, locate(point(V)), alignFactor * align, p, filltype);
+}
+
+/*<asyxml><function type="void" signature="label(picture,Label,Label,Label,triangle,real,real,pen,filltype)"><code></asyxml>*/
+void label(picture pic = currentpicture, Label LA = "$A$",
+ Label LB = "$B$", Label LC = "$C$",
+ triangle t,
+ real alignAngle = 0,
+ real alignFactor = 1,
+ pen p = nullpen, filltype filltype = NoFill)
+{/*<asyxml></code><documentation>Draw labels LA, LB and LC aligned in the rotated (by 'alignAngle' in degrees) direction
+ (towards the outside of the triangle) of the interior angle bisector of vertices.
+ One can individually modify the alignment by setting the Label parameter 'align'.</documentation></function></asyxml>*/
+ Label lla = LA.copy();
+ lla.align(lla.align, rotate(alignAngle) * locate(dir(t.VA)));
+ label(pic, LA, t.VA, align = lla.align.dir, alignFactor = alignFactor, p, filltype);
+ Label llb = LB.copy();
+ llb.align(llb.align, rotate(alignAngle) * locate(dir(t.VB)));
+ label(pic, llb, t.VB, align = llb.align.dir, alignFactor = alignFactor, p, filltype);
+ Label llc = LC.copy();
+ llc.align(llc.align, rotate(alignAngle) * locate(dir(t.VC)));
+ label(pic, llc, t.VC, align = llc.align.dir, alignFactor = alignFactor, p, filltype);
+}
+
+/*<asyxml><function type="void" signature="show(picture,Label,Label,Label,Label,Label,Label,triangle,pen,filltype)"><code></asyxml>*/
+void show(picture pic = currentpicture,
+ Label LA = "$A$", Label LB = "$B$", Label LC = "$C$",
+ Label La = "$a$", Label Lb = "$b$", Label Lc = "$c$",
+ triangle t, pen p = currentpen, filltype filltype = NoFill)
+{/*<asyxml></code><documentation>Draw triangle and labels of sides and vertices.</documentation></function></asyxml>*/
+ pair a = locate(t.A), b = locate(t.B), c = locate(t.C);
+ draw(pic, a--b--c--cycle, p);
+ label(pic, LA, a, -dir(a--b, a--c), p, filltype);
+ label(pic, LB, b, -dir(b--a, b--c), p, filltype);
+ label(pic, LC, c, -dir(c--a, c--b), p, filltype);
+ pair aligna = I * unit(c - b), alignb = I * unit(c - a), alignc = I * unit(b - a);
+ pair mAB = locate(midpoint(t.AB)), mAC = locate(midpoint(t.AC)), mBC = locate(midpoint(t.BC));
+ label(pic, La, b--c, align = rotate(dot(a - mBC, aligna) > 0 ? 180 :0) * aligna, p);
+ label(pic, Lb, a--c, align = rotate(dot(b - mAC, alignb) > 0 ? 180 :0) * alignb, p);
+ label(pic, Lc, a--b, align = rotate(dot(c - mAB, alignc) > 0 ? 180 :0) * alignc, p);
+}
+
+/*<asyxml><function type="void" signature="draw(picture,triangle,pen,marker)"><code></asyxml>*/
+void draw(picture pic = currentpicture, triangle t, pen p = currentpen, marker marker = nomarker)
+{/*<asyxml></code><documentation>Draw sides of the triangle 't' on picture 'pic' using pen 'p'.</documentation></function></asyxml>*/
+ draw(pic, t.Path(), p, marker);
+}
+
+/*<asyxml><function type="void" signature="draw(picture,triangle[],pen,marker)"><code></asyxml>*/
+void draw(picture pic = currentpicture, triangle[] t, pen p = currentpen, marker marker = nomarker)
+{/*<asyxml></code><documentation>Draw sides of the triangles 't' on picture 'pic' using pen 'p'.</documentation></function></asyxml>*/
+ for(int i = 0; i < t.length; ++i) draw(pic, t[i], p, marker);
+}
+
+/*<asyxml><function type="void" signature="drawline(picture,triangle,pen)"><code></asyxml>*/
+void drawline(picture pic = currentpicture, triangle t, pen p = currentpen)
+{/*<asyxml></code><documentation>Draw lines of the triangle 't' on picture 'pic' using pen 'p'.</documentation></function></asyxml>*/
+ draw(t, p);
+ draw(pic, line(t.A, t.B), p);
+ draw(pic, line(t.A, t.C), p);
+ draw(pic, line(t.B, t.C), p);
+}
+
+/*<asyxml><function type="void" signature="dot(picture,triangle,pen)"><code></asyxml>*/
+void dot(picture pic = currentpicture, triangle t, pen p = currentpen)
+{/*<asyxml></code><documentation>Draw a dot at each vertex of 't'.</documentation></function></asyxml>*/
+ dot(pic, t.A^^t.B^^t.C, p);
+}
+// *.......................TRIANGLES.......................*
+// *=======================================================*
+
+// *=======================================================*
+// *.......................INVERSIONS......................*
+/*<asyxml><function type="point" signature="inverse(real k,point,point)"><code></asyxml>*/
+point inverse(real k, point A, point M)
+{/*<asyxml></code><documentation>Return the inverse point of 'M' with respect to point A and inversion radius 'k'.</documentation></function></asyxml>*/
+ return A + k/conj(M - A);
+}
+
+/*<asyxml><function type="point" signature="radicalcenter(circle,circle)"><code></asyxml>*/
+point radicalcenter(circle c1, circle c2)
+{/*<asyxml></code><documentation><url href = "http://fr.wikipedia.org/wiki/Puissance_d'un_point_par_rapport_%C3%A0_un_cercle"/></documentation></function></asyxml>*/
+ point[] P = standardizecoordsys(c1.C, c2.C);
+ real k = c1.r^2 - c2.r^2;
+ pair C1 = locate(c1.C);
+ pair C2 = locate(c2.C);
+ pair oop = C2 - C1;
+ pair K = (abs(oop) == 0) ?
+ (infinity, infinity) :
+ midpoint(C1--C2) + 0.5 * k * oop/dot(oop, oop);
+ return point(P[0].coordsys, K/P[0].coordsys);
+}
+
+/*<asyxml><function type="line" signature="radicalline(circle,circle)"><code></asyxml>*/
+line radicalline(circle c1, circle c2)
+{/*<asyxml></code><documentation><url href = "http://fr.wikipedia.org/wiki/Puissance_d'un_point_par_rapport_%C3%A0_un_cercle"/></documentation></function></asyxml>*/
+ if (c1.C == c2.C) abort("radicalline: the centers must be distinct");
+ return perpendicular(radicalcenter(c1, c2), line(c1.C, c2.C));
+}
+
+/*<asyxml><function type="point" signature="radicalcenter(circle,circle,circle)"><code></asyxml>*/
+point radicalcenter(circle c1, circle c2, circle c3)
+{/*<asyxml></code><documentation><url href = "http://fr.wikipedia.org/wiki/Puissance_d'un_point_par_rapport_%C3%A0_un_cercle"/></documentation></function></asyxml>*/
+ return intersectionpoint(radicalline(c1, c2), radicalline(c1, c3));
+}
+
+/*<asyxml><struct signature="inversion"><code></asyxml>*/
+struct inversion
+{/*<asyxml></code><documentation>http://mathworld.wolfram.com/Inversion.html</documentation></asyxml>*/
+ point C;
+ real k;
+}/*<asyxml></struct></asyxml>*/
+
+/*<asyxml><function type="inversion" signature="inversion(real,point)"><code></asyxml>*/
+inversion inversion(real k, point C)
+{/*<asyxml></code><documentation>Return the inversion with respect to 'C' having inversion radius 'k'.</documentation></function></asyxml>*/
+ inversion oi;
+ oi.k = k;
+ oi.C = C;
+ return oi;
+}
+/*<asyxml><function type="inversion" signature="inversion(real,point)"><code></asyxml>*/
+inversion inversion(point C, real k)
+{/*<asyxml></code><documentation>Return the inversion with respect to 'C' having inversion radius 'k'.</documentation></function></asyxml>*/
+ return inversion(k, C);
+}
+
+/*<asyxml><function type="inversion" signature="inversion(circle,circle)"><code></asyxml>*/
+inversion inversion(circle c1, circle c2, real sgn = 1)
+{/*<asyxml></code><documentation>Return the inversion which transforms 'c1' to
+ . 'c2' and positive inversion radius if 'sgn > 0';
+ . 'c2' and negative inversion radius if 'sgn < 0';
+ . 'c1' and 'c2' to 'c2' if 'sgn = 0'.</documentation></function></asyxml>*/
+ if(sgn == 0) {
+ point O = radicalcenter(c1, c2);
+ return inversion(O^c1, O);
+ }
+ real a = abs(c1.r/c2.r);
+ if(sgn > 0) {
+ point O = c1.C + a/abs(1 - a) * (c2.C - c1.C);
+ return inversion(a * abs(abs(O - c2.C)^2 - c2.r^2), O);
+ }
+ point O = c1.C + a/abs(1 + a) * (c2.C - c1.C);
+ return inversion(-a * abs(abs(O - c2.C)^2 - c2.r^2), O);
+}
+
+/*<asyxml><function type="inversion" signature="inversion(circle,circle,circle)"><code></asyxml>*/
+inversion inversion(circle c1, circle c2, circle c3)
+{/*<asyxml></code><documentation>Return the inversion which transform 'c1' to 'c1', 'c2' to 'c2' and 'c3' to 'c3'.</documentation></function></asyxml>*/
+ point Rc = radicalcenter(c1, c2, c3);
+ return inversion(Rc, Rc^c1);
+}
+
+circle operator cast(inversion i){return circle(i.C, sgn(i.k) * sqrt(abs(i.k)));}
+/*<asyxml><function type="circle" signature="circle(inversion)"><code></asyxml>*/
+circle circle(inversion i)
+{/*<asyxml></code><documentation>Return the inversion circle of 'i'.</documentation></function></asyxml>*/
+ return i;
+}
+
+inversion operator cast(circle c)
+{
+ return inversion(sgn(c.r) * c.r^2, c.C);
+}
+/*<asyxml><function type="inversion" signature="inversion(circle)"><code></asyxml>*/
+inversion inversion(circle c)
+{/*<asyxml></code><documentation>Return the inversion represented by the circle of 'c'.</documentation></function></asyxml>*/
+ return c;
+}
+
+/*<asyxml><operator type = "point" signature="*(inversion,point)"><code></asyxml>*/
+point operator *(inversion i, point P)
+{/*<asyxml></code><documentation>Provide inversion * point.</documentation></operator></asyxml>*/
+ return inverse(i.k, i.C, P);
+}
+
+void lineinversion()
+{
+ warning("lineinversion", "the inversion of the line is not a circle.
+The returned circle has an infinite radius, circle.l has been set.");
+}
+
+
+/*<asyxml><function type="circle" signature="inverse(real,point,line)"><code></asyxml>*/
+circle inverse(real k, point A, line l)
+{/*<asyxml></code><documentation>Return the inverse circle of 'l' with
+ respect to point 'A' and inversion radius 'k'.</documentation></function></asyxml>*/
+ if(A @ l) {
+ lineinversion();
+ circle C = circle(A, infinity);
+ C.l = l;
+ return C;
+ }
+ point Ap = inverse(k, A, l.A), Bp = inverse(k, A, l.B);
+ return circle(A, Ap, Bp);
+}
+
+/*<asyxml><operator type = "circle" signature="*(inversion,line)"><code></asyxml>*/
+circle operator *(inversion i, line l)
+{/*<asyxml></code><documentation>Provide inversion * line for lines that don't pass through the inversion center.</documentation></operator></asyxml>*/
+ return inverse(i.k, i.C, l);
+}
+
+/*<asyxml><function type="circle" signature="inverse(real,point,circle)"><code></asyxml>*/
+circle inverse(real k, point A, circle c)
+{/*<asyxml></code><documentation>Return the inverse circle of 'c' with
+ respect to point A and inversion radius 'k'.</documentation></function></asyxml>*/
+ if(degenerate(c)) return inverse(k, A, c.l);
+ if(A @ c) {
+ lineinversion();
+ point M = rotate(180, c.C) * A, Mp = rotate(90, c.C) * A;
+ circle oc = circle(A, infinity);
+ oc.l = line(inverse(k, A, M), inverse(k, A, Mp));
+ return oc;
+ }
+ point[] P = standardizecoordsys(A, c.C);
+ real s = k/((P[1].x - P[0].x)^2 + (P[1].y - P[0].y)^2 - c.r^2);
+ return circle(P[0] + s * (P[1]-P[0]), abs(s) * c.r);
+}
+
+/*<asyxml><operator type = "circle" signature="*(inversion,circle)"><code></asyxml>*/
+circle operator *(inversion i, circle c)
+{/*<asyxml></code><documentation>Provide inversion * circle.</documentation></operator></asyxml>*/
+ return inverse(i.k, i.C, c);
+}
+// *.......................INVERSIONS......................*
+// *=======================================================*
+
+// *=======================================================*
+// *........................FOOTER.........................*
+/*<asyxml><function type="point[]" signature="intersectionpoints(line,circle)"><code></asyxml>*/
+point[] intersectionpoints(line l, circle c)
+{/*<asyxml></code><documentation>Note that the line 'l' may be a segment by casting.
+ intersectionpoints(circle, line) is also defined.</documentation></function></asyxml>*/
+ if(degenerate(c)) return new point[]{intersectionpoint(l, c.l)};
+ point[] op;
+ coordsys R = samecoordsys(l.A, c.C) ?
+ l.A.coordsys : defaultcoordsys;
+ coordsys Rp = defaultcoordsys;
+ circle cc = circle(changecoordsys(Rp, c.C), c.r);
+ point proj = projection(l) * c.C;
+ if(proj @ cc) { // The line is a tangente of the circle.
+ if(proj @ l) op.push(proj);// line may be a segement...
+ } else {
+ coordsys Rc = cartesiansystem(c.C, (1, 0), (0, 1));
+ line ll = changecoordsys(Rc, l);
+ pair[] P = intersectionpoints(ll.A.coordinates, ll.B.coordinates,
+ 1, 0, 1, 0, 0, -c.r^2);
+ for (int i = 0; i < P.length; ++i) {
+ point inter = changecoordsys(R, point(Rc, P[i]));
+ if(inter @ l) op.push(inter);
+ }
+ }
+ return op;
+}
+
+point[] intersectionpoints(circle c, line l)
+{
+ return intersectionpoints(l, c);
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(line,ellipse)"><code></asyxml>*/
+point[] intersectionpoints(line l, ellipse el)
+{/*<asyxml></code><documentation>Note that the line 'l' may be a segment by casting.
+ intersectionpoints(ellipse, line) is also defined.</documentation></function></asyxml>*/
+ if(el.e == 0) return intersectionpoints(l, (circle)el);
+ if(degenerate(el)) return new point[]{intersectionpoint(l, el.l)};
+ point[] op;
+ coordsys R = samecoordsys(l.A, el.C) ? l.A.coordsys : defaultcoordsys;
+ coordsys Rp = defaultcoordsys;
+ line ll = changecoordsys(Rp, l);
+ ellipse ell = changecoordsys(Rp, el);
+ circle C = circle(ell.C, ell.a);
+ point[] Ip = intersectionpoints(ll, C);
+ if (Ip.length > 0 &&
+ (perpendicular(ll, line(ell.F1, Ip[0])) ||
+ perpendicular(ll, line(ell.F2, Ip[0])))) {
+ // http://www.mathcurve.com/courbes2d/ellipse/ellipse.shtml
+ // Définition tangentielle par antipodaire de cercle.
+ // 'l' is a tangent of 'el'
+ transform t = scale(el.a/el.b, el.F1, el.F2, el.C, rotate(90, el.C) * el.F1);
+ point inter = inverse(t) * intersectionpoints(C, t * ll)[0];
+ if(inter @ l) op.push(inter);
+ } else {
+ coordsys Rc = canonicalcartesiansystem(el);
+ line ll = changecoordsys(Rc, l);
+ pair[] P = intersectionpoints(ll.A.coordinates, ll.B.coordinates,
+ 1/el.a^2, 0, 1/el.b^2, 0, 0, -1);
+ for (int i = 0; i < P.length; ++i) {
+ point inter = changecoordsys(R, point(Rc, P[i]));
+ if(inter @ l) op.push(inter);
+ }
+ }
+ return op;
+}
+
+point[] intersectionpoints(ellipse el, line l)
+{
+ return intersectionpoints(l, el);
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(line,parabola)"><code></asyxml>*/
+point[] intersectionpoints(line l, parabola p)
+{/*<asyxml></code><documentation>Note that the line 'l' may be a segment by casting.
+ intersectionpoints(parabola, line) is also defined.</documentation></function></asyxml>*/
+ point[] op;
+ coordsys R = coordsys(p);
+ bool tgt = false;
+ line ll = changecoordsys(R, l),
+ lv = parallel(p.V, p.D);
+ point M = intersectionpoint(lv, ll), tgtp;
+ if(finite(M)) {// Test if 'l' is tangent to 'p'
+ line l1 = bisector(line(M, p.F));
+ line l2 = rotate(90, M) * lv;
+ point P = intersectionpoint(l1, l2);
+ tgtp = rotate(180, P) * p.F;
+ tgt = (tgtp @ l);
+ }
+ if(tgt) {
+ if(tgtp @ l) op.push(tgtp);
+ } else {
+ real[] eq = changecoordsys(defaultcoordsys, equation(p)).a;
+ pair[] tp = intersectionpoints(locate(l.A), locate(l.B), eq);
+ point inter;
+ for (int i = 0; i < tp.length; ++i) {
+ inter = point(R, tp[i]/R);
+ if(inter @ l) op.push(inter);
+ }
+ }
+ return op;
+}
+
+point[] intersectionpoints(parabola p, line l)
+{
+ return intersectionpoints(l, p);
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(line,hyperbola)"><code></asyxml>*/
+point[] intersectionpoints(line l, hyperbola h)
+{/*<asyxml></code><documentation>Note that the line 'l' may be a segment by casting.
+ intersectionpoints(hyperbola, line) is also defined.</documentation></function></asyxml>*/
+ point[] op;
+ coordsys R = coordsys(h);
+ point A = intersectionpoint(l, h.A1), B = intersectionpoint(l, h.A2);
+ point M = midpoint(segment(A, B));
+ bool tgt = M @ h;
+ if(tgt) {
+ if(M @ l) op.push(M);
+ } else {
+ real[] eq = changecoordsys(defaultcoordsys, equation(h)).a;
+ pair[] tp = intersectionpoints(locate(l.A), locate(l.B), eq);
+ point inter;
+ for (int i = 0; i < tp.length; ++i) {
+ inter = point(R, tp[i]/R);
+ if(inter @ l) op.push(inter);
+ }
+ }
+ return op;
+}
+
+point[] intersectionpoints(hyperbola h, line l)
+{
+ return intersectionpoints(l, h);
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(line,conic)"><code></asyxml>*/
+point[] intersectionpoints(line l, conic co)
+{/*<asyxml></code><documentation>Note that the line 'l' may be a segment by casting.
+ intersectionpoints(conic, line) is also defined.</documentation></function></asyxml>*/
+ point[] op;
+ if(co.e < 1) op = intersectionpoints((ellipse)co, l);
+ else
+ if(co.e == 1) op = intersectionpoints((parabola)co, l);
+ else op = intersectionpoints((hyperbola)co, l);
+ return op;
+}
+
+point[] intersectionpoints(conic co, line l)
+{
+ return intersectionpoints(l, co);
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(conic,conic)"><code></asyxml>*/
+point[] intersectionpoints(conic co1, conic co2)
+{/*<asyxml></code><documentation>Return the intersection points of the two conics.</documentation></function></asyxml>*/
+ if(degenerate(co1)) return intersectionpoints(co1.l[0], co2);
+ if(degenerate(co2)) return intersectionpoints(co1, co2.l[0]);
+ return intersectionpoints(equation(co1), equation(co2));
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(triangle,conic,bool)"><code></asyxml>*/
+point[] intersectionpoints(triangle t, conic co, bool extended = false)
+{/*<asyxml></code><documentation>Return the intersection points.
+ If 'extended' is true, the sides are lines else the sides are segments.
+ intersectionpoints(conic, triangle, bool) is also defined.</documentation></function></asyxml>*/
+ if(degenerate(co)) return intersectionpoints(t, co.l[0], extended);
+ point[] OP;
+ void addpoint(point P[])
+ {
+ for (int i = 0; i < P.length; ++i) {
+ if(defined(P[i])) {
+ bool exist = false;
+ for (int j = 0; j < OP.length; ++j) {
+ if(P[i] == OP[j]) {exist = true; break;}
+ }
+ if(!exist) OP.push(P[i]);
+ }}}
+ if(extended) {
+ for (int i = 1; i <= 3; ++i) {
+ addpoint(intersectionpoints(t.line(i), co));
+ }
+ } else {
+ for (int i = 1; i <= 3; ++i) {
+ addpoint(intersectionpoints((segment)t.line(i), co));
+ }
+ }
+ return OP;
+}
+
+point[] intersectionpoints(conic co, triangle t, bool extended = false)
+{
+ return intersectionpoints(t, co, extended);
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(ellipse,ellipse)"><code></asyxml>*/
+point[] intersectionpoints(ellipse a, ellipse b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ // if(degenerate(a)) return intersectionpoints(a.l, b);
+ // if(degenerate(b)) return intersectionpoints(a, b.l);;
+ return intersectionpoints((conic)a, (conic)b);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(ellipse,circle)"><code></asyxml>*/
+point[] intersectionpoints(ellipse a, circle b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ // if(degenerate(a)) return intersectionpoints(a.l, b);
+ // if(degenerate(b)) return intersectionpoints(a, b.l);;
+ return intersectionpoints((conic)a, (conic)b);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(circle,ellipse)"><code></asyxml>*/
+point[] intersectionpoints(circle a, ellipse b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersectionpoints(b, a);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(ellipse,parabola)"><code></asyxml>*/
+point[] intersectionpoints(ellipse a, parabola b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ // if(degenerate(a)) return intersectionpoints(a.l, b);
+ return intersectionpoints((conic)a, (conic)b);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(parabola,ellipse)"><code></asyxml>*/
+point[] intersectionpoints(parabola a, ellipse b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersectionpoints(b, a);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(ellipse,hyperbola)"><code></asyxml>*/
+point[] intersectionpoints(ellipse a, hyperbola b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ // if(degenerate(a)) return intersectionpoints(a.l, b);
+ return intersectionpoints((conic)a, (conic)b);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(hyperbola,ellipse)"><code></asyxml>*/
+point[] intersectionpoints(hyperbola a, ellipse b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersectionpoints(b, a);
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(circle,parabola)"><code></asyxml>*/
+point[] intersectionpoints(circle a, parabola b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersectionpoints((conic)a, (conic)b);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(parabola,circle)"><code></asyxml>*/
+point[] intersectionpoints(parabola a, circle b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersectionpoints((conic)a, (conic)b);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(circle,hyperbola)"><code></asyxml>*/
+point[] intersectionpoints(circle a, hyperbola b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersectionpoints((conic)a, (conic)b);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(hyperbola,circle)"><code></asyxml>*/
+point[] intersectionpoints(hyperbola a, circle b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersectionpoints((conic)a, (conic)b);
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(parabola,parabola)"><code></asyxml>*/
+point[] intersectionpoints(parabola a, parabola b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersectionpoints((conic)a, (conic)b);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(parabola,hyperbola)"><code></asyxml>*/
+point[] intersectionpoints(parabola a, hyperbola b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersectionpoints((conic)a, (conic)b);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(hyperbola,parabola)"><code></asyxml>*/
+point[] intersectionpoints(hyperbola a, parabola b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersectionpoints((conic)a, (conic)b);
+}
+/*<asyxml><function type="point[]" signature="intersectionpoints(hyperbola,hyperbola)"><code></asyxml>*/
+point[] intersectionpoints(hyperbola a, hyperbola b)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ return intersectionpoints((conic)a, (conic)b);
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(circle,circle)"><code></asyxml>*/
+point[] intersectionpoints(circle c1, circle c2)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ if(degenerate(c1))
+ return degenerate(c2) ?
+ new point[]{intersectionpoint(c1.l, c2.l)} : intersectionpoints(c1.l, c2);
+ if(degenerate(c2)) return intersectionpoints(c1, c2.l);
+ return (c1.C == c2.C) ?
+ new point[] :
+ intersectionpoints(radicalline(c1, c2), c1);
+}
+
+/*<asyxml><function type="line" signature="tangent(circle,abscissa)"><code></asyxml>*/
+line tangent(circle c, abscissa x)
+{/*<asyxml></code><documentation>Return the tangent of 'c' at 'point(c, x)'.</documentation></function></asyxml>*/
+ if(c.r == 0) abort("tangent: a circle with a radius equals zero has no tangent.");
+ point M = point(c, x);
+ return line(rotate(90, M) * c.C, M);
+}
+
+/*<asyxml><function type="line[]" signature="tangents(circle,point)"><code></asyxml>*/
+line[] tangents(circle c, point M)
+{/*<asyxml></code><documentation>Return the tangents of 'c' passing through 'M'.</documentation></function></asyxml>*/
+ line[] ol;
+ if(inside(c, M)) return ol;
+ if(M @ c) {
+ ol.push(tangent(c, relabscissa(c, M)));
+ } else {
+ circle cc = circle(c.C, M);
+ point[] inter = intersectionpoints(c, cc);
+ for (int i = 0; i < inter.length; ++i)
+ ol.push(tangents(c, inter[i])[0]);
+ }
+ return ol;
+}
+
+/*<asyxml><function type="point" signature="point(circle,point)"><code></asyxml>*/
+point point(circle c, point M)
+{/*<asyxml></code><documentation>Return the intersection point of 'c'
+ with the half-line '[c.C M)'.</documentation></function></asyxml>*/
+ return intersectionpoints(c, line(c.C, false, M))[0];
+}
+
+/*<asyxml><function type="line" signature="tangent(circle,point)"><code></asyxml>*/
+line tangent(circle c, point M)
+{/*<asyxml></code><documentation>Return the tangent of 'c' at the
+ intersection point of the half-line'[c.C M)'.</documentation></function></asyxml>*/
+ return tangents(c, point(c, M))[0];
+}
+
+/*<asyxml><function type="point" signature="point(circle,explicit vector)"><code></asyxml>*/
+point point(circle c, explicit vector v)
+{/*<asyxml></code><documentation>Return the intersection point of 'c'
+ with the half-line '[c.C v)'.</documentation></function></asyxml>*/
+ return point(c, c.C + v);
+}
+
+/*<asyxml><function type="line" signature="tangent(circle,explicit vector)"><code></asyxml>*/
+line tangent(circle c, explicit vector v)
+{/*<asyxml></code><documentation>Return the tangent of 'c' at the
+ point M so that vec(c.C M) is collinear to 'v' with the same sense.</documentation></function></asyxml>*/
+ line ol = tangent(c, c.C + v);
+ return dot(ol.v, v) > 0 ? ol : reverse(ol);
+}
+
+/*<asyxml><function type="line" signature="tangent(ellipse,abscissa)"><code></asyxml>*/
+line tangent(ellipse el, abscissa x)
+{/*<asyxml></code><documentation>Return the tangent of 'el' at 'point(el, x)'.</documentation></function></asyxml>*/
+ point M = point(el, x);
+ line l1 = line(el.F1, M);
+ line l2 = line(el.F2, M);
+ line ol = (l1 == l2) ? perpendicular(M, l1) : bisector(l1, l2, 90, false);
+ return ol;
+}
+
+/*<asyxml><function type="line[]" signature="tangents(ellipse,point)"><code></asyxml>*/
+line[] tangents(ellipse el, point M)
+{/*<asyxml></code><documentation>Return the tangents of 'el' passing through 'M'.</documentation></function></asyxml>*/
+ line[] ol;
+ if(inside(el, M)) return ol;
+ if(M @ el) {
+ ol.push(tangent(el, relabscissa(el, M)));
+ } else {
+ point Mp = samecoordsys(M, el.F2) ?
+ M : changecoordsys(el.F2.coordsys, M);
+ circle c = circle(Mp, abs(el.F1 - Mp));
+ circle cc = circle(el.F2, 2 * el.a);
+ point[] inter = intersectionpoints(c, cc);
+ for (int i = 0; i < inter.length; ++i) {
+ line tl = line(inter[i], el.F2, false);
+ point[] P = intersectionpoints(tl, el);
+ ol.push(line(Mp, P[0]));
+ }
+ }
+ return ol;
+}
+
+/*<asyxml><function type="line" signature="tangent(parabola,abscissa)"><code></asyxml>*/
+line tangent(parabola p, abscissa x)
+{/*<asyxml></code><documentation>Return the tangent of 'p' at 'point(p, x)' (use the Wells method).</documentation></function></asyxml>*/
+ line lt = rotate(90, p.V) * line(p.V, p.F);
+ point P = point(p, x);
+ if(P == p.V) return lt;
+ point M = midpoint(segment(P, p.F));
+ line l = rotate(90, M) * line(P, p.F);
+ return line(P, projection(lt) * M);
+}
+
+/*<asyxml><function type="line[]" signature="tangents(parabola,point)"><code></asyxml>*/
+line[] tangents(parabola p, point M)
+{/*<asyxml></code><documentation>Return the tangent of 'p' at 'M' (use the Wells method).</documentation></function></asyxml>*/
+ line[] ol;
+ if(inside(p, M)) return ol;
+ if(M @ p) {
+ ol.push(tangent(p, angabscissa(p, M)));
+ }
+ else {
+ point Mt = changecoordsys(coordsys(p), M);
+ circle c = circle(Mt, p.F);
+ line l = rotate(90, p.V) * line(p.V, p.F);
+ point[] R = intersectionpoints(l, c);
+ for (int i = 0; i < R.length; ++i) {
+ ol.push(line(Mt, R[i]));
+ }
+ // An other method: http://www.du.edu/~jcalvert/math/parabola.htm
+ // point[] R = intersectionpoints(p.directrix, c);
+ // for (int i = 0; i < R.length; ++i) {
+ // ol.push(bisector(segment(p.F, R[i])));
+ // }
+ }
+ return ol;
+}
+
+/*<asyxml><function type="line" signature="tangent(hyperbola,abscissa)"><code></asyxml>*/
+line tangent(hyperbola h, abscissa x)
+{/*<asyxml></code><documentation>Return the tangent of 'h' at 'point(p, x)'.</documentation></function></asyxml>*/
+ point M = point(h, x);
+ line ol = bisector(line(M, h.F1), line(M, h.F2));
+ if(sameside(h.F1, h.F2, ol) || ol == line(h.F1, h.F2)) ol = rotate(90, M) * ol;
+ return ol;
+}
+
+/*<asyxml><function type="line[]" signature="tangents(hyperbola,point)"><code></asyxml>*/
+line[] tangents(hyperbola h, point M)
+{/*<asyxml></code><documentation>Return the tangent of 'h' at 'M'.</documentation></function></asyxml>*/
+ line[] ol;
+ if(M @ h) {
+ ol.push(tangent(h, angabscissa(h, M, fromCenter)));
+ } else {
+ coordsys cano = canonicalcartesiansystem(h);
+ bqe bqe = changecoordsys(cano, equation(h));
+ real a = abs(1/(bqe.a[5] * bqe.a[0])), b = abs(1/(bqe.a[5] * bqe.a[2]));
+ point Mp = changecoordsys(cano, M);
+ real x0 = Mp.x, y0 = Mp.y;
+ if(abs(x0) > epsgeo) {
+ real c0 = a * y0^2/(b * x0)^2 - 1/b,
+ c1 = 2 * a * y0/(b * x0^2), c2 = a/x0^2 - 1;
+ real[] sol = quadraticroots(c0, c1, c2);
+ for (real y:sol) {
+ point tmp = changecoordsys(coordsys(h), point(cano, (a * (1 + y * y0/b)/x0, y)));
+ ol.push(line(M, tmp));
+ }
+ } else if(abs(y0) > epsgeo) {
+ real y = -b/y0, x = sqrt(a * (1 + b/y0^2));
+ ol.push(line(M, changecoordsys(coordsys(h), point(cano, (x, y)))));
+ ol.push(line(M, changecoordsys(coordsys(h), point(cano, (-x, y)))));
+ }}
+ return ol;
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(conic,arc)"><code></asyxml>*/
+point[] intersectionpoints(conic co, arc a)
+{/*<asyxml></code><documentation>intersectionpoints(arc, circle) is also defined.</documentation></function></asyxml>*/
+ point[] op;
+ point[] tp = intersectionpoints(co, (conic)a.el);
+ for (int i = 0; i < tp.length; ++i)
+ if(tp[i] @ a) op.push(tp[i]);
+ return op;
+}
+
+point[] intersectionpoints(arc a, conic co)
+{
+ return intersectionpoints(co, a);
+}
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(arc,arc)"><code></asyxml>*/
+point[] intersectionpoints(arc a1, arc a2)
+{/*<asyxml></code><documentation></documentation></function></asyxml>*/
+ point[] op;
+ point[] tp = intersectionpoints(a1.el, a2.el);
+ for (int i = 0; i < tp.length; ++i)
+ if(tp[i] @ a1 && tp[i] @ a2) op.push(tp[i]);
+ return op;
+}
+
+
+/*<asyxml><function type="point[]" signature="intersectionpoints(line,arc)"><code></asyxml>*/
+point[] intersectionpoints(line l, arc a)
+{/*<asyxml></code><documentation>intersectionpoints(arc, line) is also defined.</documentation></function></asyxml>*/
+ point[] op;
+ point[] tp = intersectionpoints(a.el, l);
+ for (int i = 0; i < tp.length; ++i)
+ if(tp[i] @ a && tp[i] @ l) op.push(tp[i]);
+ return op;
+}
+
+point[] intersectionpoints(arc a, line l)
+{
+ return intersectionpoints(l, a);
+}
+
+/*<asyxml><function type="point" signature="arcsubtendedcenter(point,point,real)"><code></asyxml>*/
+point arcsubtendedcenter(point A, point B, real angle)
+{/*<asyxml></code><documentation>Return the center of the arc retuned
+ by the 'arcsubtended' routine.</documentation></function></asyxml>*/
+ point OM;
+ point[] P = standardizecoordsys(A, B);
+ angle = angle%(sgnd(angle) * 180);
+ line bis = bisector(P[0], P[1]);
+ line AB = line(P[0], P[1]);
+ return intersectionpoint(bis, rotate(90 - angle, A) * AB);
+}
+
+/*<asyxml><function type="arc" signature="arcsubtended(point,point,real)"><code></asyxml>*/
+arc arcsubtended(point A, point B, real angle)
+{/*<asyxml></code><documentation>Return the arc circle from which the segment AB is saw with
+ the angle 'angle'.
+ If the point 'M' is on this arc, the oriented angle (MA, MB) is
+ equal to 'angle'.</documentation></function></asyxml>*/
+ point[] P = standardizecoordsys(A, B);
+ line AB = line(P[0], P[1]);
+ angle = angle%(sgnd(angle) * 180);
+ point C = arcsubtendedcenter(P[0], P[1], angle);
+ real BC = degrees(B - C)%360;
+ real AC = degrees(A - C)%360;
+ return arc(circle(C, abs(B - C)), BC, AC, angle > 0 ? CCW : CW);
+}
+
+/*<asyxml><function type="arc" signature="arccircle(point,point,point)"><code></asyxml>*/
+arc arccircle(point A, point M, point B)
+{/*<asyxml></code><documentation>Return the CCW arc circle 'AB' passing through 'M'.</documentation></function></asyxml>*/
+ circle tc = circle(A, M, B);
+ real a = degrees(A - tc.C);
+ real b = degrees(B - tc.C);
+ real m = degrees(M - tc.C);
+
+ arc oa = arc(tc, a, b);
+ // TODO : use cross product to determine CWW or CW
+ if (!(M @ oa)) {
+ oa.direction = !oa.direction;
+ }
+
+ return oa;
+}
+
+/*<asyxml><function type="arc" signature="arc(ellipse,abscissa,abscissa,bool)"><code></asyxml>*/
+arc arc(ellipse el, explicit abscissa x1, explicit abscissa x2, bool direction = CCW)
+{/*<asyxml></code><documentation>Return the arc from 'point(c, x1)' to 'point(c, x2)' in the direction 'direction'.</documentation></function></asyxml>*/
+ real a = degrees(point(el, x1) - el.C);
+ real b = degrees(point(el, x2) - el.C);
+ arc oa = arc(el, a - el.angle, b - el.angle, fromCenter, direction);
+ return oa;
+}
+
+/*<asyxml><function type="arc" signature="arc(ellipse,point,point,bool)"><code></asyxml>*/
+arc arc(ellipse el, point M, point N, bool direction = CCW)
+{/*<asyxml></code><documentation>Return the arc from 'M' to 'N' in the direction 'direction'.
+ The points 'M' and 'N' must belong to the ellipse 'el'.</documentation></function></asyxml>*/
+ return arc(el, relabscissa(el, M), relabscissa(el, N), direction);
+}
+
+/*<asyxml><function type="arc" signature="arccircle(point,point,real,bool)"><code></asyxml>*/
+arc arccircle(point A, point B, real angle, bool direction = CCW)
+{/*<asyxml></code><documentation>Return the arc circle centered on A
+ from B to rotate(angle, A) * B in the direction 'direction'.</documentation></function></asyxml>*/
+ point M = rotate(angle, A) * B;
+ return arc(circle(A, abs(A - B)), B, M, direction);
+}
+
+/*<asyxml><function type="arc" signature="arc(explicit arc,abscissa,abscissa)"><code></asyxml>*/
+arc arc(explicit arc a, abscissa x1, abscissa x2)
+{/*<asyxml></code><documentation>Return the arc from 'point(a, x1)' to 'point(a, x2)' traversed in the direction of the arc direction.</documentation></function></asyxml>*/
+ real a1 = angabscissa(a.el, point(a, x1), a.polarconicroutine).x;
+ real a2 = angabscissa(a.el, point(a, x2), a.polarconicroutine).x;
+ return arc(a.el, a1, a2, a.polarconicroutine, a.direction);
+}
+
+/*<asyxml><function type="arc" signature="arc(explicit arc,point,point)"><code></asyxml>*/
+arc arc(explicit arc a, point M, point N)
+{/*<asyxml></code><documentation>Return the arc from 'M' to 'N'.
+ The points 'M' and 'N' must belong to the arc 'a'.</documentation></function></asyxml>*/
+ return arc(a, relabscissa(a, M), relabscissa(a, N));
+}
+
+/*<asyxml><function type="arc" signature="inverse(real,point,segment)"><code></asyxml>*/
+arc inverse(real k, point A, segment s)
+{/*<asyxml></code><documentation>Return the inverse arc circle of 's'
+ with respect to point A and inversion radius 'k'.</documentation></function></asyxml>*/
+ point Ap = inverse(k, A, s.A), Bp = inverse(k, A, s.B),
+ M = inverse(k, A, midpoint(s));
+ return arccircle(Ap, M, Bp);
+}
+
+/*<asyxml><operator type = "arc" signature="*(inversion,segment)"><code></asyxml>*/
+arc operator *(inversion i, segment s)
+{/*<asyxml></code><documentation>Provide
+ inversion * segment.</documentation></operator></asyxml>*/
+ return inverse(i.k, i.C, s);
+}
+
+/*<asyxml><operator type = "path" signature="*(inversion,triangle)"><code></asyxml>*/
+path operator *(inversion i, triangle t)
+{/*<asyxml></code><documentation>Provide inversion * triangle.</documentation></operator></asyxml>*/
+ return (path)(i * segment(t.AB))--
+ (path)(i * segment(t.BC))--
+ (path)(i * segment(t.CA))&cycle;
+}
+
+/*<asyxml><function type="path" signature="compassmark(pair,pair,real,real)"><code></asyxml>*/
+path compassmark(pair O, pair A, real position, real angle = 10)
+{/*<asyxml></code><documentation>Return an arc centered on O with the angle 'angle' so that the position
+ of 'A' on this arc makes an angle 'position * angle'.</documentation></function></asyxml>*/
+ real a = degrees(A - O);
+ real pa = (a - position * angle)%360,
+ pb = (a - (position - 1) * angle)%360;
+ real t1 = intersect(unitcircle, (0, 0)--2 * dir(pa))[0];
+ real t2 = intersect(unitcircle, (0, 0)--2 * dir(pb))[0];
+ int n = length(unitcircle);
+ if(t1 >= t2) t1 -= n;
+ return shift(O) * scale(abs(O - A)) * subpath(unitcircle, t1, t2);
+}
+
+/*<asyxml><function type="line" signature="tangent(explicit arc,abscissa)"><code></asyxml>*/
+line tangent(explicit arc a, abscissa x)
+{/*<asyxml></code><documentation>Return the tangent of 'a' at 'point(a, x)'.</documentation></function></asyxml>*/
+ abscissa ag = angabscissa(a, point(a, x));
+ return tangent(a.el, ag + a.angle1 + (a.el.e == 0 ? a.angle0 : 0));
+}
+
+/*<asyxml><function type="line" signature="tangent(explicit arc,point)"><code></asyxml>*/
+line tangent(explicit arc a, point M)
+{/*<asyxml></code><documentation>Return the tangent of 'a' at 'M'.
+ The points 'M' must belong to the arc 'a'.</documentation></function></asyxml>*/
+ return tangent(a, angabscissa(a, M));
+}
+
+// *=======================================================*
+// *.......Routines for compatibility with original geometry module........*
+
+path square(pair z1, pair z2)
+{
+ pair v = z2 - z1;
+ pair z3 = z2 + I * v;
+ pair z4 = z3 - v;
+ return z1--z2--z3--z4--cycle;
+}
+
+// Draw a perpendicular symbol at z aligned in the direction align
+// relative to the path z--z + dir.
+void perpendicular(picture pic = currentpicture, pair z, pair align,
+ pair dir = E, real size = 0, pen p = currentpen,
+ margin margin = NoMargin, filltype filltype = NoFill)
+{
+ perpendicularmark(pic, (point) z, align, dir, size, p, margin, filltype);
+}
+
+
+// Draw a perpendicular symbol at z aligned in the direction align
+// relative to the path z--z + dir(g, 0)
+void perpendicular(picture pic = currentpicture, pair z, pair align, path g,
+ real size = 0, pen p = currentpen, margin margin = NoMargin,
+ filltype filltype = NoFill)
+{
+ perpendicularmark(pic, (point) z, align, dir(g, 0), size, p, margin, filltype);
+}
+
+// Return an interior arc BAC of triangle ABC, given a radius r > 0.
+// If r < 0, return the corresponding exterior arc of radius |r|.
+path arc(explicit pair B, explicit pair A, explicit pair C, real r)
+{
+ real BA = degrees(B - A);
+ real CA = degrees(C - A);
+ return arc(A, abs(r), BA, CA, (r < 0) ^ ((BA-CA) % 360 < 180) ? CW : CCW);
+}
+
+// *.......End of compatibility routines........*
+// *=======================================================*
+
+// *........................FOOTER.........................*
+// *=======================================================*
diff --git a/graphics/asymptote/base/graph.asy b/graphics/asymptote/base/graph.asy
new file mode 100644
index 0000000000..7498ce8bfd
--- /dev/null
+++ b/graphics/asymptote/base/graph.asy
@@ -0,0 +1,2243 @@
+private import math;
+import graph_splinetype;
+import graph_settings;
+
+scaleT Linear;
+
+scaleT Log=scaleT(log10,pow10,logarithmic=true);
+scaleT Logarithmic=Log;
+
+string baselinetemplate="$10^4$";
+
+// A linear scale, with optional autoscaling of minimum and maximum values,
+// scaling factor s and intercept.
+scaleT Linear(bool automin=false, bool automax=automin, real s=1,
+ real intercept=0)
+{
+ real sinv=1/s;
+ scalefcn T,Tinv;
+ if(s == 1 && intercept == 0)
+ T=Tinv=identity;
+ else {
+ T=new real(real x) {return (x-intercept)*s;};
+ Tinv=new real(real x) {return x*sinv+intercept;};
+ }
+ return scaleT(T,Tinv,logarithmic=false,automin,automax);
+}
+
+// A logarithmic scale, with optional autoscaling of minimum and maximum
+// values.
+scaleT Log(bool automin=false, bool automax=automin)
+{
+ return scaleT(Log.T,Log.Tinv,logarithmic=true,automin,automax);
+}
+
+// A "broken" linear axis omitting the segment [a,b].
+scaleT Broken(real a, real b, bool automin=false, bool automax=automin)
+{
+ real skip=b-a;
+ real T(real x) {
+ if(x <= a) return x;
+ if(x <= b) return a;
+ return x-skip;
+ }
+ real Tinv(real x) {
+ if(x <= a) return x;
+ return x+skip;
+ }
+ return scaleT(T,Tinv,logarithmic=false,automin,automax);
+}
+
+// A "broken" logarithmic axis omitting the segment [a,b], where a and b are
+// automatically rounded to the nearest integral power of the base.
+scaleT BrokenLog(real a, real b, bool automin=false, bool automax=automin)
+{
+ real A=round(Log.T(a));
+ real B=round(Log.T(b));
+ a=Log.Tinv(A);
+ b=Log.Tinv(B);
+ real skip=B-A;
+ real T(real x) {
+ if(x <= a) return Log.T(x);
+ if(x <= b) return A;
+ return Log.T(x)-skip;
+ }
+ real Tinv(real x) {
+ real X=Log.Tinv(x);
+ if(X <= a) return X;
+ return Log.Tinv(x+skip);
+ }
+ return scaleT(T,Tinv,logarithmic=true,automin,automax);
+}
+
+Label Break=Label("$\approx$",UnFill(0.2mm));
+
+void scale(picture pic=currentpicture, scaleT x, scaleT y=x, scaleT z=y)
+{
+ pic.scale.x.scale=x;
+ pic.scale.y.scale=y;
+ pic.scale.z.scale=z;
+ pic.scale.x.automin=x.automin;
+ pic.scale.y.automin=y.automin;
+ pic.scale.z.automin=z.automin;
+ pic.scale.x.automax=x.automax;
+ pic.scale.y.automax=y.automax;
+ pic.scale.z.automax=z.automax;
+}
+
+void scale(picture pic=currentpicture, bool xautoscale=false,
+ bool yautoscale=xautoscale, bool zautoscale=yautoscale)
+{
+ scale(pic,Linear(xautoscale,xautoscale),Linear(yautoscale,yautoscale),
+ Linear(zautoscale,zautoscale));
+}
+
+struct scientific
+{
+ int sign;
+ real mantissa;
+ int exponent;
+ int ceil() {return sign*ceil(mantissa);}
+ real scale(real x, real exp) {
+ static real max=0.1*realMax;
+ static real limit=-log10(max);
+ return x*(exp > limit ? 10^-exp : max);
+ }
+ real ceil(real x, real exp) {return ceil(sign*scale(abs(x),exp));}
+ real floor(real x, real exp) {return floor(sign*scale(abs(x),exp));}
+}
+
+// Convert x to scientific notation
+scientific scientific(real x)
+{
+ scientific s;
+ s.sign=sgn(x);
+ x=abs(x);
+ if(x == 0) {s.mantissa=0; s.exponent=-intMax; return s;}
+ real logx=log10(x);
+ s.exponent=floor(logx);
+ s.mantissa=s.scale(x,s.exponent);
+ return s;
+}
+
+// Autoscale limits and tick divisor.
+struct bounds {
+ real min;
+ real max;
+
+ // Possible tick intervals:
+ int[] divisor;
+
+ void operator init(real min, real max, int[] divisor=new int[]) {
+ this.min=min;
+ this.max=max;
+ this.divisor=divisor;
+ }
+}
+
+// Compute tick divisors.
+int[] divisors(int a, int b)
+{
+ int[] dlist;
+ int n=b-a;
+ dlist[0]=1;
+ if(n == 1) {dlist[1]=10; dlist[2]=100; return dlist;}
+ if(n == 2) {dlist[1]=2; return dlist;}
+ int sqrtn=floor(sqrt(n));
+ int i=0;
+ for(int d=2; d <= sqrtn; ++d)
+ if(n % d == 0 && (a*b >= 0 || b % (n/d) == 0)) dlist[++i]=d;
+ for(int d=sqrtn; d >= 1; --d)
+ if(n % d == 0 && (a*b >= 0 || b % d == 0)) dlist[++i]=quotient(n,d);
+ return dlist;
+}
+
+real upscale(real b, real a)
+{
+ if(b <= 5) b=5;
+ else if (b > 10 && a >= 0 && b <= 12) b=12;
+ else if (b > 10 && (a >= 0 || 15 % -a == 0) && b <= 15) b=15;
+ else b=ceil(b/10)*10;
+ return b;
+}
+
+// Compute autoscale limits and tick divisor.
+bounds autoscale(real Min, real Max, scaleT scale=Linear)
+{
+ bounds m;
+ if(scale.logarithmic) {
+ m.min=floor(Min);
+ m.max=ceil(Max);
+ return m;
+ }
+ if(!(finite(Min) && finite(Max)))
+ abort("autoscale requires finite limits");
+ Min=scale.Tinv(Min);
+ Max=scale.Tinv(Max);
+ m.min=Min;
+ m.max=Max;
+ if(Min > Max) {real temp=Min; Min=Max; Max=temp;}
+ if(Min == Max) {
+ if(Min == 0) {m.max=1; return m;}
+ if(Min > 0) {Min=0; Max *= 2;}
+ else {Min *= 2; Max=0;}
+ }
+
+ int sign;
+ if(Min < 0 && Max <= 0) {real temp=-Min; Min=-Max; Max=temp; sign=-1;}
+ else sign=1;
+ scientific sa=scientific(Min);
+ scientific sb=scientific(Max);
+ int exp=max(sa.exponent,sb.exponent);
+ real a=sa.floor(Min,exp);
+ real b=sb.ceil(Max,exp);
+
+ void zoom() {
+ --exp;
+ a=sa.floor(Min,exp);
+ b=sb.ceil(Max,exp);
+ }
+
+ if(sb.mantissa <= 1.5)
+ zoom();
+
+ while((b-a)*10.0^exp > 10*(Max-Min))
+ zoom();
+
+ real bsave=b;
+ if(b-a > (a >= 0 ? 8 : 6)) {
+ b=upscale(b,a);
+ if(a >= 0) {
+ if(a <= 5) a=0; else a=floor(a/10)*10;
+ } else a=-upscale(-a,-1);
+ }
+
+ // Redo b in case the value of a has changed
+ if(bsave-a > (a >= 0 ? 8 : 6))
+ b=upscale(bsave,a);
+
+ if(sign == -1) {real temp=-a; a=-b; b=temp;}
+ real Scale=10.0^exp;
+ m.min=scale.T(a*Scale);
+ m.max=scale.T(b*Scale);
+ if(m.min > m.max) {real temp=m.min; m.min=m.max; m.max=temp;}
+ m.divisor=divisors(round(a),round(b));
+ return m;
+}
+
+typedef string ticklabel(real);
+
+ticklabel Format(string s=defaultformat)
+{
+ return new string(real x) {return format(s,x);};
+}
+
+ticklabel OmitFormat(string s=defaultformat ... real[] x)
+{
+ return new string(real v) {
+ string V=format(s,v);
+ for(real a : x)
+ if(format(s,a) == V) return "";
+ return V;
+ };
+}
+
+string trailingzero="$%#$";
+string signedtrailingzero="$%+#$";
+
+ticklabel DefaultFormat=Format();
+ticklabel NoZeroFormat=OmitFormat(0);
+
+// Format tick values as integral powers of base; otherwise with DefaultFormat.
+ticklabel DefaultLogFormat(int base) {
+ return new string(real x) {
+ string exponent=format("%.4f",log(x)/log(base));
+ return find(exponent,".") == -1 ?
+ "$"+(string) base+"^{"+exponent+"}$" : format(x);
+ };
+}
+
+// Format all tick values as powers of base.
+ticklabel LogFormat(int base) {
+ return new string(real x) {
+ return format("$"+(string) base+"^{%g}$",log(x)/log(base));
+ };
+}
+
+ticklabel LogFormat=LogFormat(10);
+ticklabel DefaultLogFormat=DefaultLogFormat(10);
+
+// The default direction specifier.
+pair zero(real) {return 0;}
+
+struct ticklocate {
+ real a,b; // Tick values at point(g,0), point(g,length(g)).
+ autoscaleT S; // Autoscaling transformation.
+ pair dir(real t); // Absolute 2D tick direction.
+ triple dir3(real t); // Absolute 3D tick direction.
+ real time(real v); // Returns the time corresponding to the value v.
+ ticklocate copy() {
+ ticklocate T=new ticklocate;
+ T.a=a;
+ T.b=b;
+ T.S=S.copy();
+ T.dir=dir;
+ T.dir3=dir3;
+ T.time=time;
+ return T;
+ }
+}
+
+autoscaleT defaultS;
+
+typedef real valuetime(real);
+
+valuetime linear(scalefcn S=identity, real Min, real Max)
+{
+ real factor=Max == Min ? 0.0 : 1.0/(Max-Min);
+ return new real(real v) {return (S(v)-Min)*factor;};
+}
+
+ticklocate ticklocate(real a, real b, autoscaleT S=defaultS,
+ real tickmin=-infinity, real tickmax=infinity,
+ real time(real)=null, pair dir(real)=zero)
+{
+ if((valuetime) time == null) time=linear(S.T(),a,b);
+ ticklocate locate;
+ locate.a=a;
+ locate.b=b;
+ locate.S=S.copy();
+ if(finite(tickmin)) locate.S.tickMin=tickmin;
+ if(finite(tickmax)) locate.S.tickMax=tickmax;
+ locate.time=time;
+ locate.dir=dir;
+ return locate;
+}
+
+private struct locateT {
+ real t; // tick location time
+ pair Z; // tick location in frame coordinates
+ pair pathdir; // path direction in frame coordinates
+ pair dir; // tick direction in frame coordinates
+
+ void dir(transform T, path g, ticklocate locate, real t) {
+ pathdir=unit(shiftless(T)*dir(g,t));
+ pair Dir=locate.dir(t);
+ dir=Dir == 0 ? -I*pathdir : unit(Dir);
+ }
+ // Locate the desired position of a tick along a path.
+ void calc(transform T, path g, ticklocate locate, real val) {
+ t=locate.time(val);
+ Z=T*point(g,t);
+ dir(T,g,locate,t);
+ }
+}
+
+pair ticklabelshift(pair align, pen p=currentpen)
+{
+ return 0.25*unit(align)*labelmargin(p);
+}
+
+void drawtick(frame f, transform T, path g, path g2, ticklocate locate,
+ real val, real Size, int sign, pen p, bool extend)
+{
+ locateT locate1,locate2;
+ locate1.calc(T,g,locate,val);
+ if(extend && size(g2) > 0) {
+ locate2.calc(T,g2,locate,val);
+ draw(f,locate1.Z--locate2.Z,p);
+ } else
+ if(sign == 0)
+ draw(f,locate1.Z-Size*locate1.dir--locate1.Z+Size*locate1.dir,p);
+ else
+ draw(f,locate1.Z--locate1.Z+Size*sign*locate1.dir,p);
+}
+
+real zerotickfuzz=10*epsilon;
+
+// Label a tick on a frame.
+pair labeltick(frame d, transform T, path g, ticklocate locate, real val,
+ pair side, int sign, real Size, ticklabel ticklabel,
+ Label F, real norm=0)
+{
+ locateT locate1;
+ locate1.calc(T,g,locate,val);
+ pair align=side*locate1.dir;
+ pair perp=I*locate1.pathdir;
+
+ // Adjust tick label alignment
+ pair adjust=unit(align+0.75perp*sgn(dot(align,perp)));
+ // Project align onto adjusted direction.
+ align=adjust*dot(align,adjust);
+ pair shift=dot(align,-sign*locate1.dir) <= 0 ? align*Size :
+ ticklabelshift(align,F.p);
+
+ real label;
+ if(locate.S.scale.logarithmic)
+ label=locate.S.scale.Tinv(val);
+ else {
+ label=val;
+ if(abs(label) < zerotickfuzz*norm) label=0;
+ // Fix epsilon errors at +/-1e-4
+ // default format changes to scientific notation here
+ if(abs(abs(label)-1e-4) < epsilon) label=sgn(label)*1e-4;
+ }
+
+ string s=ticklabel(label);
+ if(s != "")
+ label(d,F.T*baseline(s,baselinetemplate),locate1.Z+shift,align,F.p,
+ F.filltype);
+ return locate1.pathdir;
+}
+
+// Add axis label L to frame f.
+void labelaxis(frame f, transform T, Label L, path g,
+ ticklocate locate=null, int sign=1, bool ticklabels=false)
+{
+ Label L0=L.copy();
+ real t=L0.relative(g);
+ pair z=point(g,t);
+ pair dir=dir(g,t);
+ pair perp=I*dir;
+ if(locate != null) {
+ locateT locate1;
+ locate1.dir(T,g,locate,t);
+ L0.align(L0.align,unit(-sgn(dot(sign*locate1.dir,perp))*perp));
+ }
+ pair align=L0.align.dir;
+ if(L0.align.relative) align *= -perp;
+ pair alignperp=dot(align,perp)*perp;
+ pair offset;
+ if(ticklabels) {
+ if(piecewisestraight(g)) {
+ real angle=degrees(dir,warn=false);
+ transform S=rotate(-angle,z);
+ frame F=S*f;
+ pair Align=rotate(-angle)*alignperp;
+ offset=unit(alignperp-sign*locate.dir(t))*
+ abs((Align.y >= 0 ? max(F).y : (Align.y < 0 ? min(F).y : 0))-z.y);
+ }
+ z += offset;
+ }
+
+ L0.align(align);
+ L0.position(z);
+ frame d;
+ add(d,L0);
+ pair width=0.5*size(d);
+ int n=length(g);
+ real t=L.relative();
+ pair s=realmult(width,dir(g,t));
+ if(t <= 0) {
+ if(L.align.default) s *= -axislabelfactor;
+ d=shift(s)*d;
+ } else if(t >= n) {
+ if(L.align.default) s *= -axislabelfactor;
+ d=shift(-s)*d;
+ } else if(offset == 0 && L.align.default) {
+ pair s=realmult(width,I*dir(g,t));
+ s=axislabelfactor*s;
+ d=shift(s)*d;
+ }
+ add(f,d);
+}
+
+// Check the tick coverage of a linear axis.
+bool axiscoverage(int N, transform T, path g, ticklocate locate, real Step,
+ pair side, int sign, real Size, Label F, ticklabel ticklabel,
+ real norm, real limit)
+{
+ real coverage=0;
+ bool loop=cyclic(g);
+ real a=locate.S.Tinv(locate.a);
+ real b=locate.S.Tinv(locate.b);
+ real tickmin=finite(locate.S.tickMin) ? locate.S.Tinv(locate.S.tickMin) : a;
+ if(Size > 0) {
+ int count=0;
+ if(loop) count=N+1;
+ else {
+ for(int i=0; i <= N; ++i) {
+ real val=tickmin+i*Step;
+ if(val >= a && val <= b)
+ ++count;
+ }
+ }
+ if(count > 0) limit /= count;
+ for(int i=0; i <= N; ++i) {
+ real val=tickmin+i*Step;
+ if(loop || (val >= a && val <= b)) {
+ frame d;
+ pair dir=labeltick(d,T,g,locate,val,side,sign,Size,ticklabel,F,norm);
+ if(abs(dot(size(d),dir)) > limit) return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Check the tick coverage of a logarithmic axis.
+bool logaxiscoverage(int N, transform T, path g, ticklocate locate, pair side,
+ int sign, real Size, Label F, ticklabel ticklabel,
+ real limit, int first, int last)
+{
+ bool loop=cyclic(g);
+ real coverage=0;
+ real a=locate.a;
+ real b=locate.b;
+ int count=0;
+ for(int i=first-1; i <= last+1; i += N) {
+ if(loop || i >= a && i <= b)
+ ++count;
+ }
+ if(count > 0) limit /= count;
+ for(int i=first-1; i <= last+1; i += N) {
+ if(loop || i >= a && i <= b) {
+ frame d;
+ pair dir=labeltick(d,T,g,locate,i,side,sign,Size,ticklabel,F);
+ if(abs(dot(size(d),dir)) > limit) return false;
+ }
+ }
+ return true;
+}
+
+struct tickvalues {
+ real[] major;
+ real[] minor;
+ int N; // For logarithmic axes: number of decades between tick labels.
+}
+
+// Determine a format that distinguishes adjacent pairs of ticks, optionally
+// adding trailing zeros.
+string autoformat(string format="", real norm ... real[] a)
+{
+ bool trailingzero=(format == trailingzero);
+ bool signedtrailingzero=(format == signedtrailingzero);
+ if(!trailingzero && !signedtrailingzero && format != "") return format;
+
+ real[] A=sort(a);
+ real[] a=abs(A);
+
+ bool signchange=(A.length > 1 && A[0] < 0 && A[A.length-1] >= 0);
+
+ for(int i=0; i < A.length; ++i)
+ if(a[i] < zerotickfuzz*norm) A[i]=a[i]=0;
+
+ int n=0;
+
+ bool Fixed=find(a >= 1e4-epsilon | (a > 0 & a <= 1e-4-epsilon)) < 0;
+
+ string Format=defaultformat(4,fixed=Fixed);
+
+ if(Fixed && n < 4) {
+ for(int i=0; i < A.length; ++i) {
+ real a=A[i];
+ while(format(defaultformat(n,fixed=Fixed),a) != format(Format,a))
+ ++n;
+ }
+ }
+
+ string trailing=trailingzero ? (signchange ? "# " : "#") :
+ signedtrailingzero ? "#+" : "";
+
+ string format=defaultformat(n,trailing,Fixed);
+
+ for(int i=0; i < A.length-1; ++i) {
+ real a=A[i];
+ real b=A[i+1];
+ // Check if an extra digit of precision should be added.
+ string fixedformat="%#."+string(n+1)+"f";
+ string A=format(fixedformat,a);
+ string B=format(fixedformat,b);
+ if(substr(A,length(A)-1,1) != "0" || substr(B,length(B)-1,1) != "0") {
+ a *= 0.1;
+ b *= 0.1;
+ }
+ if(a != b) {
+ while(format(format,a) == format(format,b))
+ format=defaultformat(++n,trailing,Fixed);
+ }
+ }
+
+ if(n == 0) return defaultformat;
+ return format;
+}
+
+// Automatic tick generation routine.
+tickvalues generateticks(int sign, Label F="", ticklabel ticklabel=null,
+ int N, int n=0, real Step=0, real step=0,
+ real Size=0, real size=0,
+ transform T, pair side, path g, real limit,
+ pen p, ticklocate locate, int[] divisor,
+ bool opposite)
+{
+ tickvalues tickvalues;
+ sign=opposite ? -sign : sign;
+ if(Size == 0) Size=Ticksize;
+ if(size == 0) size=ticksize;
+ F=F.copy();
+ F.p(p);
+
+ if(F.align.dir != 0) side=F.align.dir;
+ else if(side == 0) side=((sign == 1) ? left : right);
+
+ bool ticklabels=false;
+ path G=T*g;
+
+ if(!locate.S.scale.logarithmic) {
+ real a=locate.S.Tinv(locate.a);
+ real b=locate.S.Tinv(locate.b);
+ real norm=max(abs(a),abs(b));
+ string format=autoformat(F.s,norm,a,b);
+ if(F.s == "%") F.s="";
+ if(ticklabel == null) ticklabel=Format(format);
+
+ if(a > b) {real temp=a; a=b; b=temp;}
+
+ if(b-a < 100.0*epsilon*norm) b=a;
+
+ bool autotick=Step == 0 && N == 0;
+
+ real tickmin=finite(locate.S.tickMin) && (autotick || locate.S.automin) ?
+ locate.S.Tinv(locate.S.tickMin) : a;
+ real tickmax=finite(locate.S.tickMax) && (autotick || locate.S.automax) ?
+ locate.S.Tinv(locate.S.tickMax) : b;
+ if(tickmin > tickmax) {real temp=tickmin; tickmin=tickmax; tickmax=temp;}
+
+ real inStep=Step;
+
+ bool calcStep=true;
+ real len=tickmax-tickmin;
+ if(autotick) {
+ N=1;
+ if(divisor.length > 0) {
+ bool autoscale=locate.S.automin && locate.S.automax;
+ real h=0.5*(b-a);
+ if(h > 0) {
+ for(int d=divisor.length-1; d >= 0; --d) {
+ int N0=divisor[d];
+ Step=len/N0;
+ int N1=N0;
+ int m=2;
+ while(Step > h) {
+ N0=m*N1;
+ Step=len/N0;
+ m *= 2;
+ }
+ if(axiscoverage(N0,T,g,locate,Step,side,sign,Size,F,ticklabel,norm,
+ limit)) {
+ N=N0;
+ if(N0 == 1 && !autoscale && d < divisor.length-1) {
+ // Try using 2 ticks (otherwise 1);
+ int div=divisor[d+1];
+ Step=quotient(div,2)*len/div;
+ calcStep=false;
+ if(axiscoverage(2,T,g,locate,Step,side,sign,Size,F,ticklabel,
+ norm,limit)) N=2;
+ else Step=len;
+ }
+ // Found a good divisor; now compute subtick divisor
+ if(n == 0) {
+ if(step != 0) n=ceil(Step/step);
+ else {
+ n=quotient(divisor[divisor.length-1],N);
+ if(N == 1) n=(a*b >= 0) ? 2 : 1;
+ if(n == 1) n=2;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if(inStep != 0 && !locate.S.automin) {
+ tickmin=floor(tickmin/Step)*Step;
+ len=tickmax-tickmin;
+ }
+
+ if(calcStep) {
+ if(N == 1) N=2;
+ if(N == 0) N=(int) (len/Step);
+ else Step=len/N;
+ }
+
+ if(n == 0) {
+ if(step != 0) n=ceil(Step/step);
+ } else step=Step/n;
+
+ b += epsilon*norm;
+
+ if(Size > 0) {
+ for(int i=0; i <= N; ++i) {
+ real val=tickmin+i*Step;
+ if(val >= a && val <= b)
+ tickvalues.major.push(val);
+ if(size > 0 && step > 0) {
+ real iStep=i*Step;
+ real jstop=(len-iStep)/step;
+ for(int j=1; j < n && j <= jstop; ++j) {
+ real val=tickmin+iStep+j*step;
+ if(val >= a && val <= b)
+ tickvalues.minor.push(val);
+ }
+ }
+ }
+ }
+
+ } else { // Logarithmic
+ string format=F.s;
+ if(F.s == "%") F.s="";
+
+ int base=round(locate.S.scale.Tinv(1));
+
+ if(ticklabel == null)
+ ticklabel=format == "%" ? Format("") : DefaultLogFormat(base);
+ real a=locate.S.postscale.Tinv(locate.a);
+ real b=locate.S.postscale.Tinv(locate.b);
+ if(a > b) {real temp=a; a=b; b=temp;}
+
+ int first=floor(a-epsilon);
+ int last=ceil(b+epsilon);
+
+ if(N == 0) {
+ N=1;
+ while(N <= last-first) {
+ if(logaxiscoverage(N,T,g,locate,side,sign,Size,F,ticklabel,limit,
+ first,last)) break;
+ ++N;
+ }
+ }
+
+ if(N <= 2 && n == 0) n=base;
+ tickvalues.N=N;
+
+ if(N > 0) {
+ for(int i=first-1; i <= last+1; ++i) {
+ if(i >= a && i <= b)
+ tickvalues.major.push(locate.S.scale.Tinv(i));
+ if(n > 0) {
+ for(int j=2; j < n; ++j) {
+ real val=(i+1+locate.S.scale.T(j/n));
+ if(val >= a && val <= b)
+ tickvalues.minor.push(locate.S.scale.Tinv(val));
+ }
+ }
+ }
+ }
+ }
+ return tickvalues;
+}
+
+// Signature of routines that draw labelled paths with ticks and tick labels.
+typedef void ticks(frame, transform, Label, pair, path, path, pen,
+ arrowbar, margin, ticklocate, int[], bool opposite=false);
+
+// Tick construction routine for a user-specified array of tick values.
+ticks Ticks(int sign, Label F="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ real[] Ticks=new real[], real[] ticks=new real[], int N=1,
+ bool begin=true, bool end=true,
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return new void(frame f, transform t, Label L, pair side, path g, path g2,
+ pen p, arrowbar arrow, margin margin, ticklocate locate,
+ int[] divisor, bool opposite) {
+ // Use local copy of context variables:
+ int sign=opposite ? -sign : sign;
+ pen pTick=pTick;
+ pen ptick=ptick;
+ ticklabel ticklabel=ticklabel;
+
+ real Size=Size;
+ real size=size;
+ if(Size == 0) Size=Ticksize;
+ if(size == 0) size=ticksize;
+
+ Label L=L.copy();
+ Label F=F.copy();
+ L.p(p);
+ F.p(p);
+ if(pTick == nullpen) pTick=p;
+ if(ptick == nullpen) ptick=pTick;
+
+ if(F.align.dir != 0) side=F.align.dir;
+ else if(side == 0) side=F.T*((sign == 1) ? left : right);
+
+ bool ticklabels=false;
+ path G=t*g;
+ path G2=t*g2;
+
+ scalefcn T;
+
+ real a,b;
+ if(locate.S.scale.logarithmic) {
+ a=locate.S.postscale.Tinv(locate.a);
+ b=locate.S.postscale.Tinv(locate.b);
+ T=locate.S.scale.T;
+ } else {
+ a=locate.S.Tinv(locate.a);
+ b=locate.S.Tinv(locate.b);
+ T=identity;
+ }
+
+ if(a > b) {real temp=a; a=b; b=temp;}
+
+ real norm=max(abs(a),abs(b));
+
+ string format=autoformat(F.s,norm...Ticks);
+ if(F.s == "%") F.s="";
+ if(ticklabel == null) {
+ if(locate.S.scale.logarithmic) {
+ int base=round(locate.S.scale.Tinv(1));
+ ticklabel=format == "%" ? Format("") : DefaultLogFormat(base);
+ } else ticklabel=Format(format);
+ }
+
+ begingroup(f);
+ if(opposite) draw(f,G,p);
+ else draw(f,margin(G,p).g,p,arrow);
+ for(int i=(begin ? 0 : 1); i < (end ? Ticks.length : Ticks.length-1); ++i) {
+ real val=T(Ticks[i]);
+ if(val >= a && val <= b)
+ drawtick(f,t,g,g2,locate,val,Size,sign,pTick,extend);
+ }
+ for(int i=0; i < ticks.length; ++i) {
+ real val=T(ticks[i]);
+ if(val >= a && val <= b)
+ drawtick(f,t,g,g2,locate,val,size,sign,ptick,extend);
+ }
+ endgroup(f);
+
+ if(N == 0) N=1;
+ if(Size > 0 && !opposite) {
+ for(int i=(beginlabel ? 0 : 1);
+ i < (endlabel ? Ticks.length : Ticks.length-1); i += N) {
+ real val=T(Ticks[i]);
+ if(val >= a && val <= b) {
+ ticklabels=true;
+ labeltick(f,t,g,locate,val,side,sign,Size,ticklabel,F,norm);
+ }
+ }
+ }
+ if(L.s != "" && !opposite)
+ labelaxis(f,t,L,G,locate,sign,ticklabels);
+ };
+}
+
+// Optional routine to allow modification of auto-generated tick values.
+typedef tickvalues tickmodifier(tickvalues);
+tickvalues None(tickvalues v) {return v;}
+
+// Tickmodifier that removes all ticks in the intervals [a[i],b[i]].
+tickmodifier OmitTickIntervals(real[] a, real[] b) {
+ return new tickvalues(tickvalues v) {
+ if(a.length != b.length) abort(differentlengths);
+ void omit(real[] A) {
+ if(A.length != 0) {
+ real norm=max(abs(A));
+ for(int i=0; i < a.length; ++i) {
+ int j;
+ while((j=find(A > a[i]-zerotickfuzz*norm
+ & A < b[i]+zerotickfuzz*norm)) >= 0) {
+ A.delete(j);
+ }
+ }
+ }
+ }
+ omit(v.major);
+ omit(v.minor);
+ return v;
+ };
+}
+
+// Tickmodifier that removes all ticks in the interval [a,b].
+tickmodifier OmitTickInterval(real a, real b) {
+ return OmitTickIntervals(new real[] {a}, new real[] {b});
+}
+
+// Tickmodifier that removes the specified ticks.
+tickmodifier OmitTick(... real[] x) {
+ return OmitTickIntervals(x,x);
+}
+
+tickmodifier NoZero=OmitTick(0);
+
+tickmodifier Break(real, real)=OmitTickInterval;
+
+// Automatic tick construction routine.
+ticks Ticks(int sign, Label F="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true, tickmodifier modify=None,
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return new void(frame f, transform T, Label L, pair side, path g, path g2,
+ pen p, arrowbar arrow, margin margin, ticklocate locate,
+ int[] divisor, bool opposite) {
+ real limit=Step == 0 ? axiscoverage*arclength(T*g) : 0;
+ tickvalues values=modify(generateticks(sign,F,ticklabel,N,n,Step,step,
+ Size,size,T,side,g,
+ limit,p,locate,divisor,opposite));
+
+ Ticks(sign,F,ticklabel,beginlabel,endlabel,values.major,values.minor,
+ values.N,begin,end,Size,size,extend,pTick,ptick)
+ (f,T,L,side,g,g2,p,arrow,margin,locate,divisor,opposite);
+ };
+}
+
+ticks NoTicks()
+{
+ return new void(frame f, transform T, Label L, pair, path g, path, pen p,
+ arrowbar arrow, margin margin, ticklocate,
+ int[], bool opposite) {
+ path G=T*g;
+ if(opposite) draw(f,G,p);
+ else {
+ draw(f,margin(G,p).g,p,arrow);
+ if(L.s != "") {
+ Label L=L.copy();
+ L.p(p);
+ labelaxis(f,T,L,G);
+ }
+ }
+ };
+}
+
+ticks LeftTicks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N=0, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true, tickmodifier modify=None,
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks(-1,format,ticklabel,beginlabel,endlabel,N,n,Step,step,
+ begin,end,modify,Size,size,extend,pTick,ptick);
+}
+
+ticks RightTicks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N=0, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true, tickmodifier modify=None,
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks(1,format,ticklabel,beginlabel,endlabel,N,n,Step,step,
+ begin,end,modify,Size,size,extend,pTick,ptick);
+}
+
+ticks Ticks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N=0, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true, tickmodifier modify=None,
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks(0,format,ticklabel,beginlabel,endlabel,N,n,Step,step,
+ begin,end,modify,Size,size,extend,pTick,ptick);
+}
+
+ticks LeftTicks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ real[] Ticks, real[] ticks=new real[],
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks(-1,format,ticklabel,beginlabel,endlabel,
+ Ticks,ticks,Size,size,extend,pTick,ptick);
+}
+
+ticks RightTicks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ real[] Ticks, real[] ticks=new real[],
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks(1,format,ticklabel,beginlabel,endlabel,
+ Ticks,ticks,Size,size,extend,pTick,ptick);
+}
+
+ticks Ticks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ real[] Ticks, real[] ticks=new real[],
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks(0,format,ticklabel,beginlabel,endlabel,
+ Ticks,ticks,Size,size,extend,pTick,ptick);
+}
+
+ticks NoTicks=NoTicks(),
+LeftTicks=LeftTicks(),
+RightTicks=RightTicks(),
+Ticks=Ticks();
+
+pair tickMin(picture pic)
+{
+ return minbound(pic.userMin(),(pic.scale.x.tickMin,pic.scale.y.tickMin));
+}
+
+pair tickMax(picture pic)
+{
+ return maxbound(pic.userMax(),(pic.scale.x.tickMax,pic.scale.y.tickMax));
+}
+
+int Min=-1;
+int Value=0;
+int Max=1;
+int Both=2;
+
+// Structure used to communicate axis and autoscale settings to tick routines.
+struct axisT {
+ int type; // -1 = min, 0 = given value, 1 = max, 2 = min/max
+ int type2; // for 3D axis
+ real value;
+ real value2;
+ pair side; // 2D tick label direction relative to path (left or right)
+ real position; // label position along axis
+ align align; // default axis label alignment and 3D tick label direction
+ int[] xdivisor;
+ int[] ydivisor;
+ int[] zdivisor;
+ bool extend; // extend axis to graph boundary?
+};
+
+axisT axis;
+typedef void axis(picture, axisT);
+
+axis Bottom(bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Min;
+ axis.position=0.5;
+ axis.side=right;
+ axis.align=S;
+ axis.extend=extend;
+ };
+}
+
+axis Top(bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Max;
+ axis.position=0.5;
+ axis.side=left;
+ axis.align=N;
+ axis.extend=extend;
+ };
+}
+
+axis BottomTop(bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Both;
+ axis.position=0.5;
+ axis.side=right;
+ axis.align=S;
+ axis.extend=extend;
+ };
+}
+
+axis Left(bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Min;
+ axis.position=0.5;
+ axis.side=left;
+ axis.align=W;
+ axis.extend=extend;
+ };
+}
+
+axis Right(bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Max;
+ axis.position=0.5;
+ axis.side=right;
+ axis.align=E;
+ axis.extend=extend;
+ };
+}
+
+axis LeftRight(bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Both;
+ axis.position=0.5;
+ axis.side=left;
+ axis.align=W;
+ axis.extend=extend;
+ };
+}
+
+axis XEquals(real x, bool extend=true)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Value;
+ axis.value=pic.scale.x.T(x);
+ axis.position=1;
+ axis.side=left;
+ axis.align=W;
+ axis.extend=extend;
+ };
+}
+
+axis YEquals(real y, bool extend=true)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Value;
+ axis.value=pic.scale.y.T(y);
+ axis.position=1;
+ axis.side=right;
+ axis.align=S;
+ axis.extend=extend;
+ };
+}
+
+axis XZero(bool extend=true)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Value;
+ axis.value=pic.scale.x.T(pic.scale.x.scale.logarithmic ? 1 : 0);
+ axis.position=1;
+ axis.side=left;
+ axis.align=W;
+ axis.extend=extend;
+ };
+}
+
+axis YZero(bool extend=true)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Value;
+ axis.value=pic.scale.y.T(pic.scale.y.scale.logarithmic ? 1 : 0);
+ axis.position=1;
+ axis.side=right;
+ axis.align=S;
+ axis.extend=extend;
+ };
+}
+
+axis Bottom=Bottom(),
+Top=Top(),
+BottomTop=BottomTop(),
+Left=Left(),
+Right=Right(),
+LeftRight=LeftRight(),
+XZero=XZero(),
+YZero=YZero();
+
+// Draw a general axis.
+void axis(picture pic=currentpicture, Label L="", path g, path g2=nullpath,
+ pen p=currentpen, ticks ticks, ticklocate locate,
+ arrowbar arrow=None, margin margin=NoMargin,
+ int[] divisor=new int[], bool above=false, bool opposite=false)
+{
+ Label L=L.copy();
+ real t=reltime(g,0.5);
+ if(L.defaultposition) L.position(t);
+ divisor=copy(divisor);
+ locate=locate.copy();
+ pic.add(new void (frame f, transform t, transform T, pair lb, pair rt) {
+ frame d;
+ ticks(d,t,L,0,g,g2,p,arrow,margin,locate,divisor,opposite);
+ (above ? add : prepend)(f,t*T*inverse(t)*d);
+ });
+
+ pic.addPath(g,p);
+
+ if(L.s != "") {
+ frame f;
+ Label L0=L.copy();
+ L0.position(0);
+ add(f,L0);
+ pair pos=point(g,L.relative()*length(g));
+ pic.addBox(pos,pos,min(f),max(f));
+ }
+}
+
+real xtrans(transform t, real x)
+{
+ return (t*(x,0)).x;
+}
+
+real ytrans(transform t, real y)
+{
+ return (t*(0,y)).y;
+}
+
+// An internal routine to draw an x axis at a particular y value.
+void xaxisAt(picture pic=currentpicture, Label L="", axis axis,
+ real xmin=-infinity, real xmax=infinity, pen p=currentpen,
+ ticks ticks=NoTicks, arrowbar arrow=None, margin margin=NoMargin,
+ bool above=true, bool opposite=false)
+{
+ real y=axis.value;
+ real y2;
+ Label L=L.copy();
+ int[] divisor=copy(axis.xdivisor);
+ pair side=axis.side;
+ int type=axis.type;
+
+ pic.add(new void (frame f, transform t, transform T, pair lb, pair rt) {
+ transform tinv=inverse(t);
+ pair a=xmin == -infinity ? tinv*(lb.x-min(p).x,ytrans(t,y)) : (xmin,y);
+ pair b=xmax == infinity ? tinv*(rt.x-max(p).x,ytrans(t,y)) : (xmax,y);
+ pair a2=xmin == -infinity ? tinv*(lb.x-min(p).x,ytrans(t,y2)) : (xmin,y2);
+ pair b2=xmax == infinity ? tinv*(rt.x-max(p).x,ytrans(t,y2)) : (xmax,y2);
+
+ if(xmin == -infinity || xmax == infinity) {
+ bounds mx=autoscale(a.x,b.x,pic.scale.x.scale);
+ pic.scale.x.tickMin=mx.min;
+ pic.scale.x.tickMax=mx.max;
+ divisor=mx.divisor;
+ }
+
+ real fuzz=epsilon*max(abs(a.x),abs(b.x));
+ a -= (fuzz,0);
+ b += (fuzz,0);
+
+ frame d;
+ ticks(d,t,L,side,a--b,finite(y2) ? a2--b2 : nullpath,p,arrow,margin,
+ ticklocate(a.x,b.x,pic.scale.x),divisor,opposite);
+ (above ? add : prepend)(f,t*T*tinv*d);
+ });
+
+ void bounds() {
+ if(type == Both) {
+ y2=pic.scale.y.automax() ? tickMax(pic).y : pic.userMax().y;
+ y=opposite ? y2 :
+ (pic.scale.y.automin() ? tickMin(pic).y : pic.userMin().y);
+ }
+ else if(type == Min)
+ y=pic.scale.y.automin() ? tickMin(pic).y : pic.userMin().y;
+ else if(type == Max)
+ y=pic.scale.y.automax() ? tickMax(pic).y : pic.userMax().y;
+
+ real Xmin=finite(xmin) ? xmin : pic.userMin().x;
+ real Xmax=finite(xmax) ? xmax : pic.userMax().x;
+
+ pair a=(Xmin,y);
+ pair b=(Xmax,y);
+ pair a2=(Xmin,y2);
+ pair b2=(Xmax,y2);
+
+ if(finite(a)) {
+ pic.addPoint(a,min(p));
+ pic.addPoint(a,max(p));
+ }
+
+ if(finite(b)) {
+ pic.addPoint(b,min(p));
+ pic.addPoint(b,max(p));
+ }
+
+ if(finite(a) && finite(b)) {
+ frame d;
+ ticks(d,pic.scaling(warn=false),L,side,
+ (a.x,0)--(b.x,0),(a2.x,0)--(b2.x,0),p,arrow,margin,
+ ticklocate(a.x,b.x,pic.scale.x),divisor,opposite);
+ frame f;
+ if(L.s != "") {
+ Label L0=L.copy();
+ L0.position(0);
+ add(f,L0);
+ }
+ pair pos=a+L.relative()*(b-a);
+ pic.addBox(pos,pos,(min(f).x,min(d).y),(max(f).x,max(d).y));
+ }
+ }
+
+ // Process any queued y axis bound calculation requests.
+ for(int i=0; i < pic.scale.y.bound.length; ++i)
+ pic.scale.y.bound[i]();
+
+ pic.scale.y.bound.delete();
+
+ bounds();
+
+ // Request another x bounds calculation before final picture scaling.
+ pic.scale.x.bound.push(bounds);
+}
+
+// An internal routine to draw a y axis at a particular x value.
+void yaxisAt(picture pic=currentpicture, Label L="", axis axis,
+ real ymin=-infinity, real ymax=infinity, pen p=currentpen,
+ ticks ticks=NoTicks, arrowbar arrow=None, margin margin=NoMargin,
+ bool above=true, bool opposite=false)
+{
+ real x=axis.value;
+ real x2;
+ Label L=L.copy();
+ int[] divisor=copy(axis.ydivisor);
+ pair side=axis.side;
+ int type=axis.type;
+
+ pic.add(new void (frame f, transform t, transform T, pair lb, pair rt) {
+ transform tinv=inverse(t);
+ pair a=ymin == -infinity ? tinv*(xtrans(t,x),lb.y-min(p).y) : (x,ymin);
+ pair b=ymax == infinity ? tinv*(xtrans(t,x),rt.y-max(p).y) : (x,ymax);
+ pair a2=ymin == -infinity ? tinv*(xtrans(t,x2),lb.y-min(p).y) : (x2,ymin);
+ pair b2=ymax == infinity ? tinv*(xtrans(t,x2),rt.y-max(p).y) : (x2,ymax);
+
+ if(ymin == -infinity || ymax == infinity) {
+ bounds my=autoscale(a.y,b.y,pic.scale.y.scale);
+ pic.scale.y.tickMin=my.min;
+ pic.scale.y.tickMax=my.max;
+ divisor=my.divisor;
+ }
+
+ real fuzz=epsilon*max(abs(a.y),abs(b.y));
+ a -= (0,fuzz);
+ b += (0,fuzz);
+
+ frame d;
+ ticks(d,t,L,side,a--b,finite(x2) ? a2--b2 : nullpath,p,arrow,margin,
+ ticklocate(a.y,b.y,pic.scale.y),divisor,opposite);
+ (above ? add : prepend)(f,t*T*tinv*d);
+ });
+
+ void bounds() {
+ if(type == Both) {
+ x2=pic.scale.x.automax() ? tickMax(pic).x : pic.userMax().x;
+ x=opposite ? x2 :
+ (pic.scale.x.automin() ? tickMin(pic).x : pic.userMin().x);
+ } else if(type == Min)
+ x=pic.scale.x.automin() ? tickMin(pic).x : pic.userMin().x;
+ else if(type == Max)
+ x=pic.scale.x.automax() ? tickMax(pic).x : pic.userMax().x;
+
+ real Ymin=finite(ymin) ? ymin : pic.userMin().y;
+ real Ymax=finite(ymax) ? ymax : pic.userMax().y;
+
+ pair a=(x,Ymin);
+ pair b=(x,Ymax);
+ pair a2=(x2,Ymin);
+ pair b2=(x2,Ymax);
+
+ if(finite(a)) {
+ pic.addPoint(a,min(p));
+ pic.addPoint(a,max(p));
+ }
+
+ if(finite(b)) {
+ pic.addPoint(b,min(p));
+ pic.addPoint(b,max(p));
+ }
+
+ if(finite(a) && finite(b)) {
+ frame d;
+ ticks(d,pic.scaling(warn=false),L,side,
+ (0,a.y)--(0,b.y),(0,a2.y)--(0,b2.y),p,arrow,margin,
+ ticklocate(a.y,b.y,pic.scale.y),divisor,opposite);
+ frame f;
+ if(L.s != "") {
+ Label L0=L.copy();
+ L0.position(0);
+ add(f,L0);
+ }
+ pair pos=a+L.relative()*(b-a);
+ pic.addBox(pos,pos,(min(d).x,min(f).y),(max(d).x,max(f).y));
+ }
+ }
+
+ // Process any queued x axis bound calculation requests.
+ for(int i=0; i < pic.scale.x.bound.length; ++i)
+ pic.scale.x.bound[i]();
+
+ pic.scale.x.bound.delete();
+
+ bounds();
+
+ // Request another y bounds calculation before final picture scaling.
+ pic.scale.y.bound.push(bounds);
+}
+
+// Set the x limits of a picture.
+void xlimits(picture pic=currentpicture, real min=-infinity, real max=infinity,
+ bool crop=NoCrop)
+{
+ if(min > max) return;
+
+ pic.scale.x.automin=min <= -infinity;
+ pic.scale.x.automax=max >= infinity;
+
+ bounds mx;
+ if(pic.scale.x.automin() || pic.scale.x.automax())
+ mx=autoscale(pic.userMin().x,pic.userMax().x,pic.scale.x.scale);
+
+ if(pic.scale.x.automin) {
+ if(pic.scale.x.automin()) pic.userMinx(mx.min);
+ } else pic.userMinx(min(pic.scale.x.T(min),pic.scale.x.T(max)));
+
+ if(pic.scale.x.automax) {
+ if(pic.scale.x.automax()) pic.userMaxx(mx.max);
+ } else pic.userMaxx(max(pic.scale.x.T(min),pic.scale.x.T(max)));
+
+ if(crop) {
+ pair userMin=pic.userMin();
+ pair userMax=pic.userMax();
+ pic.bounds.xclip(userMin.x,userMax.x);
+ pic.clip(userMin, userMax,
+ new void (frame f, transform t, transform T, pair, pair) {
+ frame Tinvf=T == identity() ? f : t*inverse(T)*inverse(t)*f;
+ clip(f,T*box(((t*userMin).x,(min(Tinvf)).y),
+ ((t*userMax).x,(max(Tinvf)).y)));
+ });
+ }
+}
+
+// Set the y limits of a picture.
+void ylimits(picture pic=currentpicture, real min=-infinity, real max=infinity,
+ bool crop=NoCrop)
+{
+ if(min > max) return;
+
+ pic.scale.y.automin=min <= -infinity;
+ pic.scale.y.automax=max >= infinity;
+
+ bounds my;
+ if(pic.scale.y.automin() || pic.scale.y.automax())
+ my=autoscale(pic.userMin().y,pic.userMax().y,pic.scale.y.scale);
+
+ if(pic.scale.y.automin) {
+ if(pic.scale.y.automin()) pic.userMiny(my.min);
+ } else pic.userMiny(min(pic.scale.y.T(min),pic.scale.y.T(max)));
+
+ if(pic.scale.y.automax) {
+ if(pic.scale.y.automax()) pic.userMaxy(my.max);
+ } else pic.userMaxy(max(pic.scale.y.T(min),pic.scale.y.T(max)));
+
+ if(crop) {
+ pair userMin=pic.userMin();
+ pair userMax=pic.userMax();
+ pic.bounds.yclip(userMin.y,userMax.y);
+ pic.clip(userMin, userMax,
+ new void (frame f, transform t, transform T, pair, pair) {
+ frame Tinvf=T == identity() ? f : t*inverse(T)*inverse(t)*f;
+ clip(f,T*box(((min(Tinvf)).x,(t*userMin).y),
+ ((max(Tinvf)).x,(t*userMax).y)));
+ });
+ }
+}
+
+// Crop a picture to the current user-space picture limits.
+void crop(picture pic=currentpicture)
+{
+ xlimits(pic,false);
+ ylimits(pic,false);
+ if(pic.userSetx() && pic.userSety())
+ clip(pic,box(pic.userMin(),pic.userMax()));
+}
+
+// Restrict the x and y limits to box(min,max).
+void limits(picture pic=currentpicture, pair min, pair max, bool crop=NoCrop)
+{
+ xlimits(pic,min.x,max.x);
+ ylimits(pic,min.y,max.y);
+ if(crop && pic.userSetx() && pic.userSety())
+ clip(pic,box(pic.userMin(),pic.userMax()));
+}
+
+// Internal routine to autoscale the user limits of a picture.
+void autoscale(picture pic=currentpicture, axis axis)
+{
+ if(!pic.scale.set) {
+ bounds mx,my;
+ pic.scale.set=true;
+
+ if(pic.userSetx()) {
+ mx=autoscale(pic.userMin().x,pic.userMax().x,pic.scale.x.scale);
+ if(pic.scale.x.scale.logarithmic &&
+ floor(pic.userMin().x) == floor(pic.userMax().x)) {
+ if(pic.scale.x.automin())
+ pic.userMinx2(floor(pic.userMin().x));
+ if(pic.scale.x.automax())
+ pic.userMaxx2(ceil(pic.userMax().x));
+ }
+ } else {mx.min=mx.max=0; pic.scale.set=false;}
+
+ if(pic.userSety()) {
+ my=autoscale(pic.userMin().y,pic.userMax().y,pic.scale.y.scale);
+ if(pic.scale.y.scale.logarithmic &&
+ floor(pic.userMin().y) == floor(pic.userMax().y)) {
+ if(pic.scale.y.automin())
+ pic.userMiny2(floor(pic.userMin().y));
+ if(pic.scale.y.automax())
+ pic.userMaxy2(ceil(pic.userMax().y));
+ }
+ } else {my.min=my.max=0; pic.scale.set=false;}
+
+ pic.scale.x.tickMin=mx.min;
+ pic.scale.x.tickMax=mx.max;
+ pic.scale.y.tickMin=my.min;
+ pic.scale.y.tickMax=my.max;
+ axis.xdivisor=mx.divisor;
+ axis.ydivisor=my.divisor;
+ }
+}
+
+// Draw an x axis.
+void xaxis(picture pic=currentpicture, Label L="", axis axis=YZero,
+ real xmin=-infinity, real xmax=infinity, pen p=currentpen,
+ ticks ticks=NoTicks, arrowbar arrow=None, margin margin=NoMargin,
+ bool above=false)
+{
+ if(xmin > xmax) return;
+
+ if(pic.scale.x.automin && xmin > -infinity) pic.scale.x.automin=false;
+ if(pic.scale.x.automax && xmax < infinity) pic.scale.x.automax=false;
+
+ if(!pic.scale.set) {
+ axis(pic,axis);
+ autoscale(pic,axis);
+ }
+
+ Label L=L.copy();
+ bool newticks=false;
+
+ if(xmin != -infinity) {
+ xmin=pic.scale.x.T(xmin);
+ newticks=true;
+ }
+
+ if(xmax != infinity) {
+ xmax=pic.scale.x.T(xmax);
+ newticks=true;
+ }
+
+ if(newticks && pic.userSetx() && ticks != NoTicks) {
+ if(xmin == -infinity) xmin=pic.userMin().x;
+ if(xmax == infinity) xmax=pic.userMax().x;
+ bounds mx=autoscale(xmin,xmax,pic.scale.x.scale);
+ pic.scale.x.tickMin=mx.min;
+ pic.scale.x.tickMax=mx.max;
+ axis.xdivisor=mx.divisor;
+ }
+
+ axis(pic,axis);
+
+ if(xmin == -infinity && !axis.extend) {
+ if(pic.scale.set)
+ xmin=pic.scale.x.automin() ? pic.scale.x.tickMin :
+ max(pic.scale.x.tickMin,pic.userMin().x);
+ else xmin=pic.userMin().x;
+ }
+
+ if(xmax == infinity && !axis.extend) {
+ if(pic.scale.set)
+ xmax=pic.scale.x.automax() ? pic.scale.x.tickMax :
+ min(pic.scale.x.tickMax,pic.userMax().x);
+ else xmax=pic.userMax().x;
+ }
+
+ if(L.defaultposition) L.position(axis.position);
+ L.align(L.align,axis.align);
+
+ xaxisAt(pic,L,axis,xmin,xmax,p,ticks,arrow,margin,above);
+ if(axis.type == Both)
+ xaxisAt(pic,L,axis,xmin,xmax,p,ticks,arrow,margin,above,true);
+}
+
+// Draw a y axis.
+void yaxis(picture pic=currentpicture, Label L="", axis axis=XZero,
+ real ymin=-infinity, real ymax=infinity, pen p=currentpen,
+ ticks ticks=NoTicks, arrowbar arrow=None, margin margin=NoMargin,
+ bool above=false, bool autorotate=true)
+{
+ if(ymin > ymax) return;
+
+ if(pic.scale.y.automin && ymin > -infinity) pic.scale.y.automin=false;
+ if(pic.scale.y.automax && ymax < infinity) pic.scale.y.automax=false;
+
+ if(!pic.scale.set) {
+ axis(pic,axis);
+ autoscale(pic,axis);
+ }
+
+ Label L=L.copy();
+ bool newticks=false;
+
+ if(ymin != -infinity) {
+ ymin=pic.scale.y.T(ymin);
+ newticks=true;
+ }
+
+ if(ymax != infinity) {
+ ymax=pic.scale.y.T(ymax);
+ newticks=true;
+ }
+
+ if(newticks && pic.userSety() && ticks != NoTicks) {
+ if(ymin == -infinity) ymin=pic.userMin().y;
+ if(ymax == infinity) ymax=pic.userMax().y;
+ bounds my=autoscale(ymin,ymax,pic.scale.y.scale);
+ pic.scale.y.tickMin=my.min;
+ pic.scale.y.tickMax=my.max;
+ axis.ydivisor=my.divisor;
+ }
+
+ axis(pic,axis);
+
+ if(ymin == -infinity && !axis.extend) {
+ if(pic.scale.set)
+ ymin=pic.scale.y.automin() ? pic.scale.y.tickMin :
+ max(pic.scale.y.tickMin,pic.userMin().y);
+ else ymin=pic.userMin().y;
+ }
+
+
+ if(ymax == infinity && !axis.extend) {
+ if(pic.scale.set)
+ ymax=pic.scale.y.automax() ? pic.scale.y.tickMax :
+ min(pic.scale.y.tickMax,pic.userMax().y);
+ else ymax=pic.userMax().y;
+ }
+
+ if(L.defaultposition) L.position(axis.position);
+ L.align(L.align,axis.align);
+
+ if(autorotate && L.defaulttransform) {
+ frame f;
+ add(f,Label(L.s,(0,0),L.p));
+ if(length(max(f)-min(f)) > ylabelwidth*fontsize(L.p))
+ L.transform(rotate(90));
+ }
+
+ yaxisAt(pic,L,axis,ymin,ymax,p,ticks,arrow,margin,above);
+ if(axis.type == Both)
+ yaxisAt(pic,L,axis,ymin,ymax,p,ticks,arrow,margin,above,true);
+}
+
+// Draw x and y axes.
+void axes(picture pic=currentpicture, Label xlabel="", Label ylabel="",
+ bool extend=true,
+ pair min=(-infinity,-infinity), pair max=(infinity,infinity),
+ pen p=currentpen, arrowbar arrow=None, margin margin=NoMargin,
+ bool above=false)
+{
+ xaxis(pic,xlabel,YZero(extend),min.x,max.x,p,arrow,margin,above);
+ yaxis(pic,ylabel,XZero(extend),min.y,max.y,p,arrow,margin,above);
+}
+
+// Draw a yaxis at x.
+void xequals(picture pic=currentpicture, Label L="", real x,
+ bool extend=false, real ymin=-infinity, real ymax=infinity,
+ pen p=currentpen, ticks ticks=NoTicks,
+ arrowbar arrow=None, margin margin=NoMargin, bool above=true)
+{
+ yaxis(pic,L,XEquals(x,extend),ymin,ymax,p,ticks,arrow,margin,above);
+}
+
+// Draw an xaxis at y.
+void yequals(picture pic=currentpicture, Label L="", real y,
+ bool extend=false, real xmin=-infinity, real xmax=infinity,
+ pen p=currentpen, ticks ticks=NoTicks,
+ arrowbar arrow=None, margin margin=NoMargin, bool above=true)
+{
+ xaxis(pic,L,YEquals(y,extend),xmin,xmax,p,ticks,arrow,margin,above);
+}
+
+pair Scale(picture pic=currentpicture, pair z)
+{
+ return (pic.scale.x.T(z.x),pic.scale.y.T(z.y));
+}
+
+real ScaleX(picture pic=currentpicture, real x)
+{
+ return pic.scale.x.T(x);
+}
+
+real ScaleY(picture pic=currentpicture, real y)
+{
+ return pic.scale.y.T(y);
+}
+
+// Draw a tick of length size at pair z in direction dir using pen p.
+void tick(picture pic=currentpicture, pair z, pair dir, real size=Ticksize,
+ pen p=currentpen)
+{
+ pair z=Scale(pic,z);
+ pic.add(new void (frame f, transform t) {
+ pair tz=t*z;
+ draw(f,tz--tz+unit(dir)*size,p);
+ });
+ pic.addPoint(z,p);
+ pic.addPoint(z,unit(dir)*size,p);
+}
+
+void xtick(picture pic=currentpicture, explicit pair z, pair dir=N,
+ real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,z,dir,size,p);
+}
+
+void xtick(picture pic=currentpicture, real x, pair dir=N,
+ real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,(x,pic.scale.y.scale.logarithmic ? 1 : 0),dir,size,p);
+}
+
+void ytick(picture pic=currentpicture, explicit pair z, pair dir=E,
+ real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,z,dir,size,p);
+}
+
+void ytick(picture pic=currentpicture, real y, pair dir=E,
+ real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,(pic.scale.x.scale.logarithmic ? 1 : 0,y),dir,size,p);
+}
+
+void tick(picture pic=currentpicture, Label L, real value, explicit pair z,
+ pair dir, string format="", real size=Ticksize, pen p=currentpen)
+{
+ Label L=L.copy();
+ L.position(Scale(pic,z));
+ L.align(L.align,-dir);
+ if(shift(L.T)*0 == 0)
+ L.T=shift(dot(dir,L.align.dir) > 0 ? dir*size :
+ ticklabelshift(L.align.dir,p))*L.T;
+ L.p(p);
+ if(L.s == "") L.s=format(format == "" ? defaultformat : format,value);
+ L.s=baseline(L.s,baselinetemplate);
+ add(pic,L);
+ tick(pic,z,dir,size,p);
+}
+
+void xtick(picture pic=currentpicture, Label L, explicit pair z, pair dir=N,
+ string format="", real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,L,z.x,z,dir,format,size,p);
+}
+
+void xtick(picture pic=currentpicture, Label L, real x, pair dir=N,
+ string format="", real size=Ticksize, pen p=currentpen)
+{
+ xtick(pic,L,(x,pic.scale.y.scale.logarithmic ? 1 : 0),dir,size,p);
+}
+
+void ytick(picture pic=currentpicture, Label L, explicit pair z, pair dir=E,
+ string format="", real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,L,z.y,z,dir,format,size,p);
+}
+
+void ytick(picture pic=currentpicture, Label L, real y, pair dir=E,
+ string format="", real size=Ticksize, pen p=currentpen)
+{
+ xtick(pic,L,(pic.scale.x.scale.logarithmic ? 1 : 0,y),dir,format,size,p);
+}
+
+private void label(picture pic, Label L, pair z, real x, align align,
+ string format, pen p)
+{
+ Label L=L.copy();
+ L.position(z);
+ L.align(align);
+ L.p(p);
+ if(shift(L.T)*0 == 0)
+ L.T=shift(ticklabelshift(L.align.dir,L.p))*L.T;
+ if(L.s == "") L.s=format(format == "" ? defaultformat : format,x);
+ L.s=baseline(L.s,baselinetemplate);
+ add(pic,L);
+}
+
+// Put a label on the x axis.
+void labelx(picture pic=currentpicture, Label L="", explicit pair z,
+ align align=S, string format="", pen p=currentpen)
+{
+ label(pic,L,Scale(pic,z),z.x,align,format,p);
+}
+
+void labelx(picture pic=currentpicture, Label L="", real x,
+ align align=S, string format="", pen p=currentpen)
+{
+ labelx(pic,L,(x,pic.scale.y.scale.logarithmic ? 1 : 0),align,format,p);
+}
+
+void labelx(picture pic=currentpicture, Label L,
+ string format="", explicit pen p=currentpen)
+{
+ labelx(pic,L,L.position.position,format,p);
+}
+
+// Put a label on the y axis.
+void labely(picture pic=currentpicture, Label L="", explicit pair z,
+ align align=W, string format="", pen p=currentpen)
+{
+ label(pic,L,Scale(pic,z),z.y,align,format,p);
+}
+
+void labely(picture pic=currentpicture, Label L="", real y,
+ align align=W, string format="", pen p=currentpen)
+{
+ labely(pic,L,(pic.scale.x.scale.logarithmic ? 1 : 0,y),align,format,p);
+}
+
+void labely(picture pic=currentpicture, Label L,
+ string format="", explicit pen p=currentpen)
+{
+ labely(pic,L,L.position.position,format,p);
+}
+
+private string noprimary="Primary axis must be drawn before secondary axis";
+
+// Construct a secondary X axis
+picture secondaryX(picture primary=currentpicture, void f(picture))
+{
+ if(!primary.scale.set) abort(noprimary);
+ picture pic;
+ size(pic,primary);
+ if(primary.userMax().x == primary.userMin().x) return pic;
+ f(pic);
+ if(!pic.userSetx()) return pic;
+ bounds a=autoscale(pic.userMin().x,pic.userMax().x,pic.scale.x.scale);
+ real bmin=pic.scale.x.automin() ? a.min : pic.userMin().x;
+ real bmax=pic.scale.x.automax() ? a.max : pic.userMax().x;
+
+ real denom=bmax-bmin;
+ if(denom != 0) {
+ pic.erase();
+ real m=(primary.userMax().x-primary.userMin().x)/denom;
+ pic.scale.x.postscale=Linear(m,bmin-primary.userMin().x/m);
+ pic.scale.set=true;
+ pic.scale.x.tickMin=pic.scale.x.postscale.T(a.min);
+ pic.scale.x.tickMax=pic.scale.x.postscale.T(a.max);
+ pic.scale.y.tickMin=primary.userMin().y;
+ pic.scale.y.tickMax=primary.userMax().y;
+ axis.xdivisor=a.divisor;
+ f(pic);
+ }
+ pic.userCopy(primary);
+ return pic;
+}
+
+// Construct a secondary Y axis
+picture secondaryY(picture primary=currentpicture, void f(picture))
+{
+ if(!primary.scale.set) abort(noprimary);
+ picture pic;
+ size(pic,primary);
+ if(primary.userMax().y == primary.userMin().y) return pic;
+ f(pic);
+ if(!pic.userSety()) return pic;
+ bounds a=autoscale(pic.userMin().y,pic.userMax().y,pic.scale.y.scale);
+ real bmin=pic.scale.y.automin() ? a.min : pic.userMin().y;
+ real bmax=pic.scale.y.automax() ? a.max : pic.userMax().y;
+
+ real denom=bmax-bmin;
+ if(denom != 0) {
+ pic.erase();
+ real m=(primary.userMax().y-primary.userMin().y)/denom;
+ pic.scale.y.postscale=Linear(m,bmin-primary.userMin().y/m);
+ pic.scale.set=true;
+ pic.scale.x.tickMin=primary.userMin().x;
+ pic.scale.x.tickMax=primary.userMax().x;
+ pic.scale.y.tickMin=pic.scale.y.postscale.T(a.min);
+ pic.scale.y.tickMax=pic.scale.y.postscale.T(a.max);
+ axis.ydivisor=a.divisor;
+ f(pic);
+ }
+ pic.userCopy(primary);
+ return pic;
+}
+
+typedef guide graph(pair f(real), real, real, int);
+typedef guide[] multigraph(pair f(real), real, real, int);
+
+graph graph(interpolate join)
+{
+ return new guide(pair f(real), real a, real b, int n) {
+ real width=b-a;
+ return n == 0 ? join(f(a)) :
+ join(...sequence(new guide(int i) {return f(a+(i/n)*width);},n+1));
+ };
+}
+
+multigraph graph(interpolate join, bool3 cond(real))
+{
+ return new guide[](pair f(real), real a, real b, int n) {
+ real width=b-a;
+ if(n == 0) return new guide[] {join(cond(a) ? f(a) : nullpath)};
+ guide[] G;
+ guide[] g;
+ for(int i=0; i < n+1; ++i) {
+ real t=a+(i/n)*width;
+ bool3 b=cond(t);
+ if(b)
+ g.push(f(t));
+ else {
+ if(g.length > 0) {
+ G.push(join(...g));
+ g=new guide[] {};
+ }
+ if(b == default)
+ g.push(f(t));
+ }
+ }
+ if(g.length > 0)
+ G.push(join(...g));
+ return G;
+ };
+}
+
+guide Straight(... guide[])=operator --;
+guide Spline(... guide[])=operator ..;
+
+interpolate Hermite(splinetype splinetype)
+{
+ return new guide(... guide[] a) {
+ int n=a.length;
+ if(n == 0) return nullpath;
+ real[] x,y;
+ guide G;
+ for(int i=0; i < n; ++i) {
+ guide g=a[i];
+ int m=size(g);
+ if(m == 0) continue;
+ pair z=point(g,0);
+ x.push(z.x);
+ y.push(z.y);
+ if(m > 1) {
+ G=G..hermite(x,y,splinetype) & g;
+ pair z=point(g,m);
+ x=new real[] {z.x};
+ y=new real[] {z.y};
+ }
+ }
+ return G & hermite(x,y,splinetype);
+ };
+}
+
+interpolate Hermite=Hermite(Spline);
+
+guide graph(picture pic=currentpicture, real f(real), real a, real b,
+ int n=ngraph, real T(real)=identity, interpolate join=operator --)
+{
+ if(T == identity)
+ return graph(join)(new pair(real x) {
+ return (x,pic.scale.y.T(f(pic.scale.x.Tinv(x))));},
+ pic.scale.x.T(a),pic.scale.x.T(b),n);
+ else
+ return graph(join)(new pair(real x) {
+ return Scale(pic,(T(x),f(T(x))));},
+ a,b,n);
+}
+
+guide[] graph(picture pic=currentpicture, real f(real), real a, real b,
+ int n=ngraph, real T(real)=identity,
+ bool3 cond(real), interpolate join=operator --)
+{
+ if(T == identity)
+ return graph(join,cond)(new pair(real x) {
+ return (x,pic.scale.y.T(f(pic.scale.x.Tinv(x))));},
+ pic.scale.x.T(a),pic.scale.x.T(b),n);
+ else
+ return graph(join,cond)(new pair(real x) {
+ return Scale(pic,(T(x),f(T(x))));},
+ a,b,n);
+}
+
+guide graph(picture pic=currentpicture, real x(real), real y(real), real a,
+ real b, int n=ngraph, real T(real)=identity,
+ interpolate join=operator --)
+{
+ if(T == identity)
+ return graph(join)(new pair(real t) {return Scale(pic,(x(t),y(t)));},a,b,n);
+ else
+ return graph(join)(new pair(real t) {
+ return Scale(pic,(x(T(t)),y(T(t))));
+ },a,b,n);
+}
+
+guide[] graph(picture pic=currentpicture, real x(real), real y(real), real a,
+ real b, int n=ngraph, real T(real)=identity, bool3 cond(real),
+ interpolate join=operator --)
+{
+ if(T == identity)
+ return graph(join,cond)(new pair(real t) {return Scale(pic,(x(t),y(t)));},
+ a,b,n);
+ else
+ return graph(join,cond)(new pair(real t) {
+ return Scale(pic,(x(T(t)),y(T(t))));},
+ a,b,n);
+}
+
+guide graph(picture pic=currentpicture, pair z(real), real a, real b,
+ int n=ngraph, real T(real)=identity, interpolate join=operator --)
+{
+ if(T == identity)
+ return graph(join)(new pair(real t) {return Scale(pic,z(t));},a,b,n);
+ else
+ return graph(join)(new pair(real t) {
+ return Scale(pic,z(T(t)));
+ },a,b,n);
+}
+
+guide[] graph(picture pic=currentpicture, pair z(real), real a, real b,
+ int n=ngraph, real T(real)=identity, bool3 cond(real),
+ interpolate join=operator --)
+{
+ if(T == identity)
+ return graph(join,cond)(new pair(real t) {return Scale(pic,z(t));},a,b,n);
+ else
+ return graph(join,cond)(new pair(real t) {
+ return Scale(pic,z(T(t)));
+ },a,b,n);
+}
+
+string conditionlength="condition array has different length than data";
+
+void checkconditionlength(int x, int y)
+{
+ checklengths(x,y,conditionlength);
+}
+
+guide graph(picture pic=currentpicture, pair[] z, interpolate join=operator --)
+{
+ int i=0;
+ return graph(join)(new pair(real) {
+ pair w=Scale(pic,z[i]);
+ ++i;
+ return w;
+ },0,0,z.length-1);
+}
+
+guide[] graph(picture pic=currentpicture, pair[] z, bool3[] cond,
+ interpolate join=operator --)
+{
+ int n=z.length;
+ int i=0;
+ pair w;
+ checkconditionlength(cond.length,n);
+ bool3 condition(real) {
+ bool3 b=cond[i];
+ if(b != false) w=Scale(pic,z[i]);
+ ++i;
+ return b;
+ }
+ return graph(join,condition)(new pair(real) {return w;},0,0,n-1);
+}
+
+guide graph(picture pic=currentpicture, real[] x, real[] y,
+ interpolate join=operator --)
+{
+ int n=x.length;
+ checklengths(n,y.length);
+ int i=0;
+ return graph(join)(new pair(real) {
+ pair w=Scale(pic,(x[i],y[i]));
+ ++i;
+ return w;
+ },0,0,n-1);
+}
+
+guide[] graph(picture pic=currentpicture, real[] x, real[] y, bool3[] cond,
+ interpolate join=operator --)
+{
+ int n=x.length;
+ checklengths(n,y.length);
+ int i=0;
+ pair w;
+ checkconditionlength(cond.length,n);
+ bool3 condition(real) {
+ bool3 b=cond[i];
+ if(b != false) w=Scale(pic,(x[i],y[i]));
+ ++i;
+ return b;
+ }
+ return graph(join,condition)(new pair(real) {return w;},0,0,n-1);
+}
+
+// Connect points in z into segments corresponding to consecutive true elements
+// of b using interpolation operator join.
+path[] segment(pair[] z, bool[] cond, interpolate join=operator --)
+{
+ checkconditionlength(cond.length,z.length);
+ int[][] segment=segment(cond);
+ return sequence(new path(int i) {return join(...z[segment[i]]);},
+ segment.length);
+}
+
+pair polar(real r, real theta)
+{
+ return r*expi(theta);
+}
+
+guide polargraph(picture pic=currentpicture, real r(real), real a, real b,
+ int n=ngraph, interpolate join=operator --)
+{
+ return graph(join)(new pair(real theta) {
+ return Scale(pic,polar(r(theta),theta));
+ },a,b,n);
+}
+
+guide polargraph(picture pic=currentpicture, real[] r, real[] theta,
+ interpolate join=operator--)
+{
+ int n=r.length;
+ checklengths(n,theta.length);
+ int i=0;
+ return graph(join)(new pair(real) {
+ pair w=Scale(pic,polar(r[i],theta[i]));
+ ++i;
+ return w;
+ },0,0,n-1);
+}
+
+void errorbar(picture pic, pair z, pair dp, pair dm, pen p=currentpen,
+ real size=0)
+{
+ real dmx=-abs(dm.x);
+ real dmy=-abs(dm.y);
+ real dpx=abs(dp.x);
+ real dpy=abs(dp.y);
+ if(dmx != dpx) draw(pic,Scale(pic,z+(dmx,0))--Scale(pic,z+(dpx,0)),p,
+ Bars(size));
+ if(dmy != dpy) draw(pic,Scale(pic,z+(0,dmy))--Scale(pic,z+(0,dpy)),p,
+ Bars(size));
+}
+
+void errorbars(picture pic=currentpicture, pair[] z, pair[] dp, pair[] dm={},
+ bool[] cond={}, pen p=currentpen, real size=0)
+{
+ if(dm.length == 0) dm=dp;
+ int n=z.length;
+ checklengths(n,dm.length);
+ checklengths(n,dp.length);
+ bool all=cond.length == 0;
+ if(!all)
+ checkconditionlength(cond.length,n);
+ for(int i=0; i < n; ++i) {
+ if(all || cond[i])
+ errorbar(pic,z[i],dp[i],dm[i],p,size);
+ }
+}
+
+void errorbars(picture pic=currentpicture, real[] x, real[] y,
+ real[] dpx, real[] dpy, real[] dmx={}, real[] dmy={},
+ bool[] cond={}, pen p=currentpen, real size=0)
+{
+ if(dmx.length == 0) dmx=dpx;
+ if(dmy.length == 0) dmy=dpy;
+ int n=x.length;
+ checklengths(n,y.length);
+ checklengths(n,dpx.length);
+ checklengths(n,dpy.length);
+ checklengths(n,dmx.length);
+ checklengths(n,dmy.length);
+ bool all=cond.length == 0;
+ if(!all)
+ checkconditionlength(cond.length,n);
+ for(int i=0; i < n; ++i) {
+ if(all || cond[i])
+ errorbar(pic,(x[i],y[i]),(dpx[i],dpy[i]),(dmx[i],dmy[i]),p,size);
+ }
+}
+
+void errorbars(picture pic=currentpicture, real[] x, real[] y,
+ real[] dpy, bool[] cond={}, pen p=currentpen, real size=0)
+{
+ errorbars(pic,x,y,0*x,dpy,cond,p,size);
+}
+
+// Return a vector field on path g, specifying the vector as a function of the
+// relative position along path g in [0,1].
+picture vectorfield(path vector(real), path g, int n, bool truesize=false,
+ pen p=currentpen, arrowbar arrow=Arrow,
+ margin margin=PenMargin)
+{
+ picture pic;
+ for(int i=0; i < n; ++i) {
+ real x=(n == 1) ? 0.5 : i/(n-1);
+ if(truesize)
+ draw(relpoint(g,x),pic,vector(x),p,arrow);
+ else
+ draw(pic,shift(relpoint(g,x))*vector(x),p,arrow,margin);
+ }
+ return pic;
+}
+
+real maxlength(pair a, pair b, int nx, int ny)
+{
+ return min((b.x-a.x)/nx,(b.y-a.y)/ny);
+}
+
+// return a vector field over box(a,b).
+picture vectorfield(path vector(pair), pair a, pair b,
+ int nx=nmesh, int ny=nx, bool truesize=false,
+ real maxlength=truesize ? 0 : maxlength(a,b,nx,ny),
+ bool cond(pair z)=null, pen p=currentpen,
+ arrowbar arrow=Arrow, margin margin=PenMargin)
+{
+ picture pic;
+ real dx=1/nx;
+ real dy=1/ny;
+ bool all=cond == null;
+ real scale;
+
+ if(maxlength > 0) {
+ real size(pair z) {
+ path g=vector(z);
+ return abs(point(g,size(g)-1)-point(g,0));
+ }
+ real max=size(a);
+ for(int i=0; i <= nx; ++i) {
+ real x=interp(a.x,b.x,i*dx);
+ for(int j=0; j <= ny; ++j)
+ max=max(max,size((x,interp(a.y,b.y,j*dy))));
+ }
+ scale=max > 0 ? maxlength/max : 1;
+ } else scale=1;
+
+ for(int i=0; i <= nx; ++i) {
+ real x=interp(a.x,b.x,i*dx);
+ for(int j=0; j <= ny; ++j) {
+ real y=interp(a.y,b.y,j*dy);
+ pair z=(x,y);
+ if(all || cond(z)) {
+ path g=scale(scale)*vector(z);
+ if(truesize)
+ draw(z,pic,g,p,arrow);
+ else
+ draw(pic,shift(z)*g,p,arrow,margin);
+ }
+ }
+ }
+ return pic;
+}
+
+// True arc
+path Arc(pair c, real r, real angle1, real angle2, bool direction,
+ int n=nCircle)
+{
+ angle1=radians(angle1);
+ angle2=radians(angle2);
+ if(direction) {
+ if(angle1 >= angle2) angle1 -= 2pi;
+ } else if(angle2 >= angle1) angle2 -= 2pi;
+ return shift(c)*polargraph(new real(real t){return r;},angle1,angle2,n,
+ operator ..);
+}
+
+path Arc(pair c, real r, real angle1, real angle2, int n=nCircle)
+{
+ return Arc(c,r,angle1,angle2,angle2 >= angle1 ? CCW : CW,n);
+}
+
+path Arc(pair c, explicit pair z1, explicit pair z2, bool direction=CCW,
+ int n=nCircle)
+{
+ return Arc(c,abs(z1-c),degrees(z1-c),degrees(z2-c),direction,n);
+}
+
+// True circle
+path Circle(pair c, real r, int n=nCircle)
+{
+ return Arc(c,r,0,360,n)&cycle;
+}
diff --git a/graphics/asymptote/base/graph3.asy b/graphics/asymptote/base/graph3.asy
new file mode 100644
index 0000000000..ed09830974
--- /dev/null
+++ b/graphics/asymptote/base/graph3.asy
@@ -0,0 +1,2044 @@
+// Three-dimensional graphing routines
+
+private import math;
+import graph;
+import three;
+
+typedef triple direction3(real);
+direction3 Dir(triple dir) {return new triple(real) {return dir;};}
+
+ticklocate ticklocate(real a, real b, autoscaleT S=defaultS,
+ real tickmin=-infinity, real tickmax=infinity,
+ real time(real)=null, direction3 dir)
+{
+ if((valuetime) time == null) time=linear(S.T(),a,b);
+ ticklocate locate;
+ locate.a=a;
+ locate.b=b;
+ locate.S=S.copy();
+ if(finite(tickmin)) locate.S.tickMin=tickmin;
+ if(finite(tickmax)) locate.S.tickMax=tickmax;
+ locate.time=time;
+ locate.dir=zero;
+ locate.dir3=dir;
+ return locate;
+}
+
+private struct locateT {
+ real t; // tick location time
+ triple V; // tick location in frame coordinates
+ triple pathdir; // path direction in frame coordinates
+ triple dir; // tick direction in frame coordinates
+
+ void dir(transform3 T, path3 g, ticklocate locate, real t) {
+ pathdir=unit(shiftless(T)*dir(g,t));
+ triple Dir=locate.dir3(t);
+ dir=unit(Dir);
+ }
+ // Locate the desired position of a tick along a path.
+ void calc(transform3 T, path3 g, ticklocate locate, real val) {
+ t=locate.time(val);
+ V=T*point(g,t);
+ dir(T,g,locate,t);
+ }
+}
+
+void drawtick(picture pic, transform3 T, path3 g, path3 g2,
+ ticklocate locate, real val, real Size, int sign, pen p,
+ bool extend)
+{
+ locateT locate1,locate2;
+ locate1.calc(T,g,locate,val);
+ path3 G;
+ if(extend && size(g2) > 0) {
+ locate2.calc(T,g2,locate,val);
+ G=locate1.V--locate2.V;
+ } else
+ G=(sign == 0) ?
+ locate1.V-Size*locate1.dir--locate1.V+Size*locate1.dir :
+ locate1.V--locate1.V+Size*sign*locate1.dir;
+ draw(pic,G,p,name="tick");
+}
+
+triple ticklabelshift(triple align, pen p=currentpen)
+{
+ return 0.25*unit(align)*labelmargin(p);
+}
+
+// Signature of routines that draw labelled paths with ticks and tick labels.
+typedef void ticks3(picture, transform3, Label, path3, path3, pen,
+ arrowbar3, margin3, ticklocate, int[], bool opposite=false,
+ bool primary=true);
+
+// Label a tick on a frame.
+void labeltick(picture pic, transform3 T, path3 g,
+ ticklocate locate, real val, int sign, real Size,
+ ticklabel ticklabel, Label F, real norm=0)
+{
+ locateT locate1;
+ locate1.calc(T,g,locate,val);
+ triple align=F.align.dir3;
+ if(align == O) align=sign*locate1.dir;
+
+ triple shift=align*labelmargin(F.p);
+ if(dot(align,sign*locate1.dir) >= 0)
+ shift=sign*(Size)*locate1.dir;
+
+ real label;
+ if(locate.S.scale.logarithmic)
+ label=locate.S.scale.Tinv(val);
+ else {
+ label=val;
+ if(abs(label) < zerotickfuzz*norm) label=0;
+ // Fix epsilon errors at +/-1e-4
+ // default format changes to scientific notation here
+ if(abs(abs(label)-1e-4) < epsilon) label=sgn(label)*1e-4;
+ }
+
+ string s=ticklabel(label);
+ triple v=locate1.V+shift;
+ if(s != "")
+ label(pic,F.defaulttransform3 ? baseline(s,baselinetemplate) : F.T3*s,v,
+ align,F.p);
+}
+
+// Add axis label L to frame f.
+void labelaxis(picture pic, transform3 T, Label L, path3 g,
+ ticklocate locate=null, int sign=1, bool ticklabels=false)
+{
+ triple m=pic.min(identity4);
+ triple M=pic.max(identity4);
+ triple align=L.align.dir3;
+ Label L=L.copy();
+
+ pic.add(new void(frame f, transform3 T, picture pic2, projection P) {
+ path3 g=T*g;
+ real t=relative(L,g);
+ triple v=point(g,t);
+ picture F;
+ if(L.align.dir3 == O)
+ align=unit(invert(L.align.dir,v,P))*abs(L.align.dir);
+
+ if(ticklabels && locate != null && piecewisestraight(g)) {
+ locateT locate1;
+ locate1.dir(T,g,locate,t);
+ triple pathdir=locate1.pathdir;
+
+ triple perp=cross(pathdir,P.normal);
+ if(align == O)
+ align=unit(sgn(dot(sign*locate1.dir,perp))*perp);
+ path[] g=project(box(T*m,T*M),P);
+ pair z=project(v,P);
+ pair Ppathdir=project(v+pathdir,P)-z;
+ pair Perp=unit(I*Ppathdir);
+ real angle=degrees(Ppathdir,warn=false);
+ transform S=rotate(-angle,z);
+ path[] G=S*g;
+ pair Palign=project(v+align,P)-z;
+ pair Align=rotate(-angle)*dot(Palign,Perp)*Perp;
+ pair offset=unit(Palign)*
+ abs((Align.y >= 0 ? max(G).y : (Align.y < 0 ? min(G).y : 0))-z.y);
+ triple normal=cross(pathdir,align);
+ if(normal != O) v=invert(z+offset,normal,v,P);
+ }
+
+ label(F,L,v);
+ add(f,F.fit3(identity4,pic2,P));
+ },exact=false);
+
+ path3[] G=path3(texpath(L,bbox=true));
+ if(G.length > 0) {
+ G=L.align.is3D ? align(G,O,align,L.p) : L.T3*G;
+ triple v=point(g,relative(L,g));
+ pic.addBox(v,v,min(G),max(G));
+ }
+}
+
+// Tick construction routine for a user-specified array of tick values.
+ticks3 Ticks3(int sign, Label F="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ real[] Ticks=new real[], real[] ticks=new real[], int N=1,
+ bool begin=true, bool end=true,
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return new void(picture pic, transform3 t, Label L, path3 g, path3 g2, pen p,
+ arrowbar3 arrow, margin3 margin, ticklocate locate,
+ int[] divisor, bool opposite, bool primary) {
+ // Use local copy of context variables:
+ int Sign=opposite ? -1 : 1;
+ int sign=Sign*sign;
+ pen pTick=pTick;
+ pen ptick=ptick;
+ ticklabel ticklabel=ticklabel;
+
+ real Size=Size;
+ real size=size;
+ if(Size == 0) Size=Ticksize;
+ if(size == 0) size=ticksize;
+
+ Label L=L.copy();
+ Label F=F.copy();
+ L.p(p);
+ F.p(p);
+ if(pTick == nullpen) pTick=p;
+ if(ptick == nullpen) ptick=pTick;
+
+ bool ticklabels=false;
+ path3 G=t*g;
+ path3 G2=t*g2;
+
+ scalefcn T;
+
+ real a,b;
+ if(locate.S.scale.logarithmic) {
+ a=locate.S.postscale.Tinv(locate.a);
+ b=locate.S.postscale.Tinv(locate.b);
+ T=locate.S.scale.T;
+ } else {
+ a=locate.S.Tinv(locate.a);
+ b=locate.S.Tinv(locate.b);
+ T=identity;
+ }
+
+ if(a > b) {real temp=a; a=b; b=temp;}
+
+ real norm=max(abs(a),abs(b));
+
+ string format=autoformat(F.s,norm...Ticks);
+ if(F.s == "%") F.s="";
+ if(ticklabel == null) {
+ if(locate.S.scale.logarithmic) {
+ int base=round(locate.S.scale.Tinv(1));
+ ticklabel=format == "%" ? Format("") : DefaultLogFormat(base);
+ } else ticklabel=Format(format);
+ }
+
+ bool labelaxis=L.s != "" && primary;
+
+ begingroup3(pic,"axis");
+
+ if(primary) draw(pic,margin(G,p).g,p,arrow);
+ else draw(pic,G,p);
+
+ for(int i=(begin ? 0 : 1); i < (end ? Ticks.length : Ticks.length-1); ++i) {
+ real val=T(Ticks[i]);
+ if(val >= a && val <= b)
+ drawtick(pic,t,g,g2,locate,val,Size,sign,pTick,extend);
+ }
+ for(int i=0; i < ticks.length; ++i) {
+ real val=T(ticks[i]);
+ if(val >= a && val <= b)
+ drawtick(pic,t,g,g2,locate,val,size,sign,ptick,extend);
+ }
+
+ if(N == 0) N=1;
+ if(Size > 0 && primary) {
+ for(int i=(beginlabel ? 0 : 1);
+ i < (endlabel ? Ticks.length : Ticks.length-1); i += N) {
+ real val=T(Ticks[i]);
+ if(val >= a && val <= b) {
+ ticklabels=true;
+ labeltick(pic,t,g,locate,val,Sign,Size,ticklabel,F,norm);
+ }
+ }
+ }
+ if(labelaxis)
+ labelaxis(pic,t,L,G,locate,Sign,ticklabels);
+
+ endgroup3(pic);
+ };
+}
+
+// Automatic tick construction routine.
+ticks3 Ticks3(int sign, Label F="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true, tickmodifier modify=None,
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return new void(picture pic, transform3 T, Label L,
+ path3 g, path3 g2, pen p,
+ arrowbar3 arrow, margin3 margin=NoMargin3, ticklocate locate,
+ int[] divisor, bool opposite, bool primary) {
+ path3 G=T*g;
+ real limit=Step == 0 ? axiscoverage*arclength(G) : 0;
+ tickvalues values=modify(generateticks(sign,F,ticklabel,N,n,Step,step,
+ Size,size,identity(),1,
+ project(G,currentprojection),
+ limit,p,locate,divisor,
+ opposite));
+ Ticks3(sign,F,ticklabel,beginlabel,endlabel,values.major,values.minor,
+ values.N,begin,end,Size,size,extend,pTick,ptick)
+ (pic,T,L,g,g2,p,arrow,margin,locate,divisor,opposite,primary);
+ };
+}
+
+ticks3 NoTicks3()
+{
+ return new void(picture pic, transform3 T, Label L, path3 g,
+ path3, pen p, arrowbar3 arrow, margin3 margin,
+ ticklocate, int[], bool opposite, bool primary) {
+ path3 G=T*g;
+ if(primary) draw(pic,margin(G,p).g,p,arrow,margin);
+ else draw(pic,G,p);
+ if(L.s != "" && primary) {
+ Label L=L.copy();
+ L.p(p);
+ labelaxis(pic,T,L,G,opposite ? -1 : 1);
+ }
+ };
+}
+
+ticks3 InTicks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N=0, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true, tickmodifier modify=None,
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks3(-1,format,ticklabel,beginlabel,endlabel,N,n,Step,step,
+ begin,end,modify,Size,size,extend,pTick,ptick);
+}
+
+ticks3 OutTicks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N=0, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true, tickmodifier modify=None,
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks3(1,format,ticklabel,beginlabel,endlabel,N,n,Step,step,
+ begin,end,modify,Size,size,extend,pTick,ptick);
+}
+
+ticks3 InOutTicks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N=0, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true, tickmodifier modify=None,
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks3(0,format,ticklabel,beginlabel,endlabel,N,n,Step,step,
+ begin,end,modify,Size,size,extend,pTick,ptick);
+}
+
+ticks3 InTicks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ real[] Ticks, real[] ticks=new real[],
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks3(-1,format,ticklabel,beginlabel,endlabel,
+ Ticks,ticks,Size,size,extend,pTick,ptick);
+}
+
+ticks3 OutTicks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ real[] Ticks, real[] ticks=new real[],
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks3(1,format,ticklabel,beginlabel,endlabel,
+ Ticks,ticks,Size,size,extend,pTick,ptick);
+}
+
+ticks3 InOutTicks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ real[] Ticks, real[] ticks=new real[],
+ real Size=0, real size=0, bool extend=false,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return Ticks3(0,format,ticklabel,beginlabel,endlabel,
+ Ticks,ticks,Size,size,extend,pTick,ptick);
+}
+
+ticks3 NoTicks3=NoTicks3(),
+InTicks=InTicks(),
+OutTicks=OutTicks(),
+InOutTicks=InOutTicks();
+
+triple tickMin3(picture pic)
+{
+ return minbound(pic.userMin(),(pic.scale.x.tickMin,pic.scale.y.tickMin,
+ pic.scale.z.tickMin));
+}
+
+triple tickMax3(picture pic)
+{
+ return maxbound(pic.userMax(),(pic.scale.x.tickMax,pic.scale.y.tickMax,
+ pic.scale.z.tickMax));
+}
+
+axis Bounds(int type=Both, int type2=Both, triple align=O, bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=type;
+ axis.type2=type2;
+ axis.position=0.5;
+ axis.align=align;
+ axis.extend=extend;
+ };
+}
+
+axis YZEquals(real y, real z, triple align=O, bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Value;
+ axis.type2=Value;
+ axis.value=pic.scale.y.T(y);
+ axis.value2=pic.scale.z.T(z);
+ axis.position=1;
+ axis.align=align;
+ axis.extend=extend;
+ };
+}
+
+axis XZEquals(real x, real z, triple align=O, bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Value;
+ axis.type2=Value;
+ axis.value=pic.scale.x.T(x);
+ axis.value2=pic.scale.z.T(z);
+ axis.position=1;
+ axis.align=align;
+ axis.extend=extend;
+ };
+}
+
+axis XYEquals(real x, real y, triple align=O, bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Value;
+ axis.type2=Value;
+ axis.value=pic.scale.x.T(x);
+ axis.value2=pic.scale.y.T(y);
+ axis.position=1;
+ axis.align=align;
+ axis.extend=extend;
+ };
+}
+
+axis YZZero(triple align=O, bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Value;
+ axis.type2=Value;
+ axis.value=pic.scale.y.T(pic.scale.y.scale.logarithmic ? 1 : 0);
+ axis.value2=pic.scale.z.T(pic.scale.z.scale.logarithmic ? 1 : 0);
+ axis.position=1;
+ axis.align=align;
+ axis.extend=extend;
+ };
+}
+
+axis XZZero(triple align=O, bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Value;
+ axis.type2=Value;
+ axis.value=pic.scale.x.T(pic.scale.x.scale.logarithmic ? 1 : 0);
+ axis.value2=pic.scale.z.T(pic.scale.z.scale.logarithmic ? 1 : 0);
+ axis.position=1;
+ axis.align=align;
+ axis.extend=extend;
+ };
+}
+
+axis XYZero(triple align=O, bool extend=false)
+{
+ return new void(picture pic, axisT axis) {
+ axis.type=Value;
+ axis.type2=Value;
+ axis.value=pic.scale.x.T(pic.scale.x.scale.logarithmic ? 1 : 0);
+ axis.value2=pic.scale.y.T(pic.scale.y.scale.logarithmic ? 1 : 0);
+ axis.position=1;
+ axis.align=align;
+ axis.extend=extend;
+ };
+}
+
+axis
+Bounds=Bounds(),
+YZZero=YZZero(),
+XZZero=XZZero(),
+XYZero=XYZero();
+
+// Draw a general three-dimensional axis.
+void axis(picture pic=currentpicture, Label L="", path3 g, path3 g2=nullpath3,
+ pen p=currentpen, ticks3 ticks, ticklocate locate,
+ arrowbar3 arrow=None, margin3 margin=NoMargin3,
+ int[] divisor=new int[], bool above=false, bool opposite=false)
+{
+ Label L=L.copy();
+ real t=reltime(g,0.5);
+ if(L.defaultposition) L.position(t);
+ divisor=copy(divisor);
+ locate=locate.copy();
+
+ pic.add(new void (picture f, transform3 t, transform3 T, triple, triple) {
+ picture d;
+ ticks(d,t,L,g,g2,p,arrow,margin,locate,divisor,opposite,true);
+ add(f,t*T*inverse(t)*d);
+ },above=above);
+
+ addPath(pic,g,p);
+
+ if(L.s != "") {
+ frame f;
+ Label L0=L.copy();
+ L0.position(0);
+ add(f,L0);
+ triple pos=point(g,L.relative()*length(g));
+ pic.addBox(pos,pos,min3(f),max3(f));
+ }
+}
+
+real xtrans(transform3 t, real x)
+{
+ return (t*(x,0,0)).x;
+}
+
+real ytrans(transform3 t, real y)
+{
+ return (t*(0,y,0)).y;
+}
+
+real ztrans(transform3 t, real z)
+{
+ return (t*(0,0,z)).z;
+}
+
+private triple defaultdir(triple X, triple Y, triple Z, bool opposite=false,
+ projection P) {
+ triple u=cross(P.normal,Z);
+ return abs(dot(u,X)) > abs(dot(u,Y)) ? -X : (opposite ? Y : -Y);
+}
+
+// An internal routine to draw an x axis at a particular y value.
+void xaxis3At(picture pic=currentpicture, Label L="", axis axis,
+ real xmin=-infinity, real xmax=infinity, pen p=currentpen,
+ ticks3 ticks=NoTicks3,
+ arrowbar3 arrow=None, margin3 margin=NoMargin3, bool above=true,
+ bool opposite=false, bool opposite2=false, bool primary=true)
+{
+ int type=axis.type;
+ int type2=axis.type2;
+ triple dir=axis.align.dir3 == O ?
+ defaultdir(Y,Z,X,opposite^opposite2,currentprojection) : axis.align.dir3;
+ Label L=L.copy();
+ if(L.align.dir3 == O && L.align.dir == 0) L.align(opposite ? -dir : dir);
+
+ real y=axis.value;
+ real z=axis.value2;
+ real y2,z2;
+ int[] divisor=copy(axis.xdivisor);
+
+ pic.add(new void(picture f, transform3 t, transform3 T, triple lb,
+ triple rt) {
+ transform3 tinv=inverse(t);
+ triple a=xmin == -infinity ? tinv*(lb.x-min3(p).x,ytrans(t,y),
+ ztrans(t,z)) : (xmin,y,z);
+ triple b=xmax == infinity ? tinv*(rt.x-max3(p).x,ytrans(t,y),
+ ztrans(t,z)) : (xmax,y,z);
+ real y0;
+ real z0;
+ if(abs(dir.y) < abs(dir.z)) {
+ y0=y;
+ z0=z2;
+ } else {
+ y0=y2;
+ z0=z;
+ }
+
+ triple a2=xmin == -infinity ? tinv*(lb.x-min3(p).x,ytrans(t,y0),
+ ztrans(t,z0)) : (xmin,y0,z0);
+ triple b2=xmax == infinity ? tinv*(rt.x-max3(p).x,ytrans(t,y0),
+ ztrans(t,z0)) : (xmax,y0,z0);
+
+ if(xmin == -infinity || xmax == infinity) {
+ bounds mx=autoscale(a.x,b.x,pic.scale.x.scale);
+ pic.scale.x.tickMin=mx.min;
+ pic.scale.x.tickMax=mx.max;
+ divisor=mx.divisor;
+ }
+
+ triple fuzz=X*epsilon*max(abs(a.x),abs(b.x));
+ a -= fuzz;
+ b += fuzz;
+
+ picture d;
+ ticks(d,t,L,a--b,finite(y0) && finite(z0) ? a2--b2 : nullpath3,
+ p,arrow,margin,
+ ticklocate(a.x,b.x,pic.scale.x,Dir(dir)),divisor,
+ opposite,primary);
+ add(f,t*T*tinv*d);
+ },above=above);
+
+ void bounds() {
+ if(type == Min)
+ y=pic.scale.y.automin() ? tickMin3(pic).y : pic.userMin().y;
+ else if(type == Max)
+ y=pic.scale.y.automax() ? tickMax3(pic).y : pic.userMax().y;
+ else if(type == Both) {
+ y2=pic.scale.y.automax() ? tickMax3(pic).y : pic.userMax().y;
+ y=opposite ? y2 :
+ (pic.scale.y.automin() ? tickMin3(pic).y : pic.userMin().y);
+ }
+
+ if(type2 == Min)
+ z=pic.scale.z.automin() ? tickMin3(pic).z : pic.userMin().z;
+ else if(type2 == Max)
+ z=pic.scale.z.automax() ? tickMax3(pic).z : pic.userMax().z;
+ else if(type2 == Both) {
+ z2=pic.scale.z.automax() ? tickMax3(pic).z : pic.userMax().z;
+ z=opposite2 ? z2 :
+ (pic.scale.z.automin() ? tickMin3(pic).z : pic.userMin().z);
+ }
+
+ real Xmin=finite(xmin) ? xmin : pic.userMin().x;
+ real Xmax=finite(xmax) ? xmax : pic.userMax().x;
+
+ triple a=(Xmin,y,z);
+ triple b=(Xmax,y,z);
+ triple a2=(Xmin,y2,z2);
+ triple b2=(Xmax,y2,z2);
+
+ if(finite(a)) {
+ pic.addPoint(a,min3(p));
+ pic.addPoint(a,max3(p));
+ }
+
+ if(finite(b)) {
+ pic.addPoint(b,min3(p));
+ pic.addPoint(b,max3(p));
+ }
+
+ if(finite(a) && finite(b)) {
+ picture d;
+ ticks(d,pic.scaling3(warn=false),L,
+ (a.x,0,0)--(b.x,0,0),(a2.x,0,0)--(b2.x,0,0),p,arrow,margin,
+ ticklocate(a.x,b.x,pic.scale.x,Dir(dir)),divisor,
+ opposite,primary);
+ frame f;
+ if(L.s != "") {
+ Label L0=L.copy();
+ L0.position(0);
+ add(f,L0);
+ }
+ triple pos=a+L.relative()*(b-a);
+ triple m=min3(d);
+ triple M=max3(d);
+ pic.addBox(pos,pos,(min3(f).x,m.y,m.z),(max3(f).x,m.y,m.z));
+ }
+ }
+
+ // Process any queued y and z axes bound calculation requests.
+ for(int i=0; i < pic.scale.y.bound.length; ++i)
+ pic.scale.y.bound[i]();
+ for(int i=0; i < pic.scale.z.bound.length; ++i)
+ pic.scale.z.bound[i]();
+
+ pic.scale.y.bound.delete();
+ pic.scale.z.bound.delete();
+
+ bounds();
+
+ // Request another x bounds calculation before final picture scaling.
+ pic.scale.x.bound.push(bounds);
+}
+
+// An internal routine to draw an x axis at a particular y value.
+void yaxis3At(picture pic=currentpicture, Label L="", axis axis,
+ real ymin=-infinity, real ymax=infinity, pen p=currentpen,
+ ticks3 ticks=NoTicks3,
+ arrowbar3 arrow=None, margin3 margin=NoMargin3, bool above=true,
+ bool opposite=false, bool opposite2=false, bool primary=true)
+{
+ int type=axis.type;
+ int type2=axis.type2;
+ triple dir=axis.align.dir3 == O ?
+ defaultdir(X,Z,Y,opposite^opposite2,currentprojection) : axis.align.dir3;
+ Label L=L.copy();
+ if(L.align.dir3 == O && L.align.dir == 0) L.align(opposite ? -dir : dir);
+
+ real x=axis.value;
+ real z=axis.value2;
+ real x2,z2;
+ int[] divisor=copy(axis.ydivisor);
+
+ pic.add(new void(picture f, transform3 t, transform3 T, triple lb,
+ triple rt) {
+ transform3 tinv=inverse(t);
+ triple a=ymin == -infinity ? tinv*(xtrans(t,x),lb.y-min3(p).y,
+ ztrans(t,z)) : (x,ymin,z);
+ triple b=ymax == infinity ? tinv*(xtrans(t,x),rt.y-max3(p).y,
+ ztrans(t,z)) : (x,ymax,z);
+ real x0;
+ real z0;
+ if(abs(dir.x) < abs(dir.z)) {
+ x0=x;
+ z0=z2;
+ } else {
+ x0=x2;
+ z0=z;
+ }
+
+ triple a2=ymin == -infinity ? tinv*(xtrans(t,x0),lb.y-min3(p).y,
+ ztrans(t,z0)) : (x0,ymin,z0);
+ triple b2=ymax == infinity ? tinv*(xtrans(t,x0),rt.y-max3(p).y,
+ ztrans(t,z0)) : (x0,ymax,z0);
+
+ if(ymin == -infinity || ymax == infinity) {
+ bounds my=autoscale(a.y,b.y,pic.scale.y.scale);
+ pic.scale.y.tickMin=my.min;
+ pic.scale.y.tickMax=my.max;
+ divisor=my.divisor;
+ }
+
+ triple fuzz=Y*epsilon*max(abs(a.y),abs(b.y));
+ a -= fuzz;
+ b += fuzz;
+
+ picture d;
+ ticks(d,t,L,a--b,finite(x0) && finite(z0) ? a2--b2 : nullpath3,
+ p,arrow,margin,
+ ticklocate(a.y,b.y,pic.scale.y,Dir(dir)),divisor,
+ opposite,primary);
+ add(f,t*T*tinv*d);
+ },above=above);
+
+ void bounds() {
+ if(type == Min)
+ x=pic.scale.x.automin() ? tickMin3(pic).x : pic.userMin().x;
+ else if(type == Max)
+ x=pic.scale.x.automax() ? tickMax3(pic).x : pic.userMax().x;
+ else if(type == Both) {
+ x2=pic.scale.x.automax() ? tickMax3(pic).x : pic.userMax().x;
+ x=opposite ? x2 :
+ (pic.scale.x.automin() ? tickMin3(pic).x : pic.userMin().x);
+ }
+
+ if(type2 == Min)
+ z=pic.scale.z.automin() ? tickMin3(pic).z : pic.userMin().z;
+ else if(type2 == Max)
+ z=pic.scale.z.automax() ? tickMax3(pic).z : pic.userMax().z;
+ else if(type2 == Both) {
+ z2=pic.scale.z.automax() ? tickMax3(pic).z : pic.userMax().z;
+ z=opposite2 ? z2 :
+ (pic.scale.z.automin() ? tickMin3(pic).z : pic.userMin().z);
+ }
+
+ real Ymin=finite(ymin) ? ymin : pic.userMin().y;
+ real Ymax=finite(ymax) ? ymax : pic.userMax().y;
+
+ triple a=(x,Ymin,z);
+ triple b=(x,Ymax,z);
+ triple a2=(x2,Ymin,z2);
+ triple b2=(x2,Ymax,z2);
+
+ if(finite(a)) {
+ pic.addPoint(a,min3(p));
+ pic.addPoint(a,max3(p));
+ }
+
+ if(finite(b)) {
+ pic.addPoint(b,min3(p));
+ pic.addPoint(b,max3(p));
+ }
+
+ if(finite(a) && finite(b)) {
+ picture d;
+ ticks(d,pic.scaling3(warn=false),L,
+ (0,a.y,0)--(0,b.y,0),(0,a2.y,0)--(0,a2.y,0),p,arrow,margin,
+ ticklocate(a.y,b.y,pic.scale.y,Dir(dir)),divisor,
+ opposite,primary);
+ frame f;
+ if(L.s != "") {
+ Label L0=L.copy();
+ L0.position(0);
+ add(f,L0);
+ }
+ triple pos=a+L.relative()*(b-a);
+ triple m=min3(d);
+ triple M=max3(d);
+ pic.addBox(pos,pos,(m.x,min3(f).y,m.z),(m.x,max3(f).y,m.z));
+ }
+ }
+
+ // Process any queued x and z axis bound calculation requests.
+ for(int i=0; i < pic.scale.x.bound.length; ++i)
+ pic.scale.x.bound[i]();
+ for(int i=0; i < pic.scale.z.bound.length; ++i)
+ pic.scale.z.bound[i]();
+
+ pic.scale.x.bound.delete();
+ pic.scale.z.bound.delete();
+
+ bounds();
+
+ // Request another y bounds calculation before final picture scaling.
+ pic.scale.y.bound.push(bounds);
+}
+
+// An internal routine to draw an x axis at a particular y value.
+void zaxis3At(picture pic=currentpicture, Label L="", axis axis,
+ real zmin=-infinity, real zmax=infinity, pen p=currentpen,
+ ticks3 ticks=NoTicks3,
+ arrowbar3 arrow=None, margin3 margin=NoMargin3, bool above=true,
+ bool opposite=false, bool opposite2=false, bool primary=true)
+{
+ int type=axis.type;
+ int type2=axis.type2;
+ triple dir=axis.align.dir3 == O ?
+ defaultdir(X,Y,Z,opposite^opposite2,currentprojection) : axis.align.dir3;
+ Label L=L.copy();
+ if(L.align.dir3 == O && L.align.dir == 0) L.align(opposite ? -dir : dir);
+
+ real x=axis.value;
+ real y=axis.value2;
+ real x2,y2;
+ int[] divisor=copy(axis.zdivisor);
+
+ pic.add(new void(picture f, transform3 t, transform3 T, triple lb,
+ triple rt) {
+ transform3 tinv=inverse(t);
+ triple a=zmin == -infinity ? tinv*(xtrans(t,x),ytrans(t,y),
+ lb.z-min3(p).z) : (x,y,zmin);
+ triple b=zmax == infinity ? tinv*(xtrans(t,x),ytrans(t,y),
+ rt.z-max3(p).z) : (x,y,zmax);
+ real x0;
+ real y0;
+ if(abs(dir.x) < abs(dir.y)) {
+ x0=x;
+ y0=y2;
+ } else {
+ x0=x2;
+ y0=y;
+ }
+
+ triple a2=zmin == -infinity ? tinv*(xtrans(t,x0),ytrans(t,y0),
+ lb.z-min3(p).z) : (x0,y0,zmin);
+ triple b2=zmax == infinity ? tinv*(xtrans(t,x0),ytrans(t,y0),
+ rt.z-max3(p).z) : (x0,y0,zmax);
+
+ if(zmin == -infinity || zmax == infinity) {
+ bounds mz=autoscale(a.z,b.z,pic.scale.z.scale);
+ pic.scale.z.tickMin=mz.min;
+ pic.scale.z.tickMax=mz.max;
+ divisor=mz.divisor;
+ }
+
+ triple fuzz=Z*epsilon*max(abs(a.z),abs(b.z));
+ a -= fuzz;
+ b += fuzz;
+
+ picture d;
+ ticks(d,t,L,a--b,finite(x0) && finite(y0) ? a2--b2 : nullpath3,
+ p,arrow,margin,
+ ticklocate(a.z,b.z,pic.scale.z,Dir(dir)),divisor,
+ opposite,primary);
+ add(f,t*T*tinv*d);
+ },above=above);
+
+ void bounds() {
+ if(type == Min)
+ x=pic.scale.x.automin() ? tickMin3(pic).x : pic.userMin().x;
+ else if(type == Max)
+ x=pic.scale.x.automax() ? tickMax3(pic).x : pic.userMax().x;
+ else if(type == Both) {
+ x2=pic.scale.x.automax() ? tickMax3(pic).x : pic.userMax().x;
+ x=opposite ? x2 :
+ (pic.scale.x.automin() ? tickMin3(pic).x : pic.userMin().x);
+ }
+
+ if(type2 == Min)
+ y=pic.scale.y.automin() ? tickMin3(pic).y : pic.userMin().y;
+ else if(type2 == Max)
+ y=pic.scale.y.automax() ? tickMax3(pic).y : pic.userMax().y;
+ else if(type2 == Both) {
+ y2=pic.scale.y.automax() ? tickMax3(pic).y : pic.userMax().y;
+ y=opposite2 ? y2 :
+ (pic.scale.y.automin() ? tickMin3(pic).y : pic.userMin().y);
+ }
+
+ real Zmin=finite(zmin) ? zmin : pic.userMin().z;
+ real Zmax=finite(zmax) ? zmax : pic.userMax().z;
+
+ triple a=(x,y,Zmin);
+ triple b=(x,y,Zmax);
+ triple a2=(x2,y2,Zmin);
+ triple b2=(x2,y2,Zmax);
+
+ if(finite(a)) {
+ pic.addPoint(a,min3(p));
+ pic.addPoint(a,max3(p));
+ }
+
+ if(finite(b)) {
+ pic.addPoint(b,min3(p));
+ pic.addPoint(b,max3(p));
+ }
+
+ if(finite(a) && finite(b)) {
+ picture d;
+ ticks(d,pic.scaling3(warn=false),L,
+ (0,0,a.z)--(0,0,b.z),(0,0,a2.z)--(0,0,a2.z),p,arrow,margin,
+ ticklocate(a.z,b.z,pic.scale.z,Dir(dir)),divisor,
+ opposite,primary);
+ frame f;
+ if(L.s != "") {
+ Label L0=L.copy();
+ L0.position(0);
+ add(f,L0);
+ }
+ triple pos=a+L.relative()*(b-a);
+ triple m=min3(d);
+ triple M=max3(d);
+ pic.addBox(pos,pos,(m.x,m.y,min3(f).z),(m.x,m.y,max3(f).z));
+ }
+ }
+
+ // Process any queued x and y axes bound calculation requests.
+ for(int i=0; i < pic.scale.x.bound.length; ++i)
+ pic.scale.x.bound[i]();
+ for(int i=0; i < pic.scale.y.bound.length; ++i)
+ pic.scale.y.bound[i]();
+
+ pic.scale.x.bound.delete();
+ pic.scale.y.bound.delete();
+
+ bounds();
+
+ // Request another z bounds calculation before final picture scaling.
+ pic.scale.z.bound.push(bounds);
+}
+
+// Internal routine to autoscale the user limits of a picture.
+void autoscale3(picture pic=currentpicture, axis axis)
+{
+ bool set=pic.scale.set;
+ autoscale(pic,axis);
+
+ if(!set) {
+ bounds mz;
+ if(pic.userSetz()) {
+ mz=autoscale(pic.userMin().z,pic.userMax().z,pic.scale.z.scale);
+ if(pic.scale.z.scale.logarithmic &&
+ floor(pic.userMin().z) == floor(pic.userMax().z)) {
+ if(pic.scale.z.automin())
+ pic.userMinz3(floor(pic.userMin().z));
+ if(pic.scale.z.automax())
+ pic.userMaxz3(ceil(pic.userMax().z));
+ }
+ } else {mz.min=mz.max=0; pic.scale.set=false;}
+
+ pic.scale.z.tickMin=mz.min;
+ pic.scale.z.tickMax=mz.max;
+ axis.zdivisor=mz.divisor;
+ }
+}
+
+// Draw an x axis in three dimensions.
+void xaxis3(picture pic=currentpicture, Label L="", axis axis=YZZero,
+ real xmin=-infinity, real xmax=infinity, pen p=currentpen,
+ ticks3 ticks=NoTicks3,
+ arrowbar3 arrow=None, margin3 margin=NoMargin3, bool above=false)
+{
+ if(xmin > xmax) return;
+
+ if(pic.scale.x.automin && xmin > -infinity) pic.scale.x.automin=false;
+ if(pic.scale.x.automax && xmax < infinity) pic.scale.x.automax=false;
+
+ if(!pic.scale.set) {
+ axis(pic,axis);
+ autoscale3(pic,axis);
+ }
+
+ bool newticks=false;
+
+ if(xmin != -infinity) {
+ xmin=pic.scale.x.T(xmin);
+ newticks=true;
+ }
+
+ if(xmax != infinity) {
+ xmax=pic.scale.x.T(xmax);
+ newticks=true;
+ }
+
+ if(newticks && pic.userSetx() && ticks != NoTicks3) {
+ if(xmin == -infinity) xmin=pic.userMin().x;
+ if(xmax == infinity) xmax=pic.userMax().x;
+ bounds mx=autoscale(xmin,xmax,pic.scale.x.scale);
+ pic.scale.x.tickMin=mx.min;
+ pic.scale.x.tickMax=mx.max;
+ axis.xdivisor=mx.divisor;
+ }
+
+ axis(pic,axis);
+
+ if(xmin == -infinity && !axis.extend) {
+ if(pic.scale.set)
+ xmin=pic.scale.x.automin() ? pic.scale.x.tickMin :
+ max(pic.scale.x.tickMin,pic.userMin().x);
+ else xmin=pic.userMin().x;
+ }
+
+ if(xmax == infinity && !axis.extend) {
+ if(pic.scale.set)
+ xmax=pic.scale.x.automax() ? pic.scale.x.tickMax :
+ min(pic.scale.x.tickMax,pic.userMax().x);
+ else xmax=pic.userMax().x;
+ }
+
+ if(L.defaultposition) {
+ L=L.copy();
+ L.position(axis.position);
+ }
+
+ bool back=false;
+ if(axis.type == Both) {
+ triple v=currentprojection.normal;
+ back=dot((0,pic.userMax().y-pic.userMin().y,0),v)*sgn(v.z) > 0;
+ }
+
+ xaxis3At(pic,L,axis,xmin,xmax,p,ticks,arrow,margin,above,false,false,!back);
+ if(axis.type == Both)
+ xaxis3At(pic,L,axis,xmin,xmax,p,ticks,arrow,margin,above,true,false,back);
+ if(axis.type2 == Both) {
+ xaxis3At(pic,L,axis,xmin,xmax,p,ticks,arrow,margin,above,false,true,false);
+ if(axis.type == Both)
+ xaxis3At(pic,L,axis,xmin,xmax,p,ticks,arrow,margin,above,true,true,false);
+ }
+}
+
+// Draw a y axis in three dimensions.
+void yaxis3(picture pic=currentpicture, Label L="", axis axis=XZZero,
+ real ymin=-infinity, real ymax=infinity, pen p=currentpen,
+ ticks3 ticks=NoTicks3,
+ arrowbar3 arrow=None, margin3 margin=NoMargin3, bool above=false)
+{
+ if(ymin > ymax) return;
+
+ if(pic.scale.y.automin && ymin > -infinity) pic.scale.y.automin=false;
+ if(pic.scale.y.automax && ymax < infinity) pic.scale.y.automax=false;
+
+ if(!pic.scale.set) {
+ axis(pic,axis);
+ autoscale3(pic,axis);
+ }
+
+ bool newticks=false;
+
+ if(ymin != -infinity) {
+ ymin=pic.scale.y.T(ymin);
+ newticks=true;
+ }
+
+ if(ymax != infinity) {
+ ymax=pic.scale.y.T(ymax);
+ newticks=true;
+ }
+
+ if(newticks && pic.userSety() && ticks != NoTicks3) {
+ if(ymin == -infinity) ymin=pic.userMin().y;
+ if(ymax == infinity) ymax=pic.userMax().y;
+ bounds my=autoscale(ymin,ymax,pic.scale.y.scale);
+ pic.scale.y.tickMin=my.min;
+ pic.scale.y.tickMax=my.max;
+ axis.ydivisor=my.divisor;
+ }
+
+ axis(pic,axis);
+
+ if(ymin == -infinity && !axis.extend) {
+ if(pic.scale.set)
+ ymin=pic.scale.y.automin() ? pic.scale.y.tickMin :
+ max(pic.scale.y.tickMin,pic.userMin().y);
+ else ymin=pic.userMin().y;
+ }
+
+
+ if(ymax == infinity && !axis.extend) {
+ if(pic.scale.set)
+ ymax=pic.scale.y.automax() ? pic.scale.y.tickMax :
+ min(pic.scale.y.tickMax,pic.userMax().y);
+ else ymax=pic.userMax().y;
+ }
+
+ if(L.defaultposition) {
+ L=L.copy();
+ L.position(axis.position);
+ }
+
+ bool back=false;
+ if(axis.type == Both) {
+ triple v=currentprojection.normal;
+ back=dot((pic.userMax().x-pic.userMin().x,0,0),v)*sgn(v.z) > 0;
+ }
+
+ yaxis3At(pic,L,axis,ymin,ymax,p,ticks,arrow,margin,above,false,false,!back);
+
+ if(axis.type == Both)
+ yaxis3At(pic,L,axis,ymin,ymax,p,ticks,arrow,margin,above,true,false,back);
+ if(axis.type2 == Both) {
+ yaxis3At(pic,L,axis,ymin,ymax,p,ticks,arrow,margin,above,false,true,false);
+ if(axis.type == Both)
+ yaxis3At(pic,L,axis,ymin,ymax,p,ticks,arrow,margin,above,true,true,false);
+ }
+}
+// Draw a z axis in three dimensions.
+void zaxis3(picture pic=currentpicture, Label L="", axis axis=XYZero,
+ real zmin=-infinity, real zmax=infinity, pen p=currentpen,
+ ticks3 ticks=NoTicks3,
+ arrowbar3 arrow=None, margin3 margin=NoMargin3, bool above=false)
+{
+ if(zmin > zmax) return;
+
+ if(pic.scale.z.automin && zmin > -infinity) pic.scale.z.automin=false;
+ if(pic.scale.z.automax && zmax < infinity) pic.scale.z.automax=false;
+
+ if(!pic.scale.set) {
+ axis(pic,axis);
+ autoscale3(pic,axis);
+ }
+
+ bool newticks=false;
+
+ if(zmin != -infinity) {
+ zmin=pic.scale.z.T(zmin);
+ newticks=true;
+ }
+
+ if(zmax != infinity) {
+ zmax=pic.scale.z.T(zmax);
+ newticks=true;
+ }
+
+ if(newticks && pic.userSetz() && ticks != NoTicks3) {
+ if(zmin == -infinity) zmin=pic.userMin().z;
+ if(zmax == infinity) zmax=pic.userMax().z;
+ bounds mz=autoscale(zmin,zmax,pic.scale.z.scale);
+ pic.scale.z.tickMin=mz.min;
+ pic.scale.z.tickMax=mz.max;
+ axis.zdivisor=mz.divisor;
+ }
+
+ axis(pic,axis);
+
+ if(zmin == -infinity && !axis.extend) {
+ if(pic.scale.set)
+ zmin=pic.scale.z.automin() ? pic.scale.z.tickMin :
+ max(pic.scale.z.tickMin,pic.userMin().z);
+ else zmin=pic.userMin().z;
+ }
+
+ if(zmax == infinity && !axis.extend) {
+ if(pic.scale.set)
+ zmax=pic.scale.z.automax() ? pic.scale.z.tickMax :
+ min(pic.scale.z.tickMax,pic.userMax().z);
+ else zmax=pic.userMax().z;
+ }
+
+ if(L.defaultposition) {
+ L=L.copy();
+ L.position(axis.position);
+ }
+
+ bool back=false;
+ if(axis.type == Both) {
+ triple v=currentprojection.vector();
+ back=dot((pic.userMax().x-pic.userMin().x,0,0),v)*sgn(v.y) > 0;
+ }
+
+ zaxis3At(pic,L,axis,zmin,zmax,p,ticks,arrow,margin,above,false,false,!back);
+ if(axis.type == Both)
+ zaxis3At(pic,L,axis,zmin,zmax,p,ticks,arrow,margin,above,true,false,back);
+ if(axis.type2 == Both) {
+ zaxis3At(pic,L,axis,zmin,zmax,p,ticks,arrow,margin,above,false,true,false);
+ if(axis.type == Both)
+ zaxis3At(pic,L,axis,zmin,zmax,p,ticks,arrow,margin,above,true,true,false);
+ }
+}
+
+// Set the z limits of a picture.
+void zlimits(picture pic=currentpicture, real min=-infinity, real max=infinity,
+ bool crop=NoCrop)
+{
+ if(min > max) return;
+
+ pic.scale.z.automin=min <= -infinity;
+ pic.scale.z.automax=max >= infinity;
+
+ bounds mz;
+ if(pic.scale.z.automin() || pic.scale.z.automax())
+ mz=autoscale(pic.userMin().z,pic.userMax().z,pic.scale.z.scale);
+
+ if(pic.scale.z.automin) {
+ if(pic.scale.z.automin()) pic.userMinz(mz.min);
+ } else pic.userMinz(min(pic.scale.z.T(min),pic.scale.z.T(max)));
+
+ if(pic.scale.z.automax) {
+ if(pic.scale.z.automax()) pic.userMaxz(mz.max);
+ } else pic.userMaxz(max(pic.scale.z.T(min),pic.scale.z.T(max)));
+}
+
+// Restrict the x, y, and z limits to box(min,max).
+void limits(picture pic=currentpicture, triple min, triple max)
+{
+ xlimits(pic,min.x,max.x);
+ ylimits(pic,min.y,max.y);
+ zlimits(pic,min.z,max.z);
+}
+
+// Draw x, y and z axes.
+void axes3(picture pic=currentpicture,
+ Label xlabel="", Label ylabel="", Label zlabel="",
+ bool extend=false,
+ triple min=(-infinity,-infinity,-infinity),
+ triple max=(infinity,infinity,infinity),
+ pen p=currentpen, arrowbar3 arrow=None, margin3 margin=NoMargin3)
+{
+ xaxis3(pic,xlabel,YZZero(extend),min.x,max.x,p,arrow,margin);
+ yaxis3(pic,ylabel,XZZero(extend),min.y,max.y,p,arrow,margin);
+ zaxis3(pic,zlabel,XYZero(extend),min.z,max.z,p,arrow,margin);
+}
+
+triple Scale(picture pic=currentpicture, triple v)
+{
+ return (pic.scale.x.T(v.x),pic.scale.y.T(v.y),pic.scale.z.T(v.z));
+}
+
+real ScaleZ(picture pic=currentpicture, real z)
+{
+ return pic.scale.z.T(z);
+}
+
+// Draw a tick of length size at triple v in direction dir using pen p.
+void tick(picture pic=currentpicture, triple v, triple dir, real size=Ticksize,
+ pen p=currentpen)
+{
+ triple v=Scale(pic,v);
+ pic.add(new void (picture f, transform3 t) {
+ triple tv=t*v;
+ draw(f,tv--tv+unit(dir)*size,p);
+ });
+ pic.addPoint(v,p);
+ pic.addPoint(v,unit(dir)*size,p);
+}
+
+void xtick(picture pic=currentpicture, triple v, triple dir=Y,
+ real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,v,dir,size,p);
+}
+
+void xtick3(picture pic=currentpicture, real x, triple dir=Y,
+ real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,(x,pic.scale.y.scale.logarithmic ? 1 : 0,
+ pic.scale.z.scale.logarithmic ? 1 : 0),dir,size,p);
+}
+
+void ytick(picture pic=currentpicture, triple v, triple dir=X,
+ real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,v,dir,size,p);
+}
+
+void ytick3(picture pic=currentpicture, real y, triple dir=X,
+ real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,(pic.scale.x.scale.logarithmic ? 1 : 0,y,
+ pic.scale.z.scale.logarithmic ? 1 : 0),dir,size,p);
+}
+
+void ztick(picture pic=currentpicture, triple v, triple dir=X,
+ real size=Ticksize, pen p=currentpen)
+{
+ xtick(pic,v,dir,size,p);
+}
+
+void ztick3(picture pic=currentpicture, real z, triple dir=X,
+ real size=Ticksize, pen p=currentpen)
+{
+ xtick(pic,(pic.scale.x.scale.logarithmic ? 1 : 0,
+ pic.scale.y.scale.logarithmic ? 1 : 0,z),dir,size,p);
+}
+
+void tick(picture pic=currentpicture, Label L, real value, triple v,
+ triple dir, string format="", real size=Ticksize, pen p=currentpen)
+{
+ Label L=L.copy();
+ L.align(L.align,-dir);
+ if(shift(L.T3)*O == O)
+ L.T3=shift(dot(dir,L.align.dir3) > 0 ? dir*size :
+ ticklabelshift(L.align.dir3,p))*L.T3;
+ L.p(p);
+ if(L.s == "") L.s=format(format == "" ? defaultformat : format,value);
+ L.s=baseline(L.s,baselinetemplate);
+ label(pic,L,Scale(pic,v));
+ tick(pic,v,dir,size,p);
+}
+
+void xtick(picture pic=currentpicture, Label L, triple v, triple dir=Y,
+ string format="", real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,L,v.x,v,dir,format,size,p);
+}
+
+void xtick3(picture pic=currentpicture, Label L, real x, triple dir=Y,
+ string format="", real size=Ticksize, pen p=currentpen)
+{
+ xtick(pic,L,(x,pic.scale.y.scale.logarithmic ? 1 : 0,
+ pic.scale.z.scale.logarithmic ? 1 : 0),dir,size,p);
+}
+
+void ytick(picture pic=currentpicture, Label L, triple v, triple dir=X,
+ string format="", real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,L,v.y,v,dir,format,size,p);
+}
+
+void ytick3(picture pic=currentpicture, Label L, real y, triple dir=X,
+ string format="", real size=Ticksize, pen p=currentpen)
+{
+ xtick(pic,L,(pic.scale.x.scale.logarithmic ? 1 : 0,y,
+ pic.scale.z.scale.logarithmic ? 1 : 0),dir,format,size,p);
+}
+
+void ztick(picture pic=currentpicture, Label L, triple v, triple dir=X,
+ string format="", real size=Ticksize, pen p=currentpen)
+{
+ tick(pic,L,v.z,v,dir,format,size,p);
+}
+
+void ztick3(picture pic=currentpicture, Label L, real z, triple dir=X,
+ string format="", real size=Ticksize, pen p=currentpen)
+{
+ xtick(pic,L,(pic.scale.x.scale.logarithmic ? 1 : 0,
+ pic.scale.z.scale.logarithmic ? 1 : 0,z),dir,format,size,p);
+}
+
+private void label(picture pic, Label L, triple v, real x, align align,
+ string format, pen p)
+{
+ Label L=L.copy();
+ L.align(align);
+ L.p(p);
+ if(shift(L.T3)*O == O)
+ L.T3=shift(ticklabelshift(L.align.dir3,L.p))*L.T3;
+ if(L.s == "") L.s=format(format == "" ? defaultformat : format,x);
+ L.s=baseline(L.s,baselinetemplate);
+ label(pic,L,v);
+}
+
+void labelx(picture pic=currentpicture, Label L="", triple v,
+ align align=-Y, string format="", pen p=currentpen)
+{
+ label(pic,L,Scale(pic,v),v.x,align,format,p);
+}
+
+void labelx3(picture pic=currentpicture, Label L="", real x,
+ align align=-Y, string format="", pen p=currentpen)
+{
+ labelx(pic,L,(x,pic.scale.y.scale.logarithmic ? 1 : 0,
+ pic.scale.z.scale.logarithmic ? 1 : 0),align,format,p);
+}
+
+void labely(picture pic=currentpicture, Label L="", triple v,
+ align align=-X, string format="", pen p=currentpen)
+{
+ label(pic,L,Scale(pic,v),v.y,align,format,p);
+}
+
+void labely3(picture pic=currentpicture, Label L="", real y,
+ align align=-X, string format="", pen p=currentpen)
+{
+ labely(pic,L,(pic.scale.x.scale.logarithmic ? 1 : 0,y,
+ pic.scale.z.scale.logarithmic ? 1 : 0),align,format,p);
+}
+
+void labelz(picture pic=currentpicture, Label L="", triple v,
+ align align=-X, string format="", pen p=currentpen)
+{
+ label(pic,L,Scale(pic,v),v.z,align,format,p);
+}
+
+void labelz3(picture pic=currentpicture, Label L="", real z,
+ align align=-X, string format="", pen p=currentpen)
+{
+ labelz(pic,L,(pic.scale.x.scale.logarithmic ? 1 : 0,
+ pic.scale.y.scale.logarithmic ? 1 : 0,z),align,format,p);
+}
+
+typedef guide3 graph(triple F(real), real, real, int);
+typedef guide3[] multigraph(triple F(real), real, real, int);
+
+graph graph(interpolate3 join)
+{
+ return new guide3(triple f(real), real a, real b, int n) {
+ real width=b-a;
+ return n == 0 ? join(f(a)) :
+ join(...sequence(new guide3(int i) {return f(a+(i/n)*width);},n+1));
+ };
+}
+
+multigraph graph(interpolate3 join, bool3 cond(real))
+{
+ return new guide3[](triple f(real), real a, real b, int n) {
+ real width=b-a;
+ if(n == 0) return new guide3[] {join(cond(a) ? f(a) : nullpath3)};
+ guide3[] G;
+ guide3[] g;
+ for(int i=0; i < n+1; ++i) {
+ real t=a+(i/n)*width;
+ bool3 b=cond(t);
+ if(b)
+ g.push(f(t));
+ else {
+ if(g.length > 0) {
+ G.push(join(...g));
+ g=new guide3[] {};
+ }
+ if(b == default)
+ g.push(f(t));
+ }
+ }
+ if(g.length > 0)
+ G.push(join(...g));
+ return G;
+ };
+}
+
+guide3 Straight(... guide3[])=operator --;
+guide3 Spline(... guide3[])=operator ..;
+
+guide3 graph(picture pic=currentpicture, real x(real), real y(real),
+ real z(real), real a, real b, int n=ngraph,
+ interpolate3 join=operator --)
+{
+ return graph(join)(new triple(real t) {return Scale(pic,(x(t),y(t),z(t)));},
+ a,b,n);
+}
+
+guide3[] graph(picture pic=currentpicture, real x(real), real y(real),
+ real z(real), real a, real b, int n=ngraph,
+ bool3 cond(real), interpolate3 join=operator --)
+{
+ return graph(join,cond)(new triple(real t) {
+ return Scale(pic,(x(t),y(t),z(t)));
+ },a,b,n);
+}
+
+guide3 graph(picture pic=currentpicture, triple v(real), real a, real b,
+ int n=ngraph, interpolate3 join=operator --)
+{
+ return graph(join)(new triple(real t) {return Scale(pic,v(t));},a,b,n);
+}
+
+guide3[] graph(picture pic=currentpicture, triple v(real), real a, real b,
+ int n=ngraph, bool3 cond(real), interpolate3 join=operator --)
+{
+ return graph(join,cond)(new triple(real t) {
+ return Scale(pic,v(t));
+ },a,b,n);
+}
+
+guide3 graph(picture pic=currentpicture, triple[] v,
+ interpolate3 join=operator --)
+{
+ int i=0;
+ return graph(join)(new triple(real) {
+ triple w=Scale(pic,v[i]);
+ ++i;
+ return w;
+ },0,0,v.length-1);
+}
+
+guide3[] graph(picture pic=currentpicture, triple[] v, bool3[] cond,
+ interpolate3 join=operator --)
+{
+ int n=v.length;
+ int i=0;
+ triple w;
+ checkconditionlength(cond.length,n);
+ bool3 condition(real) {
+ bool b=cond[i];
+ if(b) w=Scale(pic,v[i]);
+ ++i;
+ return b;
+ }
+ return graph(join,condition)(new triple(real) {return w;},0,0,n-1);
+}
+
+guide3 graph(picture pic=currentpicture, real[] x, real[] y, real[] z,
+ interpolate3 join=operator --)
+{
+ int n=x.length;
+ checklengths(n,y.length);
+ checklengths(n,z.length);
+ int i=0;
+ return graph(join)(new triple(real) {
+ triple w=Scale(pic,(x[i],y[i],z[i]));
+ ++i;
+ return w;
+ },0,0,n-1);
+}
+
+guide3[] graph(picture pic=currentpicture, real[] x, real[] y, real[] z,
+ bool3[] cond, interpolate3 join=operator --)
+{
+ int n=x.length;
+ checklengths(n,y.length);
+ checklengths(n,z.length);
+ int i=0;
+ triple w;
+ checkconditionlength(cond.length,n);
+ bool3 condition(real) {
+ bool3 b=cond[i];
+ if(b != false) w=Scale(pic,(x[i],y[i],z[i]));
+ ++i;
+ return b;
+ }
+ return graph(join,condition)(new triple(real) {return w;},0,0,n-1);
+}
+
+// The graph of a function along a path.
+guide3 graph(triple F(path, real), path p, int n=1,
+ interpolate3 join=operator --)
+{
+ guide3 g=join(...sequence(new guide3(int i) {
+ return F(p,i/n);
+ },n*length(p)));
+ return cyclic(p) ? join(g,cycle) : join(g,F(p,length(p)));
+}
+
+guide3 graph(triple F(pair), path p, int n=1, interpolate3 join=operator --)
+{
+ return graph(new triple(path p, real position)
+ {return F(point(p,position));},p,n,join);
+}
+
+guide3 graph(picture pic=currentpicture, real f(pair), path p, int n=1,
+ interpolate3 join=operator --)
+{
+ return graph(new triple(pair z) {return Scale(pic,(z.x,z.y,f(z)));},p,n,
+ join);
+}
+
+guide3 graph(real f(pair), path p, int n=1, real T(pair),
+ interpolate3 join=operator --)
+{
+ return graph(new triple(pair z) {pair w=T(z); return (w.x,w.y,f(w));},p,n,
+ join);
+}
+
+// Connect points in v into segments corresponding to consecutive true elements
+// of b using interpolation operator join.
+path3[] segment(triple[] v, bool[] cond, interpolate3 join=operator --)
+{
+ checkconditionlength(cond.length,v.length);
+ int[][] segment=segment(cond);
+ return sequence(new path3(int i) {return join(...v[segment[i]]);},
+ segment.length);
+}
+
+bool uperiodic(triple[][] a) {
+ int n=a.length;
+ if(n == 0) return false;
+ int m=a[0].length;
+ triple[] a0=a[0];
+ triple[] a1=a[n-1];
+ real epsilon=sqrtEpsilon*norm(a);
+ for(int j=0; j < m; ++j)
+ if(abs(a0[j]-a1[j]) > epsilon) return false;
+ return true;
+}
+bool vperiodic(triple[][] a) {
+ int n=a.length;
+ if(n == 0) return false;
+ int m=a[0].length-1;
+ real epsilon=sqrtEpsilon*norm(a);
+ for(int i=0; i < n; ++i)
+ if(abs(a[i][0]-a[i][m]) > epsilon) return false;
+ return true;
+}
+
+// return the surface described by a matrix f
+surface surface(triple[][] f, bool[][] cond={})
+{
+ if(!rectangular(f)) abort("matrix is not rectangular");
+
+ int nx=f.length-1;
+ int ny=nx > 0 ? f[0].length-1 : 0;
+
+ bool all=cond.length == 0;
+
+ int count;
+ if(all)
+ count=nx*ny;
+ else {
+ count=0;
+ for(int i=0; i < nx; ++i) {
+ bool[] condi=cond[i];
+ bool[] condp=cond[i+1];
+ for(int j=0; j < ny; ++j)
+ if(condi[j] && condi[j+1] && condp[j] && condp[j+1]) ++count;
+ }
+ }
+
+ surface s=surface(count);
+ s.index=new int[nx][ny];
+ int k=-1;
+ for(int i=0; i < nx; ++i) {
+ bool[] condi,condp;
+ if(!all) {
+ condi=cond[i];
+ condp=cond[i+1];
+ }
+ triple[] fi=f[i];
+ triple[] fp=f[i+1];
+ int[] indexi=s.index[i];
+ for(int j=0; j < ny; ++j) {
+ if(all || (condi[j] && condi[j+1] && condp[j] && condp[j+1]))
+ s.s[++k]=patch(new triple[] {fi[j],fp[j],fp[j+1],fi[j+1]});
+ indexi[j]=k;
+ }
+ }
+
+ if(count == nx*ny) {
+ if(uperiodic(f)) s.ucyclic(true);
+ if(vperiodic(f)) s.vcyclic(true);
+ }
+
+ return s;
+}
+
+surface bispline(real[][] z, real[][] p, real[][] q, real[][] r,
+ real[] x, real[] y, bool[][] cond={})
+{ // z[i][j] is the value at (x[i],y[j])
+ // p and q are the first derivatives with respect to x and y, respectively
+ // r is the second derivative ddu/dxdy
+ int n=x.length-1;
+ int m=y.length-1;
+
+ bool all=cond.length == 0;
+
+ int count;
+ if(all)
+ count=n*m;
+ else {
+ count=0;
+ for(int i=0; i < n; ++i) {
+ bool[] condi=cond[i];
+ for(int j=0; j < m; ++j)
+ if(condi[j]) ++count;
+ }
+ }
+
+ surface s=surface(count);
+ s.index=new int[n][m];
+ int k=0;
+ for(int i=0; i < n; ++i) {
+ int ip=i+1;
+ real xi=x[i];
+ real xp=x[ip];
+ real x1=interp(xi,xp,1/3);
+ real x2=interp(xi,xp,2/3);
+ real hx=x1-xi;
+ real[] zi=z[i];
+ real[] zp=z[ip];
+ real[] ri=r[i];
+ real[] rp=r[ip];
+ real[] pi=p[i];
+ real[] pp=p[ip];
+ real[] qi=q[i];
+ real[] qp=q[ip];
+ int[] indexi=s.index[i];
+ bool[] condi=all ? null : cond[i];
+ for(int j=0; j < m; ++j) {
+ if(all || condi[j]) {
+ real yj=y[j];
+ int jp=j+1;
+ real yp=y[jp];
+ real y1=interp(yj,yp,1/3);
+ real y2=interp(yj,yp,2/3);
+ real hy=y1-yj;
+ real hxy=hx*hy;
+ real zij=zi[j];
+ real zip=zi[jp];
+ real zpj=zp[j];
+ real zpp=zp[jp];
+ real pij=hx*pi[j];
+ real ppj=hx*pp[j];
+ real qip=hy*qi[jp];
+ real qpp=hy*qp[jp];
+ real zippip=zip+hx*pi[jp];
+ real zppmppp=zpp-hx*pp[jp];
+ real zijqij=zij+hy*qi[j];
+ real zpjqpj=zpj+hy*qp[j];
+
+ s.s[k]=patch(new triple[][] {
+ {(xi,yj,zij),(xi,y1,zijqij),(xi,y2,zip-qip),(xi,yp,zip)},
+ {(x1,yj,zij+pij),(x1,y1,zijqij+pij+hxy*ri[j]),
+ (x1,y2,zippip-qip-hxy*ri[jp]),(x1,yp,zippip)},
+ {(x2,yj,zpj-ppj),(x2,y1,zpjqpj-ppj-hxy*rp[j]),
+ (x2,y2,zppmppp-qpp+hxy*rp[jp]),(x2,yp,zppmppp)},
+ {(xp,yj,zpj),(xp,y1,zpjqpj),(xp,y2,zpp-qpp),(xp,yp,zpp)}},copy=false);
+ indexi[j]=k;
+ ++k;
+ }
+ }
+ }
+
+ return s;
+}
+
+// return the surface described by a real matrix f, interpolated with
+// xsplinetype and ysplinetype.
+surface surface(real[][] f, real[] x, real[] y,
+ splinetype xsplinetype=null, splinetype ysplinetype=xsplinetype,
+ bool[][] cond={})
+{
+ real epsilon=sqrtEpsilon*norm(y);
+ if(xsplinetype == null)
+ xsplinetype=(abs(x[0]-x[x.length-1]) <= epsilon) ? periodic : notaknot;
+ if(ysplinetype == null)
+ ysplinetype=(abs(y[0]-y[y.length-1]) <= epsilon) ? periodic : notaknot;
+ int n=x.length; int m=y.length;
+ real[][] ft=transpose(f);
+ real[][] tp=new real[m][];
+ for(int j=0; j < m; ++j)
+ tp[j]=xsplinetype(x,ft[j]);
+ real[][] q=new real[n][];
+ for(int i=0; i < n; ++i)
+ q[i]=ysplinetype(y,f[i]);
+ real[][] qt=transpose(q);
+ real[] d1=xsplinetype(x,qt[0]);
+ real[] d2=xsplinetype(x,qt[m-1]);
+ real[][] r=new real[n][];
+ real[][] p=transpose(tp);
+ for(int i=0; i < n; ++i)
+ r[i]=clamped(d1[i],d2[i])(y,p[i]);
+ surface s=bispline(f,p,q,r,x,y,cond);
+ if(xsplinetype == periodic) s.ucyclic(true);
+ if(ysplinetype == periodic) s.vcyclic(true);
+ return s;
+}
+
+// return the surface described by a real matrix f, interpolated with
+// xsplinetype and ysplinetype.
+surface surface(real[][] f, pair a, pair b, splinetype xsplinetype,
+ splinetype ysplinetype=xsplinetype, bool[][] cond={})
+{
+ if(!rectangular(f)) abort("matrix is not rectangular");
+
+ int nx=f.length-1;
+ int ny=nx > 0 ? f[0].length-1 : 0;
+
+ if(nx == 0 || ny == 0) return nullsurface;
+
+ real[] x=uniform(a.x,b.x,nx);
+ real[] y=uniform(a.y,b.y,ny);
+ return surface(f,x,y,xsplinetype,ysplinetype,cond);
+}
+
+// return the surface described by a real matrix f, interpolated linearly.
+surface surface(real[][] f, pair a, pair b, bool[][] cond={})
+{
+ if(!rectangular(f)) abort("matrix is not rectangular");
+
+ int nx=f.length-1;
+ int ny=nx > 0 ? f[0].length-1 : 0;
+
+ if(nx == 0 || ny == 0) return nullsurface;
+
+ bool all=cond.length == 0;
+
+ triple[][] v=new triple[nx+1][ny+1];
+ for(int i=0; i <= nx; ++i) {
+ real x=interp(a.x,b.x,i/nx);
+ bool[] condi=all ? null : cond[i];
+ triple[] vi=v[i];
+ real[] fi=f[i];
+ for(int j=0; j <= ny; ++j)
+ if(all || condi[j])
+ vi[j]=(x,interp(a.y,b.y,j/ny),fi[j]);
+ }
+ return surface(v,cond);
+}
+
+// return the surface described by a parametric function f over box(a,b),
+// interpolated linearly.
+surface surface(triple f(pair z), pair a, pair b, int nu=nmesh, int nv=nu,
+ bool cond(pair z)=null)
+{
+ if(nu <= 0 || nv <= 0) return nullsurface;
+
+ bool[][] active;
+ bool all=cond == null;
+ if(!all) active=new bool[nu+1][nv+1];
+
+ real du=1/nu;
+ real dv=1/nv;
+ pair Idv=(0,dv);
+ pair dz=(du,dv);
+
+ triple[][] v=new triple[nu+1][nv+1];
+
+ for(int i=0; i <= nu; ++i) {
+ real x=interp(a.x,b.x,i*du);
+ bool[] activei=all ? null : active[i];
+ triple[] vi=v[i];
+ for(int j=0; j <= nv; ++j) {
+ pair z=(x,interp(a.y,b.y,j*dv));
+ if(all || (activei[j]=cond(z))) vi[j]=f(z);
+ }
+ }
+ return surface(v,active);
+}
+
+// return the surface described by a parametric function f over box(a,b),
+// interpolated with usplinetype and vsplinetype.
+surface surface(triple f(pair z), pair a, pair b, int nu=nmesh, int nv=nu,
+ splinetype[] usplinetype, splinetype[] vsplinetype=Spline,
+ bool cond(pair z)=null)
+{
+ return surface(f,uniform(a.x,b.x,nu),uniform(a.y,b.y,nv),
+ usplinetype,vsplinetype,cond);
+}
+
+// return the surface described by a real function f over box(a,b),
+// interpolated linearly.
+surface surface(real f(pair z), pair a, pair b, int nx=nmesh, int ny=nx,
+ bool cond(pair z)=null)
+{
+ return surface(new triple(pair z) {return (z.x,z.y,f(z));},a,b,nx,ny,cond);
+}
+
+// return the surface described by a real function f over box(a,b),
+// interpolated with xsplinetype and ysplinetype.
+surface surface(real f(pair z), pair a, pair b, int nx=nmesh, int ny=nx,
+ splinetype xsplinetype, splinetype ysplinetype=xsplinetype,
+ bool cond(pair z)=null)
+{
+ bool[][] active;
+ bool all=cond == null;
+ if(!all) active=new bool[nx+1][ny+1];
+
+ real dx=1/nx;
+ real dy=1/ny;
+ pair Idy=(0,dy);
+ pair dz=(dx,dy);
+
+ real[][] F=new real[nx+1][ny+1];
+ real[] x=uniform(a.x,b.x,nx);
+ real[] y=uniform(a.y,b.y,ny);
+ for(int i=0; i <= nx; ++i) {
+ bool[] activei=all ? null : active[i];
+ real[] Fi=F[i];
+ real x=x[i];
+ for(int j=0; j <= ny; ++j) {
+ pair z=(x,y[j]);
+ Fi[j]=f(z);
+ if(!all) activei[j]=cond(z);
+ }
+ }
+ return surface(F,x,y,xsplinetype,ysplinetype,active);
+}
+
+guide3[][] lift(real f(real x, real y), guide[][] g,
+ interpolate3 join=operator --)
+{
+ guide3[][] G=new guide3[g.length][];
+ for(int cnt=0; cnt < g.length; ++cnt) {
+ guide[] gcnt=g[cnt];
+ guide3[] Gcnt=new guide3[gcnt.length];
+ for(int i=0; i < gcnt.length; ++i) {
+ guide gcnti=gcnt[i];
+ guide3 Gcnti=join(...sequence(new guide3(int j) {
+ pair z=point(gcnti,j);
+ return (z.x,z.y,f(z.x,z.y));
+ },size(gcnti)));
+ if(cyclic(gcnti)) Gcnti=Gcnti..cycle;
+ Gcnt[i]=Gcnti;
+ }
+ G[cnt]=Gcnt;
+ }
+ return G;
+}
+
+guide3[][] lift(real f(pair z), guide[][] g, interpolate3 join=operator --)
+{
+ return lift(new real(real x, real y) {return f((x,y));},g,join);
+}
+
+void draw(picture pic=currentpicture, Label[] L=new Label[],
+ guide3[][] g, pen[] p, light light=currentlight, string name="",
+ render render=defaultrender,
+ interaction interaction=LabelInteraction())
+{
+ pen thin=is3D() ? thin() : defaultpen;
+ bool group=g.length > 1 && (name != "" || render.defaultnames);
+ if(group)
+ begingroup3(pic,name == "" ? "contours" : name,render);
+ for(int cnt=0; cnt < g.length; ++cnt) {
+ guide3[] gcnt=g[cnt];
+ pen pcnt=thin+p[cnt];
+ for(int i=0; i < gcnt.length; ++i)
+ draw(pic,gcnt[i],pcnt,light,name);
+ if(L.length > 0) {
+ Label Lcnt=L[cnt];
+ for(int i=0; i < gcnt.length; ++i) {
+ if(Lcnt.s != "" && size(gcnt[i]) > 1)
+ label(pic,Lcnt,gcnt[i],pcnt,name,interaction);
+ }
+ }
+ }
+ if(group)
+ endgroup3(pic);
+}
+
+void draw(picture pic=currentpicture, Label[] L=new Label[],
+ guide3[][] g, pen p=currentpen, light light=currentlight,
+ string name="", render render=defaultrender,
+ interaction interaction=LabelInteraction())
+{
+ draw(pic,L,g,sequence(new pen(int) {return p;},g.length),light,name,
+ render,interaction);
+}
+
+real maxlength(triple f(pair z), pair a, pair b, int nu, int nv)
+{
+ return min(abs(f((b.x,a.y))-f(a))/nu,abs(f((a.x,b.y))-f(a))/nv);
+}
+
+// return a vector field on a parametric surface f over box(a,b).
+picture vectorfield(path3 vector(pair v), triple f(pair z), pair a, pair b,
+ int nu=nmesh, int nv=nu, bool truesize=false,
+ real maxlength=truesize ? 0 : maxlength(f,a,b,nu,nv),
+ bool cond(pair z)=null, pen p=currentpen,
+ arrowbar3 arrow=Arrow3, margin3 margin=PenMargin3,
+ string name="", render render=defaultrender)
+{
+ picture pic;
+ real du=1/nu;
+ real dv=1/nv;
+ bool all=cond == null;
+ real scale;
+
+ if(maxlength > 0) {
+ real size(pair z) {
+ path3 g=vector(z);
+ return abs(point(g,size(g)-1)-point(g,0));
+ }
+ real max=size((0,0));
+ for(int i=0; i <= nu; ++i) {
+ real x=interp(a.x,b.x,i*du);
+ for(int j=0; j <= nv; ++j)
+ max=max(max,size((x,interp(a.y,b.y,j*dv))));
+ }
+ scale=max > 0 ? maxlength/max : 1;
+ } else scale=1;
+
+ bool group=name != "" || render.defaultnames;
+ if(group)
+ begingroup3(pic,name == "" ? "vectorfield" : name,render);
+ for(int i=0; i <= nu; ++i) {
+ real x=interp(a.x,b.x,i*du);
+ for(int j=0; j <= nv; ++j) {
+ pair z=(x,interp(a.y,b.y,j*dv));
+ if(all || cond(z)) {
+ path3 g=scale3(scale)*vector(z);
+ string name="vector";
+ if(truesize) {
+ picture opic;
+ draw(opic,g,p,arrow,margin,name,render);
+ add(pic,opic,f(z));
+ } else
+ draw(pic,shift(f(z))*g,p,arrow,margin,name,render);
+ }
+ }
+ }
+ if(group)
+ endgroup3(pic);
+ return pic;
+}
+
+triple polar(real r, real theta, real phi)
+{
+ return r*expi(theta,phi);
+}
+
+guide3 polargraph(real r(real,real), real theta(real), real phi(real),
+ int n=ngraph, interpolate3 join=operator --)
+{
+ return graph(join)(new triple(real t) {
+ return polar(r(theta(t),phi(t)),theta(t),phi(t));
+ },0,1,n);
+}
+
+// True arc
+path3 Arc(triple c, triple v1, triple v2, triple normal=O, bool direction=CCW,
+ int n=nCircle)
+{
+ v1 -= c;
+ real r=abs(v1);
+ v1=unit(v1);
+ v2=unit(v2-c);
+
+ if(normal == O) {
+ normal=cross(v1,v2);
+ if(normal == O) abort("explicit normal required for these endpoints");
+ }
+
+ transform3 T=align(unit(normal));
+ transform3 Tinv=transpose(T);
+ v1=Tinv*v1;
+ v2=Tinv*v2;
+
+ real fuzz=sqrtEpsilon*max(abs(v1),abs(v2));
+ if(abs(v1.z) > fuzz || abs(v2.z) > fuzz)
+ abort("invalid normal vector");
+
+ real phi1=radians(longitude(v1,warn=false));
+ real phi2=radians(longitude(v2,warn=false));
+ if(direction) {
+ if(phi1 >= phi2) phi1 -= 2pi;
+ } else if(phi2 >= phi1) phi2 -= 2pi;
+
+ static real piby2=pi/2;
+ return shift(c)*T*polargraph(new real(real theta, real phi) {return r;},
+ new real(real t) {return piby2;},
+ new real(real t) {return interp(phi1,phi2,t);},
+ n,operator ..);
+}
+
+path3 Arc(triple c, real r, real theta1, real phi1, real theta2, real phi2,
+ triple normal=O, bool direction, int n=nCircle)
+{
+ return Arc(c,c+r*dir(theta1,phi1),c+r*dir(theta2,phi2),normal,direction,n);
+}
+
+path3 Arc(triple c, real r, real theta1, real phi1, real theta2, real phi2,
+ triple normal=O, int n=nCircle)
+{
+ return Arc(c,r,theta1,phi1,theta2,phi2,normal,
+ theta2 > theta1 || (theta2 == theta1 && phi2 >= phi1) ? CCW : CW,
+ n);
+}
+
+// True circle
+path3 Circle(triple c, real r, triple normal=Z, int n=nCircle)
+{
+ static real piby2=pi/2;
+ return shift(c)*align(unit(normal))*
+ polargraph(new real(real theta, real phi) {return r;},
+ new real(real t) {return piby2;},
+ new real(real t) {return interp(0,2pi,t);},n,operator ..);
+
+}
diff --git a/graphics/asymptote/base/graph_settings.asy b/graphics/asymptote/base/graph_settings.asy
new file mode 100644
index 0000000000..f5d04d0797
--- /dev/null
+++ b/graphics/asymptote/base/graph_settings.asy
@@ -0,0 +1,18 @@
+// Number of function samples.
+int ngraph=100;
+int nCircle=400;
+
+// Number of mesh intervals.
+int nmesh=10;
+
+real ticksize=1mm;
+real Ticksize=2*ticksize;
+real ylabelwidth=2.0;
+real axislabelfactor=1.5;
+real axiscoverage=0.8;
+
+real epsilon=10*realEpsilon;
+
+restricted bool Crop=true;
+restricted bool NoCrop=false;
+
diff --git a/graphics/asymptote/base/graph_splinetype.asy b/graphics/asymptote/base/graph_splinetype.asy
new file mode 100644
index 0000000000..02f780ca02
--- /dev/null
+++ b/graphics/asymptote/base/graph_splinetype.asy
@@ -0,0 +1,264 @@
+private import math;
+
+typedef real[] splinetype(real[], real[]);
+
+restricted real[] Spline(real[] x, real[] y);
+restricted splinetype[] Spline;
+
+string morepoints="interpolation requires at least 2 points";
+string differentlengths="arrays have different lengths";
+void checklengths(int x, int y, string text=differentlengths)
+{
+ if(x != y)
+ abort(text+": "+string(x)+" != "+string(y));
+}
+
+void checkincreasing(real[] x)
+{
+ if(!increasing(x,true))
+ abort("strictly increasing array expected");
+}
+
+// Linear interpolation
+real[] linear(real[] x, real[] y)
+{
+ int n=x.length;
+ checklengths(n,y.length);
+ real[] d=new real[n];
+ for(int i=0; i < n-1; ++i)
+ d[i]=(y[i+1]-y[i])/(x[i+1]-x[i]);
+ d[n-1]=d[n-2];
+ return d;
+}
+
+// Standard cubic spline interpolation with not-a-knot condition:
+// s'''(x_2^-)=s'''(x_2^+) et s'''(x_(n_2)^-)=s'''(x_(n-2)^+)
+// if n=2, linear interpolation is returned
+// if n=3, an interpolation polynomial of degree <= 2 is returned:
+// p(x_1)=y_1, p(x_2)=y_2, p(x_3)=y_3
+real[] notaknot(real[] x, real[] y)
+{
+ int n=x.length;
+ checklengths(n,y.length);
+ checkincreasing(x);
+ real[] d;
+ if(n > 3) {
+ real[] a=new real[n];
+ real[] b=new real[n];
+ real[] c=new real[n];
+ real[] g=new real[n];
+ b[0]=x[2]-x[1];
+ c[0]=x[2]-x[0];
+ a[0]=0;
+ g[0]=((x[1]-x[0])^2*(y[2]-y[1])/b[0]+b[0]*(2*b[0]+3*(x[1]-x[0]))*
+ (y[1]-y[0])/(x[1]-x[0]))/c[0];
+ for(int i=1; i < n-1; ++i) {
+ a[i]=x[i+1]-x[i];
+ c[i]=x[i]-x[i-1];
+ b[i]=2*(a[i]+c[i]);
+ g[i]=3*(c[i]*(y[i+1]-y[i])/a[i]+a[i]*(y[i]-y[i-1])/c[i]);
+ }
+ c[n-1]=0;
+ b[n-1]=x[n-2]-x[n-3];
+ a[n-1]=x[n-1]-x[n-3];
+ g[n-1]=((x[n-1]-x[n-2])^2*(y[n-2]-y[n-3])/b[n-1]+
+ b[n-1]*(2*b[n-1]+3(x[n-1]-x[n-2]))*
+ (y[n-1]-y[n-2])/(x[n-1]-x[n-2]))/a[n-1];
+ d=tridiagonal(a,b,c,g);
+ } else if(n == 2) {
+ real val=(y[1]-y[0])/(x[1]-x[0]);
+ d=new real[] {val,val};
+ } else if(n == 3) {
+ real a=(y[1]-y[0])/(x[1]-x[0]);
+ real b=(y[2]-y[1])/(x[2]-x[1]);
+ real c=(b-a)/(x[2]-x[0]);
+ d=new real[] {a+c*(x[0]-x[1]),a+c*(x[1]-x[0]),a+c*(2*x[2]-x[0]-x[1])};
+ } else abort(morepoints);
+ return d;
+}
+
+// Standard cubic spline interpolation with periodic condition
+// s'(a)=s'(b), s''(a)=s''(b), assuming that f(a)=f(b)
+// if n=2, linear interpolation is returned
+real[] periodic(real[] x, real[] y)
+{
+ int n=x.length;
+ checklengths(n,y.length);
+ checkincreasing(x);
+ if(abs(y[n-1]-y[0]) > sqrtEpsilon*norm(y))
+ abort("function values are not periodic");
+ real[] d;
+ if(n > 2) {
+ real[] a=new real[n-1];
+ real[] b=new real[n-1];
+ real[] c=new real[n-1];
+ real[] g=new real[n-1];
+ c[0]=x[n-1]-x[n-2];
+ a[0]=x[1]-x[0];
+ b[0]=2*(a[0]+c[0]);
+ g[0]=3*c[0]*(y[1]-y[0])/a[0]+3*a[0]*(y[n-1]-y[n-2])/c[0];
+ for(int i=1; i < n-1; ++i) {
+ a[i]=x[i+1]-x[i];
+ c[i]=x[i]-x[i-1];
+ b[i]=2*(a[i]+c[i]);
+ g[i]=3*(c[i]*(y[i+1]-y[i])/a[i]+a[i]*(y[i]-y[i-1])/c[i]);
+ }
+ d=tridiagonal(a,b,c,g);
+ d.push(d[0]);
+ } else if(n == 2) {
+ d=new real[] {0,0};
+ } else abort(morepoints);
+ return d;
+}
+
+// Standard cubic spline interpolation with the natural condition
+// s''(a)=s''(b)=0.
+// if n=2, linear interpolation is returned
+// Don't use the natural type unless the underlying function
+// has zero second end points derivatives.
+real[] natural(real[] x, real[] y)
+{
+ int n=x.length;
+ checklengths(n,y.length);
+ checkincreasing(x);
+ real[] d;
+ if(n > 2) {
+ real[] a=new real[n];
+ real[] b=new real[n];
+ real[] c=new real[n];
+ real[] g=new real[n];
+ b[0]=2*(x[1]-x[0]);
+ c[0]=x[1]-x[0];
+ a[0]=0;
+ g[0]=3*(y[1]-y[0]);
+ for(int i=1; i < n-1; ++i) {
+ a[i]=x[i+1]-x[i];
+ c[i]=x[i]-x[i-1];
+ b[i]=2*(a[i]+c[i]);
+ g[i]=3*(c[i]*(y[i+1]-y[i])/a[i]+a[i]*(y[i]-y[i-1])/c[i]);
+ }
+ c[n-1]=0;
+ a[n-1]=x[n-1]-x[n-2];
+ b[n-1]=2*a[n-1];
+ g[n-1]=3*(y[n-1]-y[n-2]);
+ d=tridiagonal(a,b,c,g);
+ } else if(n == 2) {
+ real val=(y[1]-y[0])/(x[1]-x[0]);
+ d=new real[] {val,val};
+ } else abort(morepoints);
+ return d;
+}
+
+// Standard cubic spline interpolation with clamped conditions f'(a), f'(b)
+splinetype clamped(real slopea, real slopeb)
+{
+ return new real[] (real[] x, real[] y) {
+ int n=x.length;
+ checklengths(n,y.length);
+ checkincreasing(x);
+ real[] d;
+ if(n > 2) {
+ real[] a=new real[n];
+ real[] b=new real[n];
+ real[] c=new real[n];
+ real[] g=new real[n];
+ b[0]=x[1]-x[0];
+ g[0]=b[0]*slopea;
+ c[0]=0;
+ a[0]=0;
+ for(int i=1; i < n-1; ++i) {
+ a[i]=x[i+1]-x[i];
+ c[i]=x[i]-x[i-1];
+ b[i]=2*(a[i]+c[i]);
+ g[i]=3*(c[i]*(y[i+1]-y[i])/a[i]+a[i]*(y[i]-y[i-1])/c[i]);
+ }
+ c[n-1]=0;
+ a[n-1]=0;
+ b[n-1]=x[n-1]-x[n-2];
+ g[n-1]=b[n-1]*slopeb;
+ d=tridiagonal(a,b,c,g);
+ } else if(n == 2) {
+ d=new real[] {slopea,slopeb};
+ } else abort(morepoints);
+ return d;
+ };
+}
+
+// Piecewise Cubic Hermite Interpolating Polynomial (PCHIP)
+// Modified MATLAB code
+// [1] Fritsch, F. N. and R. E. Carlson,
+// "Monotone Piecewise Cubic Interpolation,"
+// SIAM J. Numerical Analysis, Vol. 17, 1980, pp.238-246.
+// [2] Kahaner, David, Cleve Moler, Stephen Nash,
+// Numerical Methods and Software, Prentice Hall, 1988.
+real[] monotonic(real[] x, real[] y)
+{
+ int n=x.length;
+ checklengths(n,y.length);
+ checkincreasing(x);
+ real[] d=new real[n];
+ if(n > 2) {
+ real[] h=new real[n-1];
+ real[] del=new real[n-1];
+ for(int i=0; i < n-1; ++i) {
+ h[i]=x[i+1]-x[i];
+ del[i]=(y[i+1]-y[i])/h[i];
+ }
+ int j=0;
+ int k[]=new int[];
+ for(int i=0; i < n-2; ++i)
+ if((sgn(del[i])*sgn(del[i+1])) > 0) {k[j]=i; j=j+1;}
+
+ real[] hs=new real[j];
+ for(int i=0; i < j; ++i) hs[i]=h[k[i]]+h[k[i]+1];
+ real w1[]=new real[j];
+ real w2[]=new real[j];
+ real dmax[]=new real[j];
+ real dmin[]=new real[j];
+ for(int i=0; i < j; ++i) {
+ w1[i]=(h[k[i]]+hs[i])/(3*hs[i]);
+ w2[i]=(h[k[i]+1]+hs[i])/(3*hs[i]);
+ dmax[i]=max(abs(del[k[i]]),abs(del[k[i]+1]));
+ dmin[i]=min(abs(del[k[i]]),abs(del[k[i]+1]));
+ }
+ for(int i=0; i < n; ++i) d[i]=0;
+ for(int i=0; i < j; ++i)
+ d[k[i]+1]=dmin[i]/(w1[i]*(del[k[i]]/dmax[i])+w2[i]*(del[k[i]+1]/dmax[i]));
+ d[0]=((2*h[0]+h[1])*del[0]-h[0]*del[1])/(h[0]+h[1]);
+ if(sgn(d[0]) != sgn(del[0])) {d[0]=0;}
+ else if((sgn(del[0]) != sgn(del[1])) && (abs(d[0]) > abs(3*del[0])))
+ d[0]=3*del[0];
+
+ d[n-1]=((2*h[n-2]+h[n-3])*del[n-2]-h[n-2]*del[n-2])/(h[n-2]+h[n-3]);
+ if(sgn(d[n-1]) != sgn(del[n-2])) {d[n-1]=0;}
+ else if((sgn(del[n-2]) != sgn(del[n-3])) &&
+ (abs(d[n-1]) > abs(3*del[n-2])))
+ d[n-1]=3*del[n-2];
+ } else if(n == 2) {
+ d[0]=d[1]=(y[1]-y[0])/(x[1]-x[0]);
+ } else abort(morepoints);
+ return d;
+}
+
+// Return standard cubic spline interpolation as a guide
+guide hermite(real[] x, real[] y, splinetype splinetype=null)
+{
+ int n=x.length;
+ if(n == 0) return nullpath;
+
+ guide g=(x[0],y[0]);
+ if(n == 1) return g;
+ if(n == 2) return g--(x[1],y[1]);
+
+ if(splinetype == null)
+ splinetype=(x[0] == x[x.length-1] && y[0] == y[y.length-1]) ?
+ periodic : notaknot;
+
+ real[] dy=splinetype(x,y);
+ for(int i=1; i < n; ++i) {
+ pair z=(x[i],y[i]);
+ real dx=x[i]-x[i-1];
+ g=g..controls((x[i-1],y[i-1])+dx*(1,dy[i-1])/3) and (z-dx*(1,dy[i])/3)..z;
+ }
+ return g;
+}
diff --git a/graphics/asymptote/base/grid3.asy b/graphics/asymptote/base/grid3.asy
new file mode 100644
index 0000000000..98e043a71d
--- /dev/null
+++ b/graphics/asymptote/base/grid3.asy
@@ -0,0 +1,412 @@
+// grid3.asy
+// Author: Philippe Ivaldi (Grids in 3D)
+// http://www.piprime.fr/
+// Created: 10 janvier 2007
+
+import graph3;
+
+struct grid3 {
+ path3 axea,axeb;
+ bounds bds;
+ triple dir;
+ valuetime vt;
+ ticklocate locate;
+ void create(picture pic, path3 axea, path3 axeb, path3 axelevel,
+ real min, real max, position pos, autoscaleT t) {
+ real position=pos.position.x;
+ triple level;
+ if(pos.relative) {
+ position=reltime(axelevel,position);
+ level=point(axelevel,position)-point(axelevel,0);
+ } else {
+ triple v=unit(point(axelevel,1)-point(axelevel,0));
+ triple zerolevel=dot(-point(axelevel,0),v)*v;
+ level=zerolevel+position*v;
+ }
+ this.axea=shift(level)*axea;
+ this.axeb=shift(level)*axeb;
+ bds=autoscale(min,max,t.scale);
+ locate=ticklocate(min,max,t,bds.min,bds.max,
+ Dir(point(axeb,0)-point(axea,0)));
+ }
+};
+
+typedef grid3 grid3routine(picture pic);
+
+triple X(picture pic) {return (pic.userMax().x,pic.userMin().y,pic.userMin().z);}
+triple XY(picture pic) {return (pic.userMax().x,pic.userMax().y,pic.userMin().z);}
+triple Y(picture pic) {return (pic.userMin().x,pic.userMax().y,pic.userMin().z);}
+triple YZ(picture pic) {return (pic.userMin().x,pic.userMax().y,pic.userMax().z);}
+triple Z(picture pic) {return (pic.userMin().x,pic.userMin().y,pic.userMax().z);}
+triple ZX(picture pic) {return (pic.userMax().x,pic.userMin().y,pic.userMax().z);}
+
+grid3routine XYgrid(position pos=Relative(0)) {
+ return new grid3(picture pic) {
+ grid3 og;
+ triple m=pic.userMin();
+ triple M=pic.userMax();
+ og.create(pic,m--X(pic),Y(pic)--XY(pic),m--Z(pic),
+ m.x,M.x,pos,pic.scale.x);
+ return og;
+ };
+};
+grid3routine XYgrid=XYgrid();
+
+grid3routine YXgrid(position pos=Relative(0)) {
+ return new grid3(picture pic) {
+ grid3 og;
+ triple m=pic.userMin();
+ triple M=pic.userMax();
+ og.create(pic,m--Y(pic),X(pic)--XY(pic),m--Z(pic),
+ m.y,M.y,pos,pic.scale.y);
+ return og;
+ };
+};
+grid3routine YXgrid=YXgrid();
+
+
+grid3routine XZgrid(position pos=Relative(0)) {
+ return new grid3(picture pic) {
+ grid3 og;
+ triple m=pic.userMin();
+ triple M=pic.userMax();
+ og.create(pic,m--X(pic),Z(pic)--ZX(pic),m--Y(pic),
+ m.x,M.x,pos,pic.scale.x);
+ return og;
+ };
+};
+grid3routine XZgrid=XZgrid();
+
+grid3routine ZXgrid(position pos=Relative(0)) {
+ return new grid3(picture pic) {
+ grid3 og;
+ triple m=pic.userMin();
+ triple M=pic.userMax();
+ og.create(pic,m--Z(pic),X(pic)--ZX(pic),m--Y(pic),
+ m.z,M.z,pos,pic.scale.z);
+ return og;
+ };
+};
+grid3routine ZXgrid=ZXgrid();
+
+grid3routine YZgrid(position pos=Relative(0)) {
+ return new grid3(picture pic) {
+ grid3 og;
+ triple m=pic.userMin();
+ triple M=pic.userMax();
+ og.create(pic,m--Y(pic),Z(pic)--YZ(pic),m--X(pic),
+ m.y,M.y,pos,pic.scale.y);
+ return og;
+ };
+};
+grid3routine YZgrid=YZgrid();
+
+grid3routine ZYgrid(position pos=Relative(0)) {
+ return new grid3(picture pic) {
+ grid3 og;
+ triple m=pic.userMin();
+ triple M=pic.userMax();
+ og.create(pic,m--Z(pic),Y(pic)--YZ(pic),m--X(pic),
+ m.z,M.z,pos,pic.scale.z);
+ return og;
+ };
+};
+grid3routine ZYgrid=ZYgrid();
+
+typedef grid3routine grid3routines[] ;
+
+grid3routines XYXgrid(position pos=Relative(0)) {
+ grid3routines ogs=new grid3routine[] {XYgrid(pos),YXgrid(pos)};
+ return ogs;
+};
+grid3routines XYXgrid=XYXgrid();
+grid3routines YXYgrid(position pos=Relative(0)) {return XYXgrid(pos);};
+grid3routines YXYgrid=XYXgrid();
+
+grid3routines ZXZgrid(position pos=Relative(0)) {
+ grid3routines ogs=new grid3routine[] {ZXgrid(pos),XZgrid(pos)};
+ return ogs;
+};
+grid3routines ZXZgrid=ZXZgrid();
+grid3routines XZXgrid(position pos=Relative(0)) {return ZXZgrid(pos);};
+grid3routines XZXgrid=XZXgrid();
+
+grid3routines ZYZgrid(position pos=Relative(0)) {
+ grid3routines ogs=new grid3routine[] {ZYgrid(pos),YZgrid(pos)};
+ return ogs;
+};
+grid3routines ZYZgrid=ZYZgrid();
+grid3routines YZYgrid(position pos=Relative(0)) {return ZYZgrid(pos);};
+grid3routines YZYgrid=YZYgrid();
+
+grid3routines XY_XZgrid(position posa=Relative(0), position posb=Relative(0)) {
+ grid3routines ogs=new grid3routine[] {XYgrid(posa),XZgrid(posb)};
+ return ogs;
+};
+grid3routines XY_XZgrid=XY_XZgrid();
+
+grid3routines YX_YZgrid(position posa=Relative(0), position posb=Relative(0)) {
+ grid3routines ogs=new grid3routine[] {YXgrid(posa),YZgrid(posb)};
+ return ogs;
+};
+grid3routines YX_YZgrid=YX_YZgrid();
+
+grid3routines ZX_ZYgrid(position posa=Relative(0), position posb=Relative(0)) {
+ grid3routines ogs=new grid3routine[] {ZXgrid(posa),ZYgrid(posb)};
+ return ogs;
+};
+grid3routines ZX_ZYgrid=ZX_ZYgrid();
+
+typedef grid3routines[] grid3routinetype;
+
+grid3routinetype XYZgrid(position pos=Relative(0))
+{
+ grid3routinetype ogs=new grid3routines[] {YZYgrid(pos),XYXgrid(pos),
+ XZXgrid(pos)};
+ return ogs;
+}
+grid3routinetype XYZgrid=XYZgrid();
+
+grid3routines operator cast(grid3routine gridroutine) {
+ grid3routines og=new grid3routine[] {gridroutine};
+ return og;
+}
+
+grid3routinetype operator cast(grid3routines gridroutine) {
+ grid3routinetype og=new grid3routines[] {gridroutine};
+ return og;
+}
+
+grid3routinetype operator cast(grid3routine gridroutine) {
+ grid3routinetype og=(grid3routinetype)(grid3routines) gridroutine;
+ return og;
+}
+
+void grid3(picture pic=currentpicture,
+ grid3routinetype gridroutine=XYZgrid,
+ int N=0, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true,
+ pen pGrid=grey, pen pgrid=lightgrey,
+ bool above=false)
+{
+ for(int j=0; j < gridroutine.length; ++j) {
+ grid3routines gridroutinej=gridroutine[j];
+ for(int i=0; i < gridroutinej.length; ++i) {
+ grid3 gt=gridroutinej[i](pic);
+ pic.add(new void(picture f, transform3 t, transform3 T, triple, triple) {
+ picture d;
+ ticks3 ticks=Ticks3(1,F="%",ticklabel=null,
+ beginlabel=false,endlabel=false,
+ N=N,n=n,Step=Step,step=step,
+ begin=begin,end=end,
+ Size=0,size=0,extend=true,
+ pTick=pGrid,ptick=pgrid);
+ ticks(d,t,"",gt.axea,gt.axeb,nullpen,None,NoMargin3,gt.locate,
+ gt.bds.divisor,opposite=true,primary=false);
+ add(f,t*T*inverse(t)*d);
+ },above=above);
+ addPath(pic,gt.axea,pGrid);
+ addPath(pic,gt.axeb,pGrid);
+ }
+ }
+}
+
+void grid3(picture pic=currentpicture,
+ grid3routinetype gridroutine,
+ int N=0, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true,
+ pen[] pGrid, pen[] pgrid,
+ bool above=false)
+{
+ if(pGrid.length != gridroutine.length || pgrid.length != gridroutine.length)
+ abort("pen array has different length than grid");
+ for(int i=0; i < gridroutine.length; ++i) {
+ grid3(pic=pic,gridroutine=gridroutine[i],
+ N=N,n=n,Step=Step,step=step,
+ begin=begin,end=end,
+ pGrid=pGrid[i],pgrid=pgrid[i],
+ above=above);
+ }
+}
+
+position top=Relative(1);
+position bottom=Relative(0);
+position middle=Relative(0.5);
+
+// Structure used to communicate ticks and axis settings to grid3 routines.
+struct ticksgridT {
+ ticks3 ticks;
+ // Other arguments of grid3 are define by ticks and axis settings
+ void grid3(picture, bool);
+};
+
+typedef ticksgridT ticksgrid();
+
+
+ticksgrid InOutTicks(Label F="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N=0, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true,
+ real Size=0, real size=0,
+ pen pTick=nullpen, pen ptick=nullpen,
+ grid3routinetype gridroutine,
+ pen pGrid=grey, pen pgrid=lightgrey)
+{
+ return new ticksgridT()
+ {
+ ticksgridT otg;
+ otg.ticks=Ticks3(0,F,ticklabel,beginlabel,endlabel,
+ N,n,Step,step,begin,end,
+ Size,size,false,pTick,ptick);
+ otg.grid3=new void(picture pic, bool above) {
+ grid3(pic,gridroutine,N,n,Step,step,begin,end,pGrid,pgrid,above);
+ };
+ return otg;
+ };
+}
+
+ticksgrid InTicks(Label F="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N=0, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true,
+ real Size=0, real size=0,
+ pen pTick=nullpen, pen ptick=nullpen,
+ grid3routinetype gridroutine,
+ pen pGrid=grey, pen pgrid=lightgrey)
+{
+ return new ticksgridT()
+ {
+ ticksgridT otg;
+ otg.ticks=Ticks3(-1,F,ticklabel,beginlabel,endlabel,N,n,Step,step,
+ begin,end,Size,size,false,pTick,ptick);
+ otg.grid3=new void(picture pic, bool above) {
+ grid3(pic,gridroutine,N,n,Step,step,begin,end,pGrid,pgrid,above);
+ };
+ return otg;
+ };
+}
+
+ticksgrid OutTicks(Label F="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N=0, int n=0, real Step=0, real step=0,
+ bool begin=true, bool end=true,
+ real Size=0, real size=0,
+ pen pTick=nullpen, pen ptick=nullpen,
+ grid3routinetype gridroutine,
+ pen pGrid=grey, pen pgrid=lightgrey)
+{
+ return new ticksgridT()
+ {
+ ticksgridT otg;
+ otg.ticks=Ticks3(1,F,ticklabel,beginlabel,endlabel,N,n,Step,step,
+ begin,end,Size,size,false,pTick,ptick);
+ otg.grid3=new void(picture pic, bool above) {
+ grid3(pic,gridroutine,N,n,Step,step,begin,end,pGrid,pgrid,above);
+ };
+ return otg;
+ };
+}
+
+void xaxis3(picture pic=currentpicture, Label L="", axis axis=YZZero,
+ pen p=currentpen, ticksgrid ticks,
+ arrowbar3 arrow=None, bool above=false)
+{
+ xaxis3(pic,L,axis,p,ticks().ticks,arrow,above);
+ ticks().grid3(pic,above);
+}
+
+void yaxis3(picture pic=currentpicture, Label L="", axis axis=XZZero,
+ pen p=currentpen, ticksgrid ticks,
+ arrowbar3 arrow=None, bool above=false)
+{
+ yaxis3(pic,L,axis,p,ticks().ticks,arrow,above);
+ ticks().grid3(pic,above);
+}
+
+void zaxis3(picture pic=currentpicture, Label L="", axis axis=XYZero,
+ pen p=currentpen, ticksgrid ticks,
+ arrowbar3 arrow=None, bool above=false)
+{
+ zaxis3(pic,L,axis,p,ticks().ticks,arrow,above);
+ ticks().grid3(pic,above);
+}
+
+/* Example:
+
+ import grid3;
+
+ size(8cm,0);
+ currentprojection=orthographic(0.5,1,0.5);
+
+ defaultpen(overwrite(SuppressQuiet));
+
+ scale(Linear, Linear, Log);
+
+ grid3(pic=currentpicture, // picture
+ gridroutine=XYZgrid(// grid3routine
+ // or grid3routine[] (alias grid3routines)
+ // or grid3routines[]:
+ // The routine(s) to draw the grid(s):
+ // *XYgrid: draw grid from X in direction of Y
+ // *YXgrid: draw grid from Y in direction of X, ...
+ // *An array of previous values XYgrid, YXgrid, ...
+ // *XYXgrid: draw XYgrid and YXgrid grids
+ // *YXYgrid: draw XYgrid and YXgrid grids
+ // *ZXZgrid: draw ZXgrid and XZgrid grids
+ // *YX_YZgrid: draw YXgrid and YZgrid grids
+ // *XY_XZgrid: draw XYgrid and XZgrid grids
+ // *YX_YZgrid: draw YXgrid and YZgrid grids
+ // *An array of previous values XYXgrid,...
+ // *XYZgrid: draw XYXgrid, ZYZgrid, XZXgrid grids.
+ pos=Relative(0)),
+ // the position of the grid relative to the axis
+ // perpendicular to the grid; a real number
+ // specifies a coordinate relative to this axis.
+ // Aliases: top=Relative(1), middle=Relative(0.5)
+ // and bottom=Relative(0).
+
+ // These arguments are similar to those of InOutTicks():
+ N=0, // int
+ n=0, // int
+ Step=0, // real
+ step=0, // real
+ begin=true, // bool
+ end=true, // bool
+ pGrid=grey, // pen
+ pgrid=lightgrey, // pen
+ above=false // bool
+ );
+
+ xaxis3(Label("$x$",position=EndPoint,align=S),OutTicks());
+ yaxis3(Label("$y$",position=EndPoint,align=S),OutTicks());
+ zaxis3(Label("$z$",position=EndPoint,align=(0,0.5)+W),OutTicks());
+*/
+
+/* Other examples:
+
+ int N=10, n=2;
+ xaxis3(Label("$x$",position=EndPoint,align=S),OutTicks());
+ yaxis3(Label("$y$",position=EndPoint,align=S),OutTicks(N=N,n=n));
+ zaxis3(Label("$z$",position=EndPoint,align=(0,0.5)+W),OutTicks());
+ grid3(N=N,n=n);
+
+ xaxis3(Label("$x$",position=EndPoint,align=S),OutTicks());
+ yaxis3(Label("$y$",position=EndPoint,align=S),OutTicks());
+ zaxis3(Label("$z$",position=EndPoint,align=(0,0.5)+W),OutTicks());
+ grid3(new grid3routines[] {XYXgrid(top),XZXgrid(0)});
+
+ xaxis3(Label("$x$",position=EndPoint,align=S),OutTicks());
+ yaxis3(Label("$y$",position=EndPoint,align=S),OutTicks());
+ zaxis3(Label("$z$",position=EndPoint,align=(0,0.5)+W),OutTicks());
+ grid3(new grid3routines[] {XYXgrid(-0.5),XYXgrid(1.5)},
+ pGrid=new pen[] {red,blue},
+ pgrid=new pen[] {0.5red,0.5blue});
+
+ // Axes with grids:
+
+ xaxis3(Label("$x$",position=EndPoint,align=S),
+ OutTicks(Step=0.5,gridroutine=XYgrid));
+ yaxis3(Label("$y$",position=EndPoint,align=S),
+ InOutTicks(Label("",align=0.5X),N=8,n=2,gridroutine=YX_YZgrid));
+ zaxis3("$z$",OutTicks(ZYgrid));
+*/
diff --git a/graphics/asymptote/base/interpolate.asy b/graphics/asymptote/base/interpolate.asy
new file mode 100644
index 0000000000..a600c94c12
--- /dev/null
+++ b/graphics/asymptote/base/interpolate.asy
@@ -0,0 +1,140 @@
+// Lagrange and Hermite interpolation in Asymptote
+// Author: Olivier Guibé
+// Acknowledgements: Philippe Ivaldi
+
+// diffdiv(x,y) computes Newton's Divided Difference for
+// Lagrange interpolation with distinct values {x_0,..,x_n} in the array x
+// and values y_0,...,y_n in the array y,
+
+// hdiffdiv(x,y,dyp) computes Newton's Divided Difference for
+// Hermite interpolation where dyp={dy_0,...,dy_n}.
+//
+// fhorner(x,coeff) uses Horner's rule to compute the polynomial
+// a_0+a_1(x-x_0)+a_2(x-x_0)(x-x_1)+...+a_n(x-x_0)..(x-x_{n-1}),
+// where coeff={a_0,a_1,...,a_n}.
+
+// fspline does standard cubic spline interpolation of a function f
+// on the interval [a,b].
+// The points a=x_1 < x_2 < .. < x_n=b form the array x;
+// the points y_1=f(x_1),....,y_n=f(x_n) form the array y
+// We use the Hermite form for the spline.
+
+// The syntax is:
+// s=fspline(x,y); default not_a_knot condition
+// s=fspline(x,y,natural); natural spline
+// s=fspline(x,y,periodic); periodic spline
+// s=fspline(x,y,clamped(1,1)); clamped spline
+// s=fspline(x,y,monotonic); piecewise monotonic spline
+
+// Here s is a real function that is constant on (-infinity,a] and [b,infinity).
+
+private import math;
+import graph_splinetype;
+
+typedef real fhorner(real);
+
+struct horner {
+ // x={x0,..,xn}(not necessarily distinct)
+ // a={a0,..,an} corresponds to the polyonmial
+ // a_0+a_1(x-x_0)+a_2(x-x_0)(x-x_1)+...+a_n(x-x_0)..(x-x_{n-1}),
+ real[] x;
+ real[] a;
+}
+
+// Evaluate p(x)=d0+(x-x0)(d1+(x-x1)+...+(d(n-1)+(x-x(n-1))*dn)))
+// via Horner's rule: n-1 multiplications, 2n-2 additions.
+fhorner fhorner(horner sh)
+{
+ int n=sh.x.length;
+ checklengths(n,sh.a.length);
+ return new real(real x) {
+ real s=sh.a[n-1];
+ for(int k=n-2; k >= 0; --k)
+ s=sh.a[k]+(x-sh.x[k])*s;
+ return s;
+ };
+}
+
+// Newton's Divided Difference method: n(n-1)/2 divisions, n(n-1) additions.
+horner diffdiv(real[] x, real[] y)
+{
+ int n=x.length;
+ horner s;
+ checklengths(n,y.length);
+ for(int i=0; i < n; ++i)
+ s.a[i]=y[i];
+ for(int k=0; k < n-1; ++k) {
+ for(int i=n-1; i > k; --i) {
+ s.a[i]=(s.a[i]-s.a[i-1])/(x[i]-x[i-k-1]);
+ }
+ }
+ s.x=x;
+ return s;
+}
+
+// Newton's Divided Difference for simple Hermite interpolation,
+// where one specifies both p(x_i) and p'(x_i).
+horner hdiffdiv(real[] x, real[] y, real[] dy)
+{
+ int n=x.length;
+ horner s;
+ checklengths(n,y.length);
+ checklengths(n,dy.length);
+ for(int i=0; i < n; ++i) {
+ s.a[2*i]=y[i];
+ s.a[2*i+1]=dy[i];
+ s.x[2*i]=x[i];
+ s.x[2*i+1]=x[i];
+ }
+
+ for(int i=n-1; i > 0; --i)
+ s.a[2*i]=(s.a[2*i]-s.a[2*i-2])/(x[i]-x[i-1]);
+
+ int stop=2*n-1;
+ for(int k=1; k < stop; ++k) {
+ for(int i=stop; i > k; --i) {
+ s.a[i]=(s.a[i]-s.a[i-1])/(s.x[i]-s.x[i-k-1]);
+ }
+ }
+ return s;
+}
+
+typedef real realfunction(real);
+
+// piecewise Hermite interpolation:
+// return the piecewise polynomial p(x), where on [x_i,x_i+1], deg(p) <= 3,
+// p(x_i)=y_i, p(x_{i+1})=y_i+1, p'(x_i)=dy_i, and p'(x_{i+1})=dy_i+1.
+// Outside [x_1,x_n] the returned function is constant: y_1 on (infinity,x_1]
+// and y_n on [x_n,infinity).
+realfunction pwhermite(real[] x, real[] y, real[] dy)
+{
+ int n=x.length;
+ checklengths(n,y.length);
+ checklengths(n,dy.length);
+ if(n < 2) abort(morepoints);
+ if(!increasing(x,strict=true)) abort("array x is not strictly increasing");
+ return new real(real t) {
+ int i=search(x,t);
+ if(i == n-1) {
+ i=n-2;
+ t=x[n-1];
+ } else if(i == -1) {
+ i=0;
+ t=x[0];
+ }
+ real h=x[i+1]-x[i];
+ real delta=(y[i+1]-y[i])/h;
+ real e=(3*delta-2*dy[i]-dy[i+1])/h;
+ real f=(dy[i]-2*delta+dy[i+1])/h^2;
+ real s=t-x[i];
+ return y[i]+s*(dy[i]+s*(e+s*f));
+ };
+}
+
+realfunction fspline(real[] x, real[] y, splinetype splinetype=notaknot)
+{
+ real[] dy=splinetype(x,y);
+ return new real(real t) {
+ return pwhermite(x,y,dy)(t);
+ };
+}
diff --git a/graphics/asymptote/base/labelpath.asy b/graphics/asymptote/base/labelpath.asy
new file mode 100644
index 0000000000..39c1908efd
--- /dev/null
+++ b/graphics/asymptote/base/labelpath.asy
@@ -0,0 +1,28 @@
+usepackage("pstricks");
+usepackage("pst-text");
+
+string LeftJustified="l";
+string RightJustified="r";
+string Centered="c";
+
+void labelpath(frame f, Label L, path g, string justify=Centered,
+ pen p=currentpen)
+{
+ if(latex() && !pdf()) {
+ _labelpath(f,L.s,L.size,g,justify,(L.T.x,L.T.y+0.5linewidth(p)),p);
+ return;
+ }
+ warning("labelpathlatex","labelpath requires -tex latex");
+}
+
+void labelpath(picture pic=currentpicture, Label L, path g,
+ string justify=Centered, pen p=currentpen)
+{
+ pic.add(new void(frame f, transform t) {
+ labelpath(f,L,t*g,justify,p);
+ });
+ frame f;
+ label(f,Label(L.s,L.size));
+ real w=size(f).y+L.T.y+0.5linewidth(p);
+ pic.addBox(min(g),max(g),-w,w);
+}
diff --git a/graphics/asymptote/base/labelpath3.asy b/graphics/asymptote/base/labelpath3.asy
new file mode 100644
index 0000000000..63b2290f1f
--- /dev/null
+++ b/graphics/asymptote/base/labelpath3.asy
@@ -0,0 +1,83 @@
+// Fit a label to a path3.
+// Author: Jens Schwaiger
+
+import three;
+private real eps=100*realEpsilon;
+
+triple nextnormal(triple p, triple q)
+{
+ triple nw=p-(dot(p,q)*q);
+ return abs(nw) < 0.0001 ? p : unit(nw);
+}
+
+triple[] firstframe(path3 p, triple optional=O)
+{
+ triple[] start=new triple[3];
+ start[0]=dir(p,reltime(p,0));
+ start[1]=(abs(cross(start[0],optional)) < eps) ? perp(start[0]) :
+ unit(cross(start[0],optional));
+ start[2]=cross(start[0],start[1]);
+ return start;
+}
+
+// Modification of the bishop frame construction contained in
+// space_tube.asy (from Philippe Ivaldi's modules).
+// For noncyclic path3s only
+triple[] nextframe(path3 p, real reltimestart, triple[] start, real
+ reltimeend, int subdiv=20)
+{
+ triple[][] bf=new triple[subdiv+1][3];
+ real lg=reltimeend-reltimestart;
+ if(lg <= 0) return start;
+ bf[0]=start;
+ int n=subdiv+1;
+ for(int i=1; i < n; ++i)
+ bf[i][0]=dir(p,reltime(p,reltimestart+(i/subdiv)*lg));
+
+ for(int i=1; i < n; ++i) {
+ bf[i][1]=nextnormal(bf[i-1][1],bf[i][0]);
+ bf[i][2]=cross(bf[i][0],bf[i][1]);
+ }
+ return bf[subdiv];
+}
+
+surface labelpath(string s, path3 p, real angle=90, triple optional=O)
+{
+ real Cos=Cos(angle);
+ real Sin=Sin(angle);
+ path[] text=texpath(Label(s,(0,0),Align,basealign));
+ text=scale(1/(max(text).x-min(text).x))*text;
+ path[][] decompose=containmentTree(text);
+
+ real[][] xpos=new real[decompose.length][2];
+ surface sf;
+ for(int i=0; i < decompose.length; ++i) {// Identify positions along x-axis
+ xpos[i][1]=i;
+ real pos0=0.5(max(decompose[i]).x+min(decompose[i]).x);
+ xpos[i][0]=pos0;
+ }
+ xpos=sort(xpos); // sort by distance from 0;
+ triple[] pos=new triple[decompose.length];
+ real lg=arclength(p);
+ //create frames;
+ triple[] first=firstframe(p,optional);
+ triple[] t0=first;
+ real tm0=0;
+ triple[][] bfr=new triple[decompose.length][3];
+ for(int j=0; j < decompose.length; ++j) {
+ bfr[j]=nextframe(p,tm0,t0,xpos[j][0]);
+ tm0=xpos[j][0]; t0=bfr[j];
+ }
+ transform3[] mt=new transform3[bfr.length];
+ for(int j=0; j < bfr.length; ++j) {
+ triple f2=Cos*bfr[j][1]+Sin*bfr[j][2];
+ triple f3=Sin*bfr[j][1]+Cos*bfr[j][2];
+ mt[j]=shift(relpoint(p,xpos[j][0]))*transform3(bfr[j][0],f2,f3);
+ }
+ for(int j=0; j < bfr.length; ++j) {
+ path[] dc=decompose[(int) xpos[j][1]];
+ pair pos0=(0.5(max(dc).x+min(dc).x),0);
+ sf.append(mt[j]*surface(scale(lg)*shift(-pos0)*dc));
+ }
+ return sf;
+}
diff --git a/graphics/asymptote/base/latin1.asy b/graphics/asymptote/base/latin1.asy
new file mode 100644
index 0000000000..3d76925be2
--- /dev/null
+++ b/graphics/asymptote/base/latin1.asy
@@ -0,0 +1,2 @@
+usepackage("fontenc","T1");
+usepackage("inputenc","latin1");
diff --git a/graphics/asymptote/base/lmfit.asy b/graphics/asymptote/base/lmfit.asy
new file mode 100644
index 0000000000..c3dfddce06
--- /dev/null
+++ b/graphics/asymptote/base/lmfit.asy
@@ -0,0 +1,881 @@
+/*
+ Copyright (c) 2009 Philipp Stephani
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+/*
+ Fitting $n$ data points $(x_1, y_1 \pm \Delta y_1), \dots, (x_n, y_n \pm \Delta y_n)$
+ to a function $f$ that depends on $m$ parameters $a_1, \dots, a_m$ means minimizing
+ the least-squares sum
+ %
+ \begin{equation*}
+ \sum_{i = 1}^n \left( \frac{y_i - f(a_1, \dots, a_m; x_i)}{\Delta y_i} \right)^2
+ \end{equation*}
+ %
+ with respect to the parameters $a_1, \dots, a_m$.
+*/
+
+/*
+ This module provides an implementation of the Levenberg--Marquardt
+ (LM) algorithm, converted from the C lmfit routine by Joachim Wuttke
+ (see http://www.messen-und-deuten.de/lmfit/).
+
+ Implementation strategy: Fortunately, Asymptote's syntax is very
+ similar to C, and the original code cleanly separates the
+ customizable parts (user-provided data, output routines, etc.) from
+ the dirty number crunching. Thus, mst of the code was just copied
+ and slightly modified from the original source files. I have
+ amended the lm_data_type structure and the callback routines with a
+ weight array that can be used to provide experimental errors. I
+ have also created two simple wrapper functions.
+*/
+
+
+// copied from the C code
+private real LM_MACHEP = realEpsilon;
+private real LM_DWARF = realMin;
+private real LM_SQRT_DWARF = sqrt(realMin);
+private real LM_SQRT_GIANT = sqrt(realMax);
+private real LM_USERTOL = 30 * LM_MACHEP;
+
+restricted string lm_infmsg[] = {
+ "improper input parameters",
+ "the relative error in the sum of squares is at most tol",
+ "the relative error between x and the solution is at most tol",
+ "both errors are at most tol",
+ "fvec is orthogonal to the columns of the jacobian to machine precision",
+ "number of calls to fcn has reached or exceeded maxcall*(n+1)",
+ "ftol is too small: no further reduction in the sum of squares is possible",
+ "xtol too small: no further improvement in approximate solution x possible",
+ "gtol too small: no further improvement in approximate solution x possible",
+ "not enough memory",
+ "break requested within function evaluation"
+};
+
+restricted string lm_shortmsg[] = {
+ "invalid input",
+ "success (f)",
+ "success (p)",
+ "success (f,p)",
+ "degenerate",
+ "call limit",
+ "failed (f)",
+ "failed (p)",
+ "failed (o)",
+ "no memory",
+ "user break"
+};
+
+
+// copied from the C code and amended with the weight (user_w) array
+struct lm_data_type {
+ real[] user_t;
+ real[] user_y;
+ real[] user_w;
+ real user_func(real user_t_point, real[] par);
+};
+
+
+// Asymptote has no pointer support, so we need reference wrappers for
+// the int and real types
+struct lm_int_type {
+ int val;
+
+ void operator init(int val) {
+ this.val = val;
+ }
+};
+
+
+struct lm_real_type {
+ real val;
+
+ void operator init(real val) {
+ this.val = val;
+ }
+};
+
+
+// copied from the C code; the lm_initialize_control function turned
+// into a constructor
+struct lm_control_type {
+ real ftol;
+ real xtol;
+ real gtol;
+ real epsilon;
+ real stepbound;
+ real fnorm;
+ int maxcall;
+ lm_int_type nfev;
+ lm_int_type info;
+
+ void operator init() {
+ maxcall = 100;
+ epsilon = LM_USERTOL;
+ stepbound = 100;
+ ftol = LM_USERTOL;
+ xtol = LM_USERTOL;
+ gtol = LM_USERTOL;
+ }
+};
+
+
+// copied from the C code
+typedef void lm_evaluate_ftype(real[] par, int m_dat, real[] fvec, lm_data_type data, lm_int_type info);
+typedef void lm_print_ftype(int n_par, real[] par, int m_dat, real[] fvec, lm_data_type data, int iflag, int iter, int nfev);
+
+
+// copied from the C code
+private real SQR(real x) {
+ return x * x;
+}
+
+
+// Asymptote doesn't support pointers to arbitrary array elements, so
+// we provide an offset parameter.
+private real lm_enorm(int n, real[] x, int offset=0) {
+ real s1 = 0;
+ real s2 = 0;
+ real s3 = 0;
+ real x1max = 0;
+ real x3max = 0;
+ real agiant = LM_SQRT_GIANT / n;
+ real xabs, temp;
+
+ for (int i = 0; i < n; ++i) {
+ xabs = fabs(x[offset + i]);
+ if (xabs > LM_SQRT_DWARF && xabs < agiant) {
+ s2 += SQR(xabs);
+ continue;
+ }
+
+ if (xabs > LM_SQRT_DWARF) {
+ if (xabs > x1max) {
+ temp = x1max / xabs;
+ s1 = 1 + s1 * SQR(temp);
+ x1max = xabs;
+ } else {
+ temp = xabs / x1max;
+ s1 += SQR(temp);
+ }
+ continue;
+ }
+ if (xabs > x3max) {
+ temp = x3max / xabs;
+ s3 = 1 + s3 * SQR(temp);
+ x3max = xabs;
+ } else {
+ if (xabs != 0.0) {
+ temp = xabs / x3max;
+ s3 += SQR(temp);
+ }
+ }
+ }
+
+ if (s1 != 0)
+ return x1max * sqrt(s1 + (s2 / x1max) / x1max);
+ if (s2 != 0) {
+ if (s2 >= x3max)
+ return sqrt(s2 * (1 + (x3max / s2) * (x3max * s3)));
+ else
+ return sqrt(x3max * ((s2 / x3max) + (x3max * s3)));
+ }
+
+ return x3max * sqrt(s3);
+}
+
+
+// This function calculated the vector whose square sum is to be
+// minimized. We use a slight modification of the original code that
+// includes the weight factor. The user may provide different
+// customizations.
+void lm_evaluate_default(real[] par, int m_dat, real[] fvec, lm_data_type data, lm_int_type info) {
+ for (int i = 0; i < m_dat; ++i) {
+ fvec[i] = data.user_w[i] * (data.user_y[i] - data.user_func(data.user_t[i], par));
+ }
+}
+
+
+// Helper functions to print padded strings and numbers (until
+// Asymptote provides a real printf function)
+private string pad(string str, int count, string pad=" ") {
+ string res = str;
+ while (length(res) < count)
+ res = pad + res;
+ return res;
+}
+
+
+private string pad(int num, int digits, string pad=" ") {
+ return pad(string(num), digits, pad);
+}
+
+
+private string pad(real num, int digits, string pad=" ") {
+ return pad(string(num), digits, pad);
+}
+
+
+// Similar to the C code, also prints weights
+void lm_print_default(int n_par, real[] par, int m_dat, real[] fvec, lm_data_type data, int iflag, int iter, int nfev) {
+ real f, y, t, w;
+ int i;
+
+ if (iflag == 2) {
+ write("trying step in gradient direction");
+ } else if (iflag == 1) {
+ write(format("determining gradient (iteration %d)", iter));
+ } else if (iflag == 0) {
+ write("starting minimization");
+ } else if (iflag == -1) {
+ write(format("terminated after %d evaluations", nfev));
+ }
+
+ write(" par: ", none);
+ for (i = 0; i < n_par; ++i) {
+ write(" " + pad(par[i], 12), none);
+ }
+ write(" => norm: " + pad(lm_enorm(m_dat, fvec), 12));
+
+ if (iflag == -1) {
+ write(" fitting data as follows:");
+ for (i = 0; i < m_dat; ++i) {
+ t = data.user_t[i];
+ y = data.user_y[i];
+ w = data.user_w[i];
+ f = data.user_func(t, par);
+ write(format(" t[%2d]=", i) + pad(t, 12) + " y=" + pad(y, 12) + " w=" + pad(w, 12) + " fit=" + pad(f, 12) + " residue=" + pad(y - f, 12));
+ }
+ }
+}
+
+
+// Prints nothing
+void lm_print_quiet(int n_par, real[] par, int m_dat, real[] fvec, lm_data_type data, int iflag, int iter, int nfev) {
+}
+
+
+// copied from the C code
+private void lm_qrfac(int m, int n, real[] a, bool pivot, int[] ipvt, real[] rdiag, real[] acnorm, real[] wa) {
+ int i, j, k, kmax, minmn;
+ real ajnorm, sum, temp;
+ static real p05 = 0.05;
+
+ for (j = 0; j < n; ++j) {
+ acnorm[j] = lm_enorm(m, a, j * m);
+ rdiag[j] = acnorm[j];
+ wa[j] = rdiag[j];
+ if (pivot)
+ ipvt[j] = j;
+ }
+
+ minmn = min(m, n);
+ for (j = 0; j < minmn; ++j) {
+ while (pivot) {
+ kmax = j;
+ for (k = j + 1; k < n; ++k)
+ if (rdiag[k] > rdiag[kmax])
+ kmax = k;
+ if (kmax == j)
+ break;
+
+ for (i = 0; i < m; ++i) {
+ temp = a[j * m + i];
+ a[j * m + i] = a[kmax * m + i];
+ a[kmax * m + i] = temp;
+ }
+ rdiag[kmax] = rdiag[j];
+ wa[kmax] = wa[j];
+ k = ipvt[j];
+ ipvt[j] = ipvt[kmax];
+ ipvt[kmax] = k;
+
+ break;
+ }
+
+ ajnorm = lm_enorm(m - j, a, j * m + j);
+ if (ajnorm == 0.0) {
+ rdiag[j] = 0;
+ continue;
+ }
+
+ if (a[j * m + j] < 0.0)
+ ajnorm = -ajnorm;
+ for (i = j; i < m; ++i)
+ a[j * m + i] /= ajnorm;
+ a[j * m + j] += 1;
+
+ for (k = j + 1; k < n; ++k) {
+ sum = 0;
+
+ for (i = j; i < m; ++i)
+ sum += a[j * m + i] * a[k * m + i];
+
+ temp = sum / a[j + m * j];
+
+ for (i = j; i < m; ++i)
+ a[k * m + i] -= temp * a[j * m + i];
+
+ if (pivot && rdiag[k] != 0.0) {
+ temp = a[m * k + j] / rdiag[k];
+ temp = max(0.0, 1 - SQR(temp));
+ rdiag[k] *= sqrt(temp);
+ temp = rdiag[k] / wa[k];
+ if (p05 * SQR(temp) <= LM_MACHEP) {
+ rdiag[k] = lm_enorm(m - j - 1, a, m * k + j + 1);
+ wa[k] = rdiag[k];
+ }
+ }
+ }
+
+ rdiag[j] = -ajnorm;
+ }
+}
+
+
+// copied from the C code
+private void lm_qrsolv(int n, real[] r, int ldr, int[] ipvt, real[] diag, real[] qtb, real[] x, real[] sdiag, real[] wa) {
+ static real p25 = 0.25;
+ static real p5 = 0.5;
+
+ int i, kk, j, k, nsing;
+ real qtbpj, sum, temp;
+ real _sin, _cos, _tan, _cot;
+
+ for (j = 0; j < n; ++j) {
+ for (i = j; i < n; ++i)
+ r[j * ldr + i] = r[i * ldr + j];
+ x[j] = r[j * ldr + j];
+ wa[j] = qtb[j];
+ }
+
+ for (j = 0; j < n; ++j) {
+ while (diag[ipvt[j]] != 0.0) {
+ for (k = j; k < n; ++k)
+ sdiag[k] = 0.0;
+ sdiag[j] = diag[ipvt[j]];
+
+ qtbpj = 0.;
+ for (k = j; k < n; ++k) {
+ if (sdiag[k] == 0.)
+ continue;
+ kk = k + ldr * k;
+ if (fabs(r[kk]) < fabs(sdiag[k])) {
+ _cot = r[kk] / sdiag[k];
+ _sin = p5 / sqrt(p25 + p25 * _cot * _cot);
+ _cos = _sin * _cot;
+ } else {
+ _tan = sdiag[k] / r[kk];
+ _cos = p5 / sqrt(p25 + p25 * _tan * _tan);
+ _sin = _cos * _tan;
+ }
+
+ r[kk] = _cos * r[kk] + _sin * sdiag[k];
+ temp = _cos * wa[k] + _sin * qtbpj;
+ qtbpj = -_sin * wa[k] + _cos * qtbpj;
+ wa[k] = temp;
+
+ for (i = k + 1; i < n; ++i) {
+ temp = _cos * r[k * ldr + i] + _sin * sdiag[i];
+ sdiag[i] = -_sin * r[k * ldr + i] + _cos * sdiag[i];
+ r[k * ldr + i] = temp;
+ }
+ }
+ break;
+ }
+
+ sdiag[j] = r[j * ldr + j];
+ r[j * ldr + j] = x[j];
+ }
+
+ nsing = n;
+ for (j = 0; j < n; ++j) {
+ if (sdiag[j] == 0.0 && nsing == n)
+ nsing = j;
+ if (nsing < n)
+ wa[j] = 0;
+ }
+
+ for (j = nsing - 1; j >= 0; --j) {
+ sum = 0;
+ for (i = j + 1; i < nsing; ++i)
+ sum += r[j * ldr + i] * wa[i];
+ wa[j] = (wa[j] - sum) / sdiag[j];
+ }
+
+ for (j = 0; j < n; ++j)
+ x[ipvt[j]] = wa[j];
+}
+
+
+// copied from the C code
+private void lm_lmpar(int n, real[] r, int ldr, int[] ipvt, real[] diag, real[] qtb, real delta, lm_real_type par, real[] x, real[] sdiag, real[] wa1, real[] wa2) {
+ static real p1 = 0.1;
+ static real p001 = 0.001;
+
+ int nsing = n;
+ real parl = 0.0;
+
+ int i, iter, j;
+ real dxnorm, fp, fp_old, gnorm, parc, paru;
+ real sum, temp;
+
+ for (j = 0; j < n; ++j) {
+ wa1[j] = qtb[j];
+ if (r[j * ldr + j] == 0 && nsing == n)
+ nsing = j;
+ if (nsing < n)
+ wa1[j] = 0;
+ }
+ for (j = nsing - 1; j >= 0; --j) {
+ wa1[j] = wa1[j] / r[j + ldr * j];
+ temp = wa1[j];
+ for (i = 0; i < j; ++i)
+ wa1[i] -= r[j * ldr + i] * temp;
+ }
+
+ for (j = 0; j < n; ++j)
+ x[ipvt[j]] = wa1[j];
+
+ iter = 0;
+ for (j = 0; j < n; ++j)
+ wa2[j] = diag[j] * x[j];
+ dxnorm = lm_enorm(n, wa2);
+ fp = dxnorm - delta;
+ if (fp <= p1 * delta) {
+ par.val = 0;
+ return;
+ }
+
+ if (nsing >= n) {
+ for (j = 0; j < n; ++j)
+ wa1[j] = diag[ipvt[j]] * wa2[ipvt[j]] / dxnorm;
+
+ for (j = 0; j < n; ++j) {
+ sum = 0.0;
+ for (i = 0; i < j; ++i)
+ sum += r[j * ldr + i] * wa1[i];
+ wa1[j] = (wa1[j] - sum) / r[j + ldr * j];
+ }
+ temp = lm_enorm(n, wa1);
+ parl = fp / delta / temp / temp;
+ }
+
+ for (j = 0; j < n; ++j) {
+ sum = 0;
+ for (i = 0; i <= j; ++i)
+ sum += r[j * ldr + i] * qtb[i];
+ wa1[j] = sum / diag[ipvt[j]];
+ }
+ gnorm = lm_enorm(n, wa1);
+ paru = gnorm / delta;
+ if (paru == 0.0)
+ paru = LM_DWARF / min(delta, p1);
+
+ par.val = max(par.val, parl);
+ par.val = min(par.val, paru);
+ if (par.val == 0.0)
+ par.val = gnorm / dxnorm;
+
+ for (;; ++iter) {
+ if (par.val == 0.0)
+ par.val = max(LM_DWARF, p001 * paru);
+ temp = sqrt(par.val);
+ for (j = 0; j < n; ++j)
+ wa1[j] = temp * diag[j];
+ lm_qrsolv(n, r, ldr, ipvt, wa1, qtb, x, sdiag, wa2);
+ for (j = 0; j < n; ++j)
+ wa2[j] = diag[j] * x[j];
+ dxnorm = lm_enorm(n, wa2);
+ fp_old = fp;
+ fp = dxnorm - delta;
+
+ if (fabs(fp) <= p1 * delta || (parl == 0.0 && fp <= fp_old && fp_old < 0.0) || iter == 10)
+ break;
+
+ for (j = 0; j < n; ++j)
+ wa1[j] = diag[ipvt[j]] * wa2[ipvt[j]] / dxnorm;
+
+ for (j = 0; j < n; ++j) {
+ wa1[j] = wa1[j] / sdiag[j];
+ for (i = j + 1; i < n; ++i)
+ wa1[i] -= r[j * ldr + i] * wa1[j];
+ }
+ temp = lm_enorm(n, wa1);
+ parc = fp / delta / temp / temp;
+
+ if (fp > 0)
+ parl = max(parl, par.val);
+ else if (fp < 0)
+ paru = min(paru, par.val);
+
+ par.val = max(parl, par.val + parc);
+ }
+}
+
+
+// copied from the C code; the main function
+void lm_lmdif(int m, int n, real[] x, real[] fvec, real ftol, real xtol, real gtol, int maxfev, real epsfcn, real[] diag, int mode, real factor, lm_int_type info, lm_int_type nfev, real[] fjac, int[] ipvt, real[] qtf, real[] wa1, real[] wa2, real[] wa3, real[] wa4, lm_evaluate_ftype evaluate, lm_print_ftype printout, lm_data_type data) {
+ static real p1 = 0.1;
+ static real p5 = 0.5;
+ static real p25 = 0.25;
+ static real p75 = 0.75;
+ static real p0001 = 1.0e-4;
+
+ nfev.val = 0;
+ int iter = 1;
+ lm_real_type par = lm_real_type(0);
+ real delta = 0;
+ real xnorm = 0;
+ real temp = max(epsfcn, LM_MACHEP);
+ real eps = sqrt(temp);
+ int i, j;
+ real actred, dirder, fnorm, fnorm1, gnorm, pnorm, prered, ratio, step, sum, temp1, temp2, temp3;
+
+ if ((n <= 0) || (m < n) || (ftol < 0.0) || (xtol < 0.0) || (gtol < 0.0) || (maxfev <= 0) || (factor <= 0)) {
+ info.val = 0;
+ return;
+ }
+ if (mode == 2) {
+ for (j = 0; j < n; ++j) {
+ if (diag[j] <= 0.0) {
+ info.val = 0;
+ return;
+ }
+ }
+ }
+
+ info.val = 0;
+ evaluate(x, m, fvec, data, info);
+ if(printout != null) printout(n, x, m, fvec, data, 0, 0, ++nfev.val);
+ if (info.val < 0)
+ return;
+ fnorm = lm_enorm(m, fvec);
+
+ do {
+ for (j = 0; j < n; ++j) {
+ temp = x[j];
+ step = eps * fabs(temp);
+ if (step == 0.0)
+ step = eps;
+ x[j] = temp + step;
+ info.val = 0;
+ evaluate(x, m, wa4, data, info);
+ if(printout != null) printout(n, x, m, wa4, data, 1, iter, ++nfev.val);
+ if (info.val < 0)
+ return;
+ for (i = 0; i < m; ++i)
+ fjac[j * m + i] = (wa4[i] - fvec[i]) / (x[j] - temp);
+ x[j] = temp;
+ }
+
+ lm_qrfac(m, n, fjac, true, ipvt, wa1, wa2, wa3);
+
+ if (iter == 1) {
+ if (mode != 2) {
+ for (j = 0; j < n; ++j) {
+ diag[j] = wa2[j];
+ if (wa2[j] == 0.0)
+ diag[j] = 1.0;
+ }
+ }
+ for (j = 0; j < n; ++j)
+ wa3[j] = diag[j] * x[j];
+ xnorm = lm_enorm(n, wa3);
+ delta = factor * xnorm;
+ if (delta == 0.0)
+ delta = factor;
+ }
+
+ for (i = 0; i < m; ++i)
+ wa4[i] = fvec[i];
+
+ for (j = 0; j < n; ++j) {
+ temp3 = fjac[j * m + j];
+ if (temp3 != 0.0) {
+ sum = 0;
+ for (i = j; i < m; ++i)
+ sum += fjac[j * m + i] * wa4[i];
+ temp = -sum / temp3;
+ for (i = j; i < m; ++i)
+ wa4[i] += fjac[j * m + i] * temp;
+ }
+ fjac[j * m + j] = wa1[j];
+ qtf[j] = wa4[j];
+ }
+
+ gnorm = 0;
+ if (fnorm != 0) {
+ for (j = 0; j < n; ++j) {
+ if (wa2[ipvt[j]] == 0) continue;
+ sum = 0.0;
+ for (i = 0; i <= j; ++i)
+ sum += fjac[j * m + i] * qtf[i] / fnorm;
+ gnorm = max(gnorm, fabs(sum / wa2[ipvt[j]]));
+ }
+ }
+
+ if (gnorm <= gtol) {
+ info.val = 4;
+ return;
+ }
+
+ if (mode != 2) {
+ for (j = 0; j < n; ++j)
+ diag[j] = max(diag[j], wa2[j]);
+ }
+
+ do {
+ lm_lmpar(n, fjac, m, ipvt, diag, qtf, delta, par, wa1, wa2, wa3, wa4);
+
+ for (j = 0; j < n; ++j) {
+ wa1[j] = -wa1[j];
+ wa2[j] = x[j] + wa1[j];
+ wa3[j] = diag[j] * wa1[j];
+ }
+ pnorm = lm_enorm(n, wa3);
+
+ if (nfev.val <= 1 + n)
+ delta = min(delta, pnorm);
+
+ info.val = 0;
+ evaluate(wa2, m, wa4, data, info);
+ if(printout != null) printout(n, x, m, wa4, data, 2, iter, ++nfev.val);
+ if (info.val < 0)
+ return;
+
+ fnorm1 = lm_enorm(m, wa4);
+
+ if (p1 * fnorm1 < fnorm)
+ actred = 1 - SQR(fnorm1 / fnorm);
+ else
+ actred = -1;
+
+ for (j = 0; j < n; ++j) {
+ wa3[j] = 0;
+ for (i = 0; i <= j; ++i)
+ wa3[i] += fjac[j * m + i] * wa1[ipvt[j]];
+ }
+ temp1 = lm_enorm(n, wa3) / fnorm;
+ temp2 = sqrt(par.val) * pnorm / fnorm;
+ prered = SQR(temp1) + 2 * SQR(temp2);
+ dirder = -(SQR(temp1) + SQR(temp2));
+
+ ratio = prered != 0 ? actred / prered : 0;
+
+ if (ratio <= p25) {
+ if (actred >= 0.0)
+ temp = p5;
+ else
+ temp = p5 * dirder / (dirder + p5 * actred);
+ if (p1 * fnorm1 >= fnorm || temp < p1)
+ temp = p1;
+ delta = temp * min(delta, pnorm / p1);
+ par.val /= temp;
+ } else if (par.val == 0.0 || ratio >= p75) {
+ delta = pnorm / p5;
+ par.val *= p5;
+ }
+
+ if (ratio >= p0001) {
+ for (j = 0; j < n; ++j) {
+ x[j] = wa2[j];
+ wa2[j] = diag[j] * x[j];
+ }
+ for (i = 0; i < m; ++i)
+ fvec[i] = wa4[i];
+ xnorm = lm_enorm(n, wa2);
+ fnorm = fnorm1;
+ ++iter;
+ }
+
+ info.val = 0;
+ if (fabs(actred) <= ftol && prered <= ftol && p5 * ratio <= 1)
+ info.val = 1;
+ if (delta <= xtol * xnorm)
+ info.val += 2;
+ if (info.val != 0)
+ return;
+
+ if (nfev.val >= maxfev)
+ info.val = 5;
+ if (fabs(actred) <= LM_MACHEP && prered <= LM_MACHEP && p5 * ratio <= 1)
+ info.val = 6;
+ if (delta <= LM_MACHEP * xnorm)
+ info.val = 7;
+ if (gnorm <= LM_MACHEP)
+ info.val = 8;
+ if (info.val != 0)
+ return;
+ } while (ratio < p0001);
+ } while (true);
+}
+
+
+// copied from the C code; wrapper of lm_lmdif
+void lm_minimize(int m_dat, int n_par, real[] par, lm_evaluate_ftype evaluate, lm_print_ftype printout, lm_data_type data, lm_control_type control) {
+ int n = n_par;
+ int m = m_dat;
+
+ real[] fvec = new real[m];
+ real[] diag = new real[n];
+ real[] qtf = new real[n];
+ real[] fjac = new real[n * m];
+ real[] wa1 = new real[n];
+ real[] wa2 = new real[n];
+ real[] wa3 = new real[n];
+ real[] wa4 = new real[m];
+ int[] ipvt = new int[n];
+
+ control.info.val = 0;
+ control.nfev.val = 0;
+
+ lm_lmdif(m, n, par, fvec, control.ftol, control.xtol, control.gtol, control.maxcall * (n + 1), control.epsilon, diag, 1, control.stepbound, control.info, control.nfev, fjac, ipvt, qtf, wa1, wa2, wa3, wa4, evaluate, printout, data);
+
+ if(printout != null) printout(n, par, m, fvec, data, -1, 0, control.nfev.val);
+ control.fnorm = lm_enorm(m, fvec);
+ if (control.info.val < 0)
+ control.info.val = 10;
+}
+
+
+// convenience functions; wrappers of lm_minimize
+
+/*
+ The structure FitControl specifies various control parameters.
+*/
+struct FitControl {
+ real squareSumTolerance; // relative error desired in the sum of squares
+ real approximationTolerance; // relative error between last two approximations
+ real desiredOrthogonality; // orthogonality desired between the residue vector and its derivatives
+ real epsilon; // step used to calculate the jacobian
+ real stepBound; // initial bound to steps in the outer loop
+ int maxIterations; // maximum number of iterations
+ bool verbose; // whether to print detailed information about every iteration, or nothing
+
+ void operator init(real squareSumTolerance=LM_USERTOL, real approximationTolerance=LM_USERTOL, real desiredOrthogonality=LM_USERTOL, real epsilon=LM_USERTOL, real stepBound=100, int maxIterations=100, bool verbose=false) {
+ this.squareSumTolerance = squareSumTolerance;
+ this.approximationTolerance = approximationTolerance;
+ this.desiredOrthogonality = desiredOrthogonality;
+ this.epsilon = epsilon;
+ this.stepBound = stepBound;
+ this.maxIterations = maxIterations;
+ this.verbose = verbose;
+ }
+
+ FitControl copy() {
+ FitControl result = new FitControl;
+ result.squareSumTolerance = this.squareSumTolerance;
+ result.approximationTolerance = this.approximationTolerance;
+ result.desiredOrthogonality = this.desiredOrthogonality;
+ result.epsilon = this.epsilon;
+ result.stepBound = this.stepBound;
+ result.maxIterations = this.maxIterations;
+ result.verbose = this.verbose;
+ return result;
+ }
+};
+
+FitControl operator init() {
+ return FitControl();
+}
+
+FitControl defaultControl;
+
+
+/*
+ Upon returning, this structure provides information about the fit.
+*/
+struct FitResult {
+ real norm; // norm of the residue vector
+ int iterations; // actual number of iterations
+ int status; // status of minimization
+
+ void operator init(real norm, int iterations, int status) {
+ this.norm = norm;
+ this.iterations = iterations;
+ this.status = status;
+ }
+};
+
+
+/*
+ Fits data points to a function that depends on some parameters.
+
+ Parameters:
+ - xdata: Array of x values.
+ - ydata: Array of y values.
+ - errors: Array of experimental errors; each element must be strictly positive
+ - function: Fit function.
+ - parameters: Parameter array. Before calling fit(), this must contain the initial guesses for the parameters.
+ Upon return, it will contain the solution parameters.
+ - control: object of type FitControl that controls various aspects of the fitting procedure.
+
+ Returns:
+ An object of type FitResult that conveys information about the fitting process.
+*/
+FitResult fit(real[] xdata, real[] ydata, real[] errors, real function(real[], real), real[] parameters, FitControl control=defaultControl) {
+ int m_dat = min(xdata.length, ydata.length);
+ int n_par = parameters.length;
+ lm_evaluate_ftype evaluate = lm_evaluate_default;
+ lm_print_ftype printout = control.verbose ? lm_print_default : lm_print_quiet;
+
+ lm_data_type data;
+ data.user_t = xdata;
+ data.user_y = ydata;
+ data.user_w = 1 / errors;
+ data.user_func = new real(real x, real[] params) {
+ return function(params, x);
+ };
+
+ lm_control_type ctrl;
+ ctrl.ftol = control.squareSumTolerance;
+ ctrl.xtol = control.approximationTolerance;
+ ctrl.gtol = control.desiredOrthogonality;
+ ctrl.epsilon = control.epsilon;
+ ctrl.stepbound = control.stepBound;
+ ctrl.maxcall = control.maxIterations;
+
+ lm_minimize(m_dat, n_par, parameters, evaluate, printout, data, ctrl);
+
+ return FitResult(ctrl.fnorm, ctrl.nfev.val, ctrl.info.val);
+}
+
+
+/*
+ Fits data points to a function that depends on some parameters.
+
+ Parameters:
+ - xdata: Array of x values.
+ - ydata: Array of y values.
+ - function: Fit function.
+ - parameters: Parameter array. Before calling fit(), this must contain the initial guesses for the parameters.
+ Upon return, it will contain the solution parameters.
+ - control: object of type FitControl that controls various aspects of the fitting procedure.
+
+ Returns:
+ An object of type FitResult that conveys information about the fitting process.
+*/
+FitResult fit(real[] xdata, real[] ydata, real function(real[], real), real[] parameters, FitControl control=defaultControl) {
+ return fit(xdata, ydata, array(min(xdata.length, ydata.length), 1.0), function, parameters, control);
+}
+
diff --git a/graphics/asymptote/base/markers.asy b/graphics/asymptote/base/markers.asy
new file mode 100644
index 0000000000..84adc8e6cf
--- /dev/null
+++ b/graphics/asymptote/base/markers.asy
@@ -0,0 +1,218 @@
+// Mark routines and markers written by Philippe Ivaldi.
+// http://www.piprime.fr/
+
+marker operator * (transform T, marker m)
+{
+ marker M=new marker;
+ M.f=T*m.f;
+ M.above=m.above;
+ M.markroutine=m.markroutine;
+ return M;
+}
+
+// Add n frames f midway (in arclength) between n+1 uniformly spaced marks.
+markroutine markinterval(int n=1, frame f, bool rotated=false)
+{
+ return new void(picture pic=currentpicture, frame mark, path g) {
+ markuniform(n+1,rotated)(pic,mark,g);
+ markuniform(centered=true,n,rotated)(pic,f,g);
+ };
+}
+
+// Return a frame containing n copies of the path g shifted by space
+// drawn with pen p.
+frame duplicate(path g, int n=1, pair space=0, pen p=currentpen)
+{
+ if(space == 0) space=dotsize(p);
+ frame f;
+ int pos=0;
+ int sign=1;
+ int m=(n+1) % 2;
+ for(int i=1; i <= n; ++i) {
+ draw(f,shift(space*(pos-0.5*m))*g,p);
+ pos += i*sign;
+ sign *= -1;
+ }
+ return f;
+}
+
+real tildemarksizefactor=5;
+real tildemarksize(pen p=currentpen)
+{
+ static real golden=(1+sqrt(5))/2;
+ return (1mm+tildemarksizefactor*sqrt(linewidth(p)))/golden;
+}
+frame tildeframe(int n=1, real size=0, pair space=0,
+ real angle=0, pair offset=0, pen p=currentpen)
+{
+ size=(size == 0) ? tildemarksize(p) : size;
+ space=(space == 0) ? 1.5*size : space;
+ path g=yscale(1.25)*((-1.5,-0.5)..(-0.75,0.5)..(0,0)..(0.75,-0.5)..(1.5,0.5));
+ return duplicate(shift(offset)*rotate(angle)*scale(size)*g,n,space,p);
+}
+
+frame tildeframe=tildeframe();
+
+real stickmarkspacefactor=4;
+real stickmarksizefactor=10;
+real stickmarksize(pen p=currentpen)
+{
+ return 1mm+stickmarksizefactor*sqrt(linewidth(p));
+}
+real stickmarkspace(pen p=currentpen)
+{
+ return stickmarkspacefactor*sqrt(linewidth(p));
+}
+frame stickframe(int n=1, real size=0, pair space=0, real angle=0,
+ pair offset=0, pen p=currentpen)
+{
+ if(size == 0) size=stickmarksize(p);
+ if(space == 0) space=stickmarkspace(p);
+ return duplicate(shift(offset)*rotate(angle)*scale(0.5*size)*(N--S),n,
+ space,p);
+}
+
+frame stickframe=stickframe();
+
+real circlemarkradiusfactor=stickmarksizefactor/2;
+real circlemarkradius(pen p=currentpen)
+{
+ static real golden=(1+sqrt(5))/2;
+ return (1mm+circlemarkradiusfactor*sqrt(linewidth(p)))/golden;
+}
+real barmarksizefactor=stickmarksizefactor;
+real barmarksize(pen p=currentpen)
+{
+ return 1mm+barmarksizefactor*sqrt(linewidth(p));
+}
+frame circlebarframe(int n=1, real barsize=0,
+ real radius=0,real angle=0,
+ pair offset=0, pen p=currentpen,
+ filltype filltype=NoFill, bool above=false)
+{
+ if(barsize == 0) barsize=barmarksize(p);
+ if(radius == 0) radius=circlemarkradius(p);
+ frame opic;
+ path g=circle(offset,radius);
+ frame f=stickframe(n,barsize,space=2*radius/(n+1),angle,offset,p);
+ if(above) {
+ add(opic,f);
+ filltype.fill(opic,g,p);
+ } else {
+ filltype.fill(opic,g,p);
+ add(opic,f);
+ }
+ return opic;
+}
+
+real crossmarksizefactor=5;
+real crossmarksize(pen p=currentpen)
+{
+ return 1mm+crossmarksizefactor*sqrt(linewidth(p));
+}
+frame crossframe(int n=3, real size=0, pair space=0,
+ real angle=0, pair offset=0, pen p=currentpen)
+{
+ if(size == 0) size=crossmarksize(p);
+ frame opic;
+ draw(opic,shift(offset)*rotate(angle)*scale(size)*cross(n),p);
+ return opic;
+}
+
+real markanglespacefactor=4;
+real markangleradiusfactor=8;
+real markangleradius(pen p=currentpen)
+{
+ return 8mm+markangleradiusfactor*sqrt(linewidth(p));
+}
+real markangleradius=markangleradius();
+real markanglespace(pen p=currentpen)
+{
+ return markanglespacefactor*sqrt(linewidth(p));
+}
+real markanglespace=markanglespace();
+// Mark the oriented angle AOB counterclockwise with optional Label, arrows, and markers.
+// With radius < 0, AOB-2pi is marked clockwise.
+void markangle(picture pic=currentpicture, Label L="",
+ int n=1, real radius=0, real space=0,
+ pair A, pair O, pair B, arrowbar arrow=None,
+ pen p=currentpen, filltype filltype=NoFill,
+ margin margin=NoMargin, marker marker=nomarker)
+{
+ if(space == 0) space=markanglespace(p);
+ if(radius == 0) radius=markangleradius(p);
+ picture lpic,phantom;
+ frame ff;
+ path lpth;
+ p=squarecap+p;
+ pair OB=unit(B-O), OA=unit(A-O);
+ real xoa=degrees(OA,false);
+ real gle=degrees(acos(dot(OA,OB)));
+ if((conj(OA)*OB).y < 0) gle *= -1;
+ bool ccw=radius > 0;
+ if(!ccw) radius=-radius;
+ bool drawarrow = !arrow(phantom,arc((0,0),radius,xoa,xoa+gle,ccw),p,margin);
+ if(drawarrow && margin == NoMargin) margin=TrueMargin(0,0.5linewidth(p));
+ if(filltype != NoFill) {
+ lpth=margin(arc((0,0),radius+(n-1)*space,xoa,xoa+gle,ccw),p).g;
+ pair p0=relpoint(lpth,0), p1=relpoint(lpth,1);
+ pair ac=p0-p0-A+O, bd=p1-p1-B+O, det=(conj(ac)*bd).y;
+ pair op=(det == 0) ? O : p0+(conj(p1-p0)*bd).y*ac/det;
+ filltype.fill(ff,op--lpth--relpoint(lpth,1)--cycle,p);
+ add(lpic,ff);
+ }
+ for(int i=0; i < n; ++i) {
+ lpth=margin(arc((0,0),radius+i*space,xoa,xoa+gle,ccw),p).g;
+ draw(lpic,lpth,p=p,arrow=arrow,margin=NoMargin,marker=marker);
+ }
+ Label lL=L.copy();
+ real position=lL.position.position.x;
+ if(lL.defaultposition) {lL.position.relative=true; position=0.5;}
+ if(lL.position.relative) position=reltime(lpth,position);
+ if(lL.align.default) {
+ lL.align.relative=true;
+ lL.align.dir=unit(point(lpth,position));
+ }
+ label(lpic,lL,point(lpth,position),align=NoAlign, p=p);
+ add(pic,lpic,O);
+}
+
+marker StickIntervalMarker(int i=2, int n=1, real size=0, real space=0,
+ real angle=0, pair offset=0, bool rotated=true,
+ pen p=currentpen, frame uniform=newframe,
+ bool above=true)
+{
+ return marker(uniform,markinterval(i,stickframe(n,size,space,angle,offset,p),
+ rotated),above);
+}
+
+
+marker CrossIntervalMarker(int i=2, int n=3, real size=0, real space=0,
+ real angle=0, pair offset=0, bool rotated=true,
+ pen p=currentpen, frame uniform=newframe,
+ bool above=true)
+{
+ return marker(uniform,markinterval(i,crossframe(n,size,space,angle,offset,p),
+ rotated=rotated),above);
+}
+
+marker CircleBarIntervalMarker(int i=2, int n=1, real barsize=0, real radius=0,
+ real angle=0, pair offset=0, bool rotated=true,
+ pen p=currentpen, filltype filltype=NoFill,
+ bool circleabove=false, frame uniform=newframe,
+ bool above=true)
+{
+ return marker(uniform,markinterval(i,circlebarframe(n,barsize,radius,angle,
+ offset,p,filltype,
+ circleabove),
+ rotated),above);
+}
+
+marker TildeIntervalMarker(int i=2, int n=1, real size=0, real space=0,
+ real angle=0, pair offset=0, bool rotated=true,
+ pen p=currentpen, frame uniform=newframe,
+ bool above=true)
+{
+ return marker(uniform,markinterval(i,tildeframe(n,size,space,angle,offset,p),
+ rotated),above);
+}
diff --git a/graphics/asymptote/base/math.asy b/graphics/asymptote/base/math.asy
new file mode 100644
index 0000000000..9d10e2b779
--- /dev/null
+++ b/graphics/asymptote/base/math.asy
@@ -0,0 +1,451 @@
+// Asymptote mathematics routines
+
+int quadrant(real degrees)
+{
+ return floor(degrees/90) % 4;
+}
+
+// Roots of unity.
+pair unityroot(int n, int k=1)
+{
+ return expi(2pi*k/n);
+}
+
+real csc(real x) {return 1/sin(x);}
+real sec(real x) {return 1/cos(x);}
+real cot(real x) {return tan(pi/2-x);}
+
+real acsc(real x) {return asin(1/x);}
+real asec(real x) {return acos(1/x);}
+real acot(real x) {return pi/2-atan(x);}
+
+real frac(real x) {return x-(int)x;}
+
+pair exp(explicit pair z) {return exp(z.x)*expi(z.y);}
+pair log(explicit pair z) {return log(abs(z))+I*angle(z);}
+
+// Return an Nx by Ny unit square lattice with lower-left corner at (0,0).
+picture grid(int Nx, int Ny, pen p=currentpen)
+{
+ picture pic;
+ for(int i=0; i <= Nx; ++i) draw(pic,(i,0)--(i,Ny),p);
+ for(int j=0; j <= Ny; ++j) draw(pic,(0,j)--(Nx,j),p);
+ return pic;
+}
+
+bool polygon(path p)
+{
+ return cyclic(p) && piecewisestraight(p);
+}
+
+// Return the intersection time of the point on the line through p and q
+// that is closest to z.
+real intersect(pair p, pair q, pair z)
+{
+ pair u=q-p;
+ real denom=dot(u,u);
+ return denom == 0 ? infinity : dot(z-p,u)/denom;
+}
+
+// Return the intersection time of the extension of the line segment PQ
+// with the plane perpendicular to n and passing through Z.
+real intersect(triple P, triple Q, triple n, triple Z)
+{
+ real d=n.x*Z.x+n.y*Z.y+n.z*Z.z;
+ real denom=n.x*(Q.x-P.x)+n.y*(Q.y-P.y)+n.z*(Q.z-P.z);
+ return denom == 0 ? infinity : (d-n.x*P.x-n.y*P.y-n.z*P.z)/denom;
+}
+
+// Return any point on the intersection of the two planes with normals
+// n0 and n1 passing through points P0 and P1, respectively.
+// If the planes are parallel return (infinity,infinity,infinity).
+triple intersectionpoint(triple n0, triple P0, triple n1, triple P1)
+{
+ real Dx=n0.y*n1.z-n1.y*n0.z;
+ real Dy=n0.z*n1.x-n1.z*n0.x;
+ real Dz=n0.x*n1.y-n1.x*n0.y;
+ if(abs(Dx) > abs(Dy) && abs(Dx) > abs(Dz)) {
+ Dx=1/Dx;
+ real d0=n0.y*P0.y+n0.z*P0.z;
+ real d1=n1.y*P1.y+n1.z*P1.z+n1.x*(P1.x-P0.x);
+ real y=(d0*n1.z-d1*n0.z)*Dx;
+ real z=(d1*n0.y-d0*n1.y)*Dx;
+ return (P0.x,y,z);
+ } else if(abs(Dy) > abs(Dz)) {
+ Dy=1/Dy;
+ real d0=n0.z*P0.z+n0.x*P0.x;
+ real d1=n1.z*P1.z+n1.x*P1.x+n1.y*(P1.y-P0.y);
+ real z=(d0*n1.x-d1*n0.x)*Dy;
+ real x=(d1*n0.z-d0*n1.z)*Dy;
+ return (x,P0.y,z);
+ } else {
+ if(Dz == 0) return (infinity,infinity,infinity);
+ Dz=1/Dz;
+ real d0=n0.x*P0.x+n0.y*P0.y;
+ real d1=n1.x*P1.x+n1.y*P1.y+n1.z*(P1.z-P0.z);
+ real x=(d0*n1.y-d1*n0.y)*Dz;
+ real y=(d1*n0.x-d0*n1.x)*Dz;
+ return (x,y,P0.z);
+ }
+}
+
+// Given a real array a, return its partial sums.
+real[] partialsum(real[] a)
+{
+ real[] b=new real[a.length];
+ real sum=0;
+ for(int i=0; i < a.length; ++i) {
+ sum += a[i];
+ b[i]=sum;
+ }
+ return b;
+}
+
+// Given a real array a, return its partial dx-weighted sums.
+real[] partialsum(real[] a, real[] dx)
+{
+ real[] b=new real[a.length];
+ real sum=0;
+ for(int i=0; i < a.length; ++i) {
+ sum += a[i]*dx[i];
+ b[i]=sum;
+ }
+ return b;
+}
+
+// Given an integer array a, return its partial sums.
+int[] partialsum(int[] a)
+{
+ int[] b=new int[a.length];
+ int sum=0;
+ for(int i=0; i < a.length; ++i) {
+ sum += a[i];
+ b[i]=sum;
+ }
+ return b;
+}
+
+// Given an integer array a, return its partial dx-weighted sums.
+int[] partialsum(int[] a, int[] dx)
+{
+ int[] b=new int[a.length];
+ int sum=0;
+ for(int i=0; i < a.length; ++i) {
+ sum += a[i]*dx[i];
+ b[i]=sum;
+ }
+ return b;
+}
+
+// If strict=false, return whether i > j implies a[i] >= a[j]
+// If strict=true, return whether i > j implies a[i] > a[j]
+bool increasing(real[] a, bool strict=false)
+{
+ real[] ap=copy(a);
+ ap.delete(0);
+ ap.push(0);
+ bool[] b=strict ? (ap > a) : (ap >= a);
+ b[a.length-1]=true;
+ return all(b);
+}
+
+// Return the first and last indices of consecutive true-element segments
+// of bool[] b.
+int[][] segmentlimits(bool[] b)
+{
+ int[][] segment;
+ bool[] n=copy(b);
+ n.delete(0);
+ n.push(!b[b.length-1]);
+ int[] edge=(b != n) ? sequence(1,b.length) : null;
+ edge.insert(0,0);
+ int stop=edge[0];
+ for(int i=1; i < edge.length; ++i) {
+ int start=stop;
+ stop=edge[i];
+ if(b[start])
+ segment.push(new int[] {start,stop-1});
+ }
+ return segment;
+}
+
+// Return the indices of consecutive true-element segments of bool[] b.
+int[][] segment(bool[] b)
+{
+ int[][] S=segmentlimits(b);
+ return sequence(new int[](int i) {
+ return sequence(S[i][0],S[i][1]);
+ },S.length);
+}
+
+// If the sorted array a does not contain x, insert it sequentially,
+// returning the index of x in the resulting array.
+int unique(real[] a, real x) {
+ int i=search(a,x);
+ if(i == -1 || x != a[i]) {
+ ++i;
+ a.insert(i,x);
+ }
+ return i;
+}
+
+int unique(string[] a, string x) {
+ int i=search(a,x);
+ if(i == -1 || x != a[i]) {
+ ++i;
+ a.insert(i,x);
+ }
+ return i;
+}
+
+bool lexorder(pair a, pair b) {
+ return a.x < b.x || (a.x == b.x && a.y < b.y);
+}
+
+bool lexorder(triple a, triple b) {
+ return a.x < b.x || (a.x == b.x && (a.y < b.y || (a.y == b.y && a.z < b.z)));
+}
+
+real[] zero(int n)
+{
+ return sequence(new real(int) {return 0;},n);
+}
+
+real[][] zero(int n, int m)
+{
+ real[][] M=new real[n][];
+ for(int i=0; i < n; ++i)
+ M[i]=sequence(new real(int) {return 0;},m);
+ return M;
+}
+
+bool square(real[][] m)
+{
+ int n=m.length;
+ for(int i=0; i < n; ++i)
+ if(m[i].length != n) return false;
+ return true;
+}
+
+bool rectangular(real[][] m)
+{
+ int n=m.length;
+ if(n > 0) {
+ int m0=m[0].length;
+ for(int i=1; i < n; ++i)
+ if(m[i].length != m0) return false;
+ }
+ return true;
+}
+
+bool rectangular(pair[][] m)
+{
+ int n=m.length;
+ if(n > 0) {
+ int m0=m[0].length;
+ for(int i=1; i < n; ++i)
+ if(m[i].length != m0) return false;
+ }
+ return true;
+}
+
+bool rectangular(triple[][] m)
+{
+ int n=m.length;
+ if(n > 0) {
+ int m0=m[0].length;
+ for(int i=1; i < n; ++i)
+ if(m[i].length != m0) return false;
+ }
+ return true;
+}
+
+// draw the (infinite) line going through P and Q, without altering the
+// size of picture pic.
+void drawline(picture pic=currentpicture, pair P, pair Q, pen p=currentpen)
+{
+ pic.add(new void (frame f, transform t, transform T, pair m, pair M) {
+ // Reduce the bounds by the size of the pen.
+ m -= min(p); M -= max(p);
+
+ // Calculate the points and direction vector in the transformed space.
+ t=t*T;
+ pair z=t*P;
+ pair v=t*Q-z;
+
+ // Handle horizontal and vertical lines.
+ if(v.x == 0) {
+ if(m.x <= z.x && z.x <= M.x)
+ draw(f,(z.x,m.y)--(z.x,M.y),p);
+ } else if(v.y == 0) {
+ if(m.y <= z.y && z.y <= M.y)
+ draw(f,(m.x,z.y)--(M.x,z.y),p);
+ } else {
+ // Calculate the maximum and minimum t values allowed for the
+ // parametric equation z + t*v
+ real mx=(m.x-z.x)/v.x, Mx=(M.x-z.x)/v.x;
+ real my=(m.y-z.y)/v.y, My=(M.y-z.y)/v.y;
+ real tmin=max(v.x > 0 ? mx : Mx, v.y > 0 ? my : My);
+ real tmax=min(v.x > 0 ? Mx : mx, v.y > 0 ? My : my);
+ if(tmin <= tmax)
+ draw(f,z+tmin*v--z+tmax*v,p);
+ }
+ },true);
+}
+
+real interpolate(real[] x, real[] y, real x0, int i)
+{
+ int n=x.length;
+ if(n == 0) abort("Zero data points in interpolate");
+ if(n == 1) return y[0];
+ if(i < 0) {
+ real dx=x[1]-x[0];
+ return y[0]+(y[1]-y[0])/dx*(x0-x[0]);
+ }
+ if(i >= n-1) {
+ real dx=x[n-1]-x[n-2];
+ return y[n-1]+(y[n-1]-y[n-2])/dx*(x0-x[n-1]);
+ }
+
+ real D=x[i+1]-x[i];
+ real B=(x0-x[i])/D;
+ real A=1.0-B;
+ return A*y[i]+B*y[i+1];
+}
+
+// Linearly interpolate data points (x,y) to (x0,y0), where the elements of
+// real[] x are listed in ascending order and return y0. Values outside the
+// available data range are linearly extrapolated using the first derivative
+// at the nearest endpoint.
+real interpolate(real[] x, real[] y, real x0)
+{
+ return interpolate(x,y,x0,search(x,x0));
+}
+
+private string nopoint="point not found";
+
+// Return the nth intersection time of path g with the vertical line through x.
+real time(path g, real x, int n=0)
+{
+ real[] t=times(g,x);
+ if(t.length <= n) abort(nopoint);
+ return t[n];
+}
+
+// Return the nth intersection time of path g with the horizontal line through
+// (0,z.y).
+real time(path g, explicit pair z, int n=0)
+{
+ real[] t=times(g,z);
+ if(t.length <= n) abort(nopoint);
+ return t[n];
+}
+
+// Return the nth y value of g at x.
+real value(path g, real x, int n=0)
+{
+ return point(g,time(g,x,n)).y;
+}
+
+// Return the nth x value of g at y=z.y.
+real value(path g, explicit pair z, int n=0)
+{
+ return point(g,time(g,(0,z.y),n)).x;
+}
+
+// Return the nth slope of g at x.
+real slope(path g, real x, int n=0)
+{
+ pair a=dir(g,time(g,x,n));
+ return a.y/a.x;
+}
+
+// Return the nth slope of g at y=z.y.
+real slope(path g, explicit pair z, int n=0)
+{
+ pair a=dir(g,time(g,(0,z.y),n));
+ return a.y/a.x;
+}
+
+// A quartic complex root solver based on these references:
+// http://planetmath.org/encyclopedia/GaloisTheoreticDerivationOfTheQuarticFormula.html
+// Neumark, S., Solution of Cubic and Quartic Equations, Pergamon Press
+// Oxford (1965).
+pair[] quarticroots(real a, real b, real c, real d, real e)
+{
+ real Fuzz=100000*realEpsilon;
+
+ // Remove roots at numerical infinity.
+ if(abs(a) <= Fuzz*(abs(b)+Fuzz*(abs(c)+Fuzz*(abs(d)+Fuzz*abs(e)))))
+ return cubicroots(b,c,d,e);
+
+ // Detect roots at numerical zero.
+ if(abs(e) <= Fuzz*(abs(d)+Fuzz*(abs(c)+Fuzz*(abs(b)+Fuzz*abs(a)))))
+ return cubicroots(a,b,c,d);
+
+ real ainv=1/a;
+ b *= ainv;
+ c *= ainv;
+ d *= ainv;
+ e *= ainv;
+
+ pair[] roots;
+ real[] T=cubicroots(1,-2c,c^2+b*d-4e,d^2+b^2*e-b*c*d);
+ if(T.length == 0) return roots;
+
+ real t0=T[0];
+ pair[] sum=quadraticroots((1,0),(b,0),(t0,0));
+ pair[] product=quadraticroots((1,0),(t0-c,0),(e,0));
+
+ if(abs(sum[0]*product[0]+sum[1]*product[1]+d) <
+ abs(sum[0]*product[1]+sum[1]*product[0]+d))
+ product=reverse(product);
+
+ for(int i=0; i < 2; ++i)
+ roots.append(quadraticroots((1,0),-sum[i],product[i]));
+
+ return roots;
+}
+
+pair[][] fft(pair[][] a, int sign=1)
+{
+ pair[][] A=new pair[a.length][];
+ int k=0;
+ for(pair[] v : a) {
+ A[k]=fft(v,sign);
+ ++k;
+ }
+ a=transpose(A);
+ k=0;
+ for(pair[] v : a) {
+ A[k]=fft(v,sign);
+ ++k;
+ }
+ return transpose(A);
+}
+
+// Given a matrix A with independent columns, return
+// the unique vector y minimizing |Ay - b|^2 (the L2 norm).
+// If the columns of A are not linearly independent,
+// throw an error (if warn == true) or return an empty array
+// (if warn == false).
+real[] leastsquares(real[][] A, real[] b, bool warn=true)
+{
+ real[] solution=solve(AtA(A),b*A,warn=false);
+ if (solution.length == 0 && warn)
+ abort("Cannot compute least-squares approximation for " +
+ "a matrix with linearly dependent columns.");
+ return solution;
+}
+
+// Namespace
+struct rootfinder_settings {
+ static real roottolerance = 1e-4;
+}
+
+real findroot(real f(real), real a, real b,
+ real tolerance=rootfinder_settings.roottolerance,
+ real fa=f(a), real fb=f(b))
+{
+ return _findroot(f,a,b,tolerance,fa,fb);
+}
diff --git a/graphics/asymptote/base/metapost.asy b/graphics/asymptote/base/metapost.asy
new file mode 100644
index 0000000000..525ffe755e
--- /dev/null
+++ b/graphics/asymptote/base/metapost.asy
@@ -0,0 +1,19 @@
+// MetaPost compatibility routines
+
+path cuttings;
+
+path cutbefore(path p, path q)
+{
+ slice s=firstcut(p,q);
+ cuttings=s.before;
+ return s.after;
+}
+
+path cutafter(path p, path q)
+{
+ slice s=lastcut(p,q);
+ cuttings=s.after;
+ return s.before;
+}
+
+
diff --git a/graphics/asymptote/base/nopapersize.ps b/graphics/asymptote/base/nopapersize.ps
new file mode 100644
index 0000000000..67e3140474
--- /dev/null
+++ b/graphics/asymptote/base/nopapersize.ps
@@ -0,0 +1,3 @@
+@ a4size 0in 0in
+
+@ letterSize 0in 0in
diff --git a/graphics/asymptote/base/obj.asy b/graphics/asymptote/base/obj.asy
new file mode 100644
index 0000000000..8dde9341c4
--- /dev/null
+++ b/graphics/asymptote/base/obj.asy
@@ -0,0 +1,113 @@
+// A module for reading simple obj files with groups.
+// Authors: Jens Schwaiger and John Bowman
+//
+// Here simple means that :
+//
+// 1) all vertex statements should come before the face statements;
+//
+// 2) face informations with respect to texture and/or normal vectors are
+// ignored;
+//
+// 3) face statements only contain positive numbers(no relative positions).
+//
+// The reading process only takes into account lines starting with "v" or
+// "f" or "g"(group).
+
+import three;
+
+struct obj {
+ surface s;
+ material[] surfacepen;
+ pen[] meshpen;
+
+ path3[][] read(string datafile, bool verbose=false) {
+ file in=input(datafile).word().line();
+ triple[] vert;
+ path3[][] g;
+ g[0]=new path3[] ;
+ string[] G;
+ void Vertex(real x,real y ,real z) {vert.push((x,y,z));}
+ void Face(int[] vertnr, int groupnr) {
+ guide3 gh;
+ for(int i=0; i < vertnr.length; ++i)
+ gh=gh--vert[vertnr[i]-1];
+ gh=gh--cycle;
+ g[groupnr].push(gh);
+ }
+ if(verbose) write("Reading data from "+datafile+".");
+ int groupnr;
+ while(true) {
+ string[] str=in;
+ if(str.length == 0) break;
+ str=sequence(new string(int i) {return split(str[i],"/")[0];},str.length);
+ if(str[0] == "g" && str.length > 1) {
+ int tst=find(G == str[1]);
+ if(tst == -1) {
+ G.push(str[1]);
+ groupnr=G.length-1;
+ g[groupnr]=new path3[] ;
+ }
+ if(tst > -1) groupnr=tst;
+ }
+ if(str[0] == "v") Vertex((real) str[1],(real) str[2],(real) str[3]);
+ if(str[0] == "f") {
+ int[] vertnr;
+ for(int i=1; i < str.length; ++i) vertnr[i-1]=(int) str[i];
+ Face(vertnr,groupnr);
+ }
+ if(eof(in)) break;
+ }
+ close(in);
+ if(verbose) {
+ write("Number of groups: ",G.length);
+ write("Groups and their names:");
+ write(G);
+ write("Reading done.");
+ write("Number of faces contained in the groups: ");
+ for(int j=0; j < G.length; ++j)
+ write(G[j],": ",(string) g[j].length);
+ }
+ return g;
+ }
+
+ void operator init(path3[][] g, material[] surfacepen, pen[] meshpen) {
+ for(int i=0; i < g.length; ++i) {
+ path3[] gi=g[i];
+ for(int j=0; j < gi.length; ++j) {
+ // Treat all faces as planar to avoid subdivision cracks.
+ surface sij=surface(gi[j],planar=true);
+ s.append(sij);
+ this.surfacepen.append(array(sij.s.length,surfacepen[i]));
+ this.meshpen.append(array(sij.s.length,meshpen[i]));
+ }
+ }
+ }
+
+ void operator init(string datafile, bool verbose=false,
+ material[] surfacepen, pen[] meshpen=nullpens) {
+ operator init(read(datafile,verbose),surfacepen,meshpen);
+ }
+
+ void operator init(string datafile, bool verbose=false,
+ material surfacepen, pen meshpen=nullpen) {
+ material[] surfacepen={surfacepen};
+ pen[] meshpen={meshpen};
+ surfacepen.cyclic=true;
+ meshpen.cyclic=true;
+ operator init(read(datafile,verbose),surfacepen,meshpen);
+ }
+}
+
+obj operator * (transform3 T, obj o)
+{
+ obj ot;
+ ot.s=T*o.s;
+ ot.surfacepen=copy(o.surfacepen);
+ ot.meshpen=copy(o.meshpen);
+ return ot;
+}
+
+void draw(picture pic=currentpicture, obj o, light light=currentlight)
+{
+ draw(pic,o.s,o.surfacepen,o.meshpen,light);
+}
diff --git a/graphics/asymptote/base/ode.asy b/graphics/asymptote/base/ode.asy
new file mode 100644
index 0000000000..7104a8d153
--- /dev/null
+++ b/graphics/asymptote/base/ode.asy
@@ -0,0 +1,457 @@
+real stepfactor=2; // Maximum dynamic step size adjustment factor.
+
+struct coefficients
+{
+ real[] steps;
+ real[] factors;
+ real[][] weights;
+ real[] highOrderWeights;
+ real[] lowOrderWeights;
+}
+
+struct RKTableau
+{
+ int order;
+ coefficients a;
+ void stepDependence(real h, real c, coefficients a) {}
+
+ real pgrow;
+ real pshrink;
+ bool exponential;
+
+ void operator init(int order, real[][] weights, real[] highOrderWeights,
+ real[] lowOrderWeights=new real[],
+ real[] steps=sequence(new real(int i) {
+ return sum(weights[i]);},weights.length),
+ void stepDependence(real, real, coefficients)=null) {
+ this.order=order;
+ a.steps=steps;
+ a.factors=array(a.steps.length+1,1);
+ a.weights=weights;
+ a.highOrderWeights=highOrderWeights;
+ a.lowOrderWeights=lowOrderWeights;
+ if(stepDependence != null) {
+ this.stepDependence=stepDependence;
+ exponential=true;
+ }
+ pgrow=(order > 0) ? 1/order : 0;
+ pshrink=(order > 1) ? 1/(order-1) : pgrow;
+ }
+}
+
+real[] Coeff={1,1/2,1/6,1/24,1/120,1/720,1/5040,1/40320,1/362880,1/3628800,
+ 1/39916800.0,1/479001600.0,1/6227020800.0,1/87178291200.0,
+ 1/1307674368000.0,1/20922789888000.0,1/355687428096000.0,
+ 1/6402373705728000.0,1/121645100408832000.0,
+ 1/2432902008176640000.0,1/51090942171709440000.0,
+ 1/1124000727777607680000.0};
+
+real phi1(real x) {return x != 0 ? expm1(x)/x : 1;}
+
+real phi2(real x)
+{
+ real x2=x*x;
+ if(fabs(x) > 1) return (exp(x)-x-1)/x2;
+ real x3=x2*x;
+ real x5=x2*x3;
+ if(fabs(x) < 0.1)
+ return Coeff[1]+x*Coeff[2]+x2*Coeff[3]+x3*Coeff[4]+x2*x2*Coeff[5]
+ +x5*Coeff[6]+x3*x3*Coeff[7]+x5*x2*Coeff[8]+x5*x3*Coeff[9];
+ else {
+ real x7=x5*x2;
+ real x8=x7*x;
+ return Coeff[1]+x*Coeff[2]+x2*Coeff[3]+x3*Coeff[4]+x2*x2*Coeff[5]
+ +x5*Coeff[6]+x3*x3*Coeff[7]+x7*Coeff[8]+x8*Coeff[9]
+ +x8*x*Coeff[10]+x5*x5*Coeff[11]+x8*x3*Coeff[12]+x7*x5*Coeff[13]+
+ x8*x5*Coeff[14]+x7*x7*Coeff[15]+x8*x7*Coeff[16]+x8*x8*Coeff[17];
+ }
+}
+
+real phi3(real x)
+{
+ real x2=x*x;
+ real x3=x2*x;
+ if(fabs(x) > 1.6) return (exp(x)-0.5*x2-x-1)/x3;
+ real x5=x2*x3;
+ if(fabs(x) < 0.1)
+ return Coeff[2]+x*Coeff[3]+x2*Coeff[4]+x3*Coeff[5]
+ +x2*x2*Coeff[6]+x5*Coeff[7]+x3*x3*Coeff[8]+x5*x2*Coeff[9]
+ +x5*x3*Coeff[10];
+ else {
+ real x7=x5*x2;
+ real x8=x7*x;
+ real x16=x8*x8;
+ return Coeff[2]+x*Coeff[3]+x2*Coeff[4]+x3*Coeff[5]
+ +x2*x2*Coeff[6]+x5*Coeff[7]+x3*x3*Coeff[8]+x5*x2*Coeff[9]
+ +x5*x3*Coeff[10]+x8*x*Coeff[11]
+ +x5*x5*Coeff[12]+x8*x3*Coeff[13]+x7*x5*Coeff[14]
+ +x8*x5*Coeff[15]+x7*x7*Coeff[16]+x8*x7*Coeff[17]+x16*Coeff[18]
+ +x16*x*Coeff[19]+x16*x2*Coeff[20];
+ }
+}
+
+void expfactors(real x, coefficients a)
+{
+ for(int i=0; i < a.steps.length; ++i)
+ a.factors[i]=exp(x*a.steps[i]);
+ a.factors[a.steps.length]=exp(x);
+}
+
+// First-Order Euler
+RKTableau Euler=RKTableau(1,new real[][], new real[] {1});
+
+// First-Order Exponential Euler
+RKTableau E_Euler=RKTableau(1,new real[][], new real[] {1},
+ new void(real h, real c, coefficients a) {
+ real x=-c*h;
+ expfactors(x,a);
+ a.highOrderWeights[0]=phi1(x);
+ });
+
+// Second-Order Runge-Kutta
+RKTableau RK2=RKTableau(2,new real[][] {{1/2}},
+ new real[] {0,1}, // 2nd order
+ new real[] {1,0}); // 1st order
+
+// Second-Order Exponential Runge-Kutta
+RKTableau E_RK2=RKTableau(2,new real[][] {{1/2}},
+ new real[] {0,1}, // 2nd order
+ new real[] {1,0}, // 1st order
+ new void(real h, real c, coefficients a) {
+ real x=-c*h;
+ expfactors(x,a);
+ a.weights[0][0]=1/2*phi1(x/2);
+ real w=phi1(x);
+ a.highOrderWeights[0]=0;
+ a.highOrderWeights[1]=w;
+ a.lowOrderWeights[0]=w;
+ });
+
+// Second-Order Predictor-Corrector
+RKTableau PC=RKTableau(2,new real[][] {{1}},
+ new real[] {1/2,1/2}, // 2nd order
+ new real[] {1,0}); // 1st order
+
+// Second-Order Exponential Predictor-Corrector
+RKTableau E_PC=RKTableau(2,new real[][] {{1}},
+ new real[] {1/2,1/2}, // 2nd order
+ new real[] {1,0}, // 1st order
+ new void(real h, real c, coefficients a) {
+ real x=-c*h;
+ expfactors(x,a);
+ real w=phi1(x);
+ a.weights[0][0]=w;
+ a.highOrderWeights[0]=w/2;
+ a.highOrderWeights[1]=w/2;
+ a.lowOrderWeights[0]=w;
+ });
+
+// Third-Order Classical Runge-Kutta
+RKTableau RK3=RKTableau(3,new real[][] {{1/2},{-1,2}},
+ new real[] {1/6,2/3,1/6});
+
+// Third-Order Bogacki-Shampine Runge-Kutta
+RKTableau RK3BS=RKTableau(3,new real[][] {{1/2},{0,3/4}},
+ new real[] {2/9,1/3,4/9}, // 3rd order
+ new real[] {7/24,1/4,1/3,1/8}); // 2nd order
+
+// Third-Order Exponential Bogacki-Shampine Runge-Kutta
+RKTableau E_RK3BS=RKTableau(3,new real[][] {{1/2},{0,3/4}},
+ new real[] {2/9,1/3,4/9}, // 3rd order
+ new real[] {7/24,1/4,1/3,1/8}, // 2nd order
+ new void(real h, real c, coefficients a) {
+ real x=-c*h;
+ expfactors(x,a);
+ real w=phi1(x);
+ real w2=phi2(x);
+ a.weights[0][0]=1/2*phi1(x/2);
+ real a11=9/8*phi2(3/4*x)+3/8*phi2(x/2);
+ a.weights[1][0]=3/4*phi1(3/4*x)-a11;
+ a.weights[1][1]=a11;
+ real a21=1/3*w;
+ real a22=4/3*w2-2/9*w;
+ a.highOrderWeights[0]=w-a21-a22;
+ a.highOrderWeights[1]=a21;
+ a.highOrderWeights[2]=a22;
+ a.lowOrderWeights[0]=w-17/12*w2;
+ a.lowOrderWeights[1]=w2/2;
+ a.lowOrderWeights[2]=2/3*w2;
+ a.lowOrderWeights[3]=w2/4;
+ });
+
+// Fourth-Order Classical Runge-Kutta
+RKTableau RK4=RKTableau(4,new real[][] {{1/2},{0,1/2},{0,0,1}},
+ new real[] {1/6,1/3,1/3,1/6});
+
+// Fifth-Order Cash-Karp Runge-Kutta
+RKTableau RK5=RKTableau(5,new real[][] {{1/5},
+ {3/40,9/40},
+ {3/10,-9/10,6/5},
+ {-11/54,5/2,-70/27,35/27},
+ {1631/55296,175/512,575/13824,
+ 44275/110592,253/4096}},
+ new real[] {37/378,0,250/621,125/594,
+ 0,512/1771}, // 5th order
+ new real[] {2825/27648,0,18575/48384,13525/55296,
+ 277/14336,1/4}); // 4th order
+
+// Fifth-Order Fehlberg Runge-Kutta
+RKTableau RK5F=RKTableau(5,new real[][] {{1/4},
+ {3/32,9/32},
+ {1932/2197,-7200/2197,7296/2197},
+ {439/216,-8,3680/513,-845/4104},
+ {-8/27,2,-3544/2565,1859/4104,
+ -11/40}},
+ new real[] {16/135,0,6656/12825,28561/56430,-9/50,2/55}, // 5th order
+ new real[] {25/216,0,1408/2565,2197/4104,-1/5,0}); // 4th order
+
+// Fifth-Order Dormand-Prince Runge-Kutta
+RKTableau RK5DP=RKTableau(5,new real[][] {{1/5},
+ {3/40,9/40},
+ {44/45,-56/15,32/9},
+ {19372/6561,-25360/2187,64448/6561,
+ -212/729},
+ {9017/3168,-355/33,46732/5247,49/176,
+ -5103/18656}},
+ new real[] {35/384,0,500/1113,125/192,-2187/6784,
+ 11/84}, // 5th order
+ new real[] {5179/57600,0,7571/16695,393/640,
+ -92097/339200,187/2100,1/40}); // 4th order
+
+real error(real error, real initial, real lowOrder, real norm, real diff)
+{
+ if(initial != 0 && lowOrder != initial) {
+ static real epsilon=realMin/realEpsilon;
+ real denom=max(abs(norm),abs(initial))+epsilon;
+ return max(error,max(abs(diff)/denom));
+ }
+ return error;
+}
+
+void report(real old, real h, real t)
+{
+ write("Time step changed from "+(string) old+" to "+(string) h+" at t="+
+ (string) t+".");
+}
+
+real adjust(real h, real error, real tolmin, real tolmax, RKTableau tableau)
+{
+ if(error > tolmax)
+ h *= max((tolmin/error)^tableau.pshrink,1/stepfactor);
+ else if(error > 0 && error < tolmin)
+ h *= min((tolmin/error)^tableau.pgrow,stepfactor);
+ return h;
+}
+
+struct solution
+{
+ real[] t;
+ real[] y;
+}
+
+void write(solution S)
+{
+ for(int i=0; i < S.t.length; ++i)
+ write(S.t[i],S.y[i]);
+}
+
+// Integrate dy/dt+cy=f(t,y) from a to b using initial conditions y,
+// specifying either the step size h or the number of steps n.
+solution integrate(real y, real c=0, real f(real t, real y), real a, real b=a,
+ real h=0, int n=0, bool dynamic=false, real tolmin=0,
+ real tolmax=0, real dtmin=0, real dtmax=realMax,
+ RKTableau tableau, bool verbose=false)
+{
+ solution S;
+ S.t=new real[] {a};
+ S.y=new real[] {y};
+
+ if(h == 0) {
+ if(b == a) return S;
+ if(n == 0) abort("Either n or h must be specified");
+ else h=(b-a)/n;
+ }
+
+ real F(real t, real y)=(c == 0 || tableau.exponential) ? f :
+ new real(real t, real y) {return f(t,y)-c*y;};
+
+ tableau.stepDependence(h,c,tableau.a);
+
+ real t=a;
+ real f0;
+ if(tableau.a.lowOrderWeights.length == 0) dynamic=false;
+ bool fsal=dynamic &&
+ (tableau.a.lowOrderWeights.length > tableau.a.highOrderWeights.length);
+ if(fsal) f0=F(t,y);
+
+ real dt=h;
+ while(t < b) {
+ h=min(h,b-t);
+ if(t+h == t) break;
+ if(h != dt) {
+ if(verbose) report(dt,h,t);
+ tableau.stepDependence(h,c,tableau.a);
+ dt=h;
+ }
+
+ real[] predictions={fsal ? f0 : F(t,y)};
+ for(int i=0; i < tableau.a.steps.length; ++i)
+ predictions.push(F(t+h*tableau.a.steps[i],
+ tableau.a.factors[i]*y+h*dot(tableau.a.weights[i],
+ predictions)));
+
+ real highOrder=h*dot(tableau.a.highOrderWeights,predictions);
+ real y0=tableau.a.factors[tableau.a.steps.length]*y;
+ if(dynamic) {
+ real f1;
+ if(fsal) {
+ f1=F(t+h,y0+highOrder);
+ predictions.push(f1);
+ }
+ real lowOrder=h*dot(tableau.a.lowOrderWeights,predictions);
+ real error;
+ error=error(error,y,y0+lowOrder,y0+highOrder,highOrder-lowOrder);
+ h=adjust(h,error,tolmin,tolmax,tableau);
+ if(h >= dt) {
+ t += dt;
+ y=y0+highOrder;
+ S.t.push(t);
+ S.y.push(y);
+ f0=f1;
+ }
+ h=min(max(h,dtmin),dtmax);
+ } else {
+ t += h;
+ y=y0+highOrder;
+ S.t.push(t);
+ S.y.push(y);
+ }
+ }
+ return S;
+}
+
+struct Solution
+{
+ real[] t;
+ real[][] y;
+}
+
+void write(Solution S)
+{
+ for(int i=0; i < S.t.length; ++i) {
+ write(S.t[i],tab);
+ for(real y : S.y[i])
+ write(y,tab);
+ write();
+ }
+}
+
+// Integrate a set of equations, dy/dt=f(t,y), from a to b using initial
+// conditions y, specifying either the step size h or the number of steps n.
+Solution integrate(real[] y, real[] f(real t, real[] y), real a, real b=a,
+ real h=0, int n=0, bool dynamic=false,
+ real tolmin=0, real tolmax=0, real dtmin=0,
+ real dtmax=realMax, RKTableau tableau, bool verbose=false)
+{
+ Solution S;
+ S.t=new real[] {a};
+ S.y=new real[][] {copy(y)};
+
+ if(h == 0) {
+ if(b == a) return S;
+ if(n == 0) abort("Either n or h must be specified");
+ else h=(b-a)/n;
+ }
+ real t=a;
+ real[] f0;
+ if(tableau.a.lowOrderWeights.length == 0) dynamic=false;
+ bool fsal=dynamic &&
+ (tableau.a.lowOrderWeights.length > tableau.a.highOrderWeights.length);
+ if(fsal) f0=f(t,y);
+
+ real dt=h;
+ while(t < b) {
+ h=min(h,b-t);
+ if(t+h == t) break;
+ if(h != dt) {
+ if(verbose) report(dt,h,t);
+ dt=h;
+ }
+
+ real[][] predictions={fsal ? f0 : f(t,y)};
+ for(int i=0; i < tableau.a.steps.length; ++i)
+ predictions.push(f(t+h*tableau.a.steps[i],
+ y+h*tableau.a.weights[i]*predictions));
+
+ real[] highOrder=h*tableau.a.highOrderWeights*predictions;
+ if(dynamic) {
+ real[] f1;
+ if(fsal) {
+ f1=f(t+h,y+highOrder);
+ predictions.push(f1);
+ }
+ real[] lowOrder=h*tableau.a.lowOrderWeights*predictions;
+ real error;
+ for(int i=0; i < y.length; ++i)
+ error=error(error,y[i],y[i]+lowOrder[i],y[i]+highOrder[i],
+ highOrder[i]-lowOrder[i]);
+ h=adjust(h,error,tolmin,tolmax,tableau);
+ if(h >= dt) {
+ t += dt;
+ y += highOrder;
+ S.t.push(t);
+ S.y.push(y);
+ f0=f1;
+ }
+ h=min(max(h,dtmin),dtmax);
+ } else {
+ t += h;
+ y += highOrder;
+ S.t.push(t);
+ S.y.push(y);
+ }
+ }
+ return S;
+}
+
+real[][] finiteDifferenceJacobian(real[] f(real[]), real[] t,
+ real[] h=sqrtEpsilon*abs(t))
+{
+ real[] ft=f(t);
+ real[][] J=new real[t.length][ft.length];
+ real[] ti=copy(t);
+ real tlast=ti[0];
+ ti[0] += h[0];
+ J[0]=(f(ti)-ft)/h[0];
+ for(int i=1; i < t.length; ++i) {
+ ti[i-1]=tlast;
+ tlast=ti[i];
+ ti[i] += h[i];
+ J[i]=(f(ti)-ft)/h[i];
+ }
+ return transpose(J);
+}
+
+// Solve simultaneous nonlinear system by Newton's method.
+real[] newton(int iterations=100, real[] f(real[]), real[][] jacobian(real[]),
+ real[] t)
+{
+ real[] t=copy(t);
+ for(int i=0; i < iterations; ++i)
+ t += solve(jacobian(t),-f(t));
+ return t;
+}
+
+real[] solveBVP(real[] f(real, real[]), real a, real b=a, real h=0, int n=0,
+ bool dynamic=false, real tolmin=0, real tolmax=0, real dtmin=0,
+ real dtmax=realMax, RKTableau tableau, bool verbose=false,
+ real[] initial(real[]), real[] discrepancy(real[]),
+ real[] guess, int iterations=100)
+{
+ real[] g(real[] t) {
+ real[][] y=integrate(initial(t),f,a,b,h,n,dynamic,tolmin,tolmax,dtmin,dtmax,
+ tableau,verbose).y;return discrepancy(y[y.length-1]);
+ }
+ real[][] jacobian(real[] t) {return finiteDifferenceJacobian(g,t);}
+ return initial(newton(iterations,g,jacobian,guess));
+}
diff --git a/graphics/asymptote/base/palette.asy b/graphics/asymptote/base/palette.asy
new file mode 100644
index 0000000000..2cb6e7c547
--- /dev/null
+++ b/graphics/asymptote/base/palette.asy
@@ -0,0 +1,530 @@
+private import graph;
+
+private transform swap=(0,0,0,1,1,0);
+
+typedef bounds range(picture pic, real min, real max);
+
+range Range(bool automin=false, real min=-infinity,
+ bool automax=false, real max=infinity)
+{
+ return new bounds(picture pic, real dmin, real dmax) {
+ // autoscale routine finds reasonable limits
+ bounds mz=autoscale(pic.scale.z.T(dmin),
+ pic.scale.z.T(dmax),
+ pic.scale.z.scale);
+ // If automin/max, use autoscale result, else
+ // if min/max is finite, use specified value, else
+ // use minimum/maximum data value
+ real pmin=automin ? pic.scale.z.Tinv(mz.min) : (finite(min) ? min : dmin);
+ real pmax=automax ? pic.scale.z.Tinv(mz.max) : (finite(max) ? max : dmax);
+ return bounds(pmin,pmax);
+ };
+}
+
+range Automatic=Range(true,true);
+range Full=Range();
+
+void image(frame f, real[][] data, pair initial, pair final, pen[] palette,
+ bool transpose=(initial.x < final.x && initial.y < final.y),
+ transform t=identity(), bool copy=true, bool antialias=false)
+{
+ transform T=transpose ? swap : identity();
+ _image(f,copy ? copy(data) : data,T*initial,T*final,palette,t*T,copy=false,
+ antialias=antialias);
+}
+
+void image(frame f, pen[][] data, pair initial, pair final,
+ bool transpose=(initial.x < final.x && initial.y < final.y),
+ transform t=identity(), bool copy=true, bool antialias=false)
+{
+ transform T=transpose ? swap : identity();
+ _image(f,copy ? copy(data) : data,T*initial,T*final,t*T,copy=false,
+ antialias=antialias);
+}
+
+// Reduce color palette to approximate range of data relative to "display"
+// range => errors of 1/palette.length in resulting color space.
+pen[] adjust(picture pic, real min, real max, real rmin, real rmax,
+ pen[] palette)
+{
+ real dmin=pic.scale.z.T(min);
+ real dmax=pic.scale.z.T(max);
+ real delta=rmax-rmin;
+ if(delta > 0) {
+ real factor=palette.length/delta;
+ int minindex=floor(factor*(dmin-rmin));
+ if(minindex < 0) minindex=0;
+ int maxindex=ceil(factor*(dmax-rmin));
+ if(maxindex > palette.length) maxindex=palette.length;
+ if(minindex > 0 || maxindex < palette.length)
+ return palette[minindex:maxindex];
+ }
+ return palette;
+}
+
+private real[] sequencereal;
+
+bounds image(picture pic=currentpicture, real[][] f, range range=Full,
+ pair initial, pair final, pen[] palette,
+ bool transpose=(initial.x < final.x && initial.y < final.y),
+ bool copy=true, bool antialias=false)
+{
+ if(copy) f=copy(f);
+ if(copy) palette=copy(palette);
+
+ real m=min(f);
+ real M=max(f);
+ bounds bounds=range(pic,m,M);
+ real rmin=pic.scale.z.T(bounds.min);
+ real rmax=pic.scale.z.T(bounds.max);
+ palette=adjust(pic,m,M,rmin,rmax,palette);
+
+ // Crop data to allowed range and scale
+ if(range != Full || pic.scale.z.scale.T != identity ||
+ pic.scale.z.postscale.T != identity) {
+ scalefcn T=pic.scale.z.T;
+ real m=bounds.min;
+ real M=bounds.max;
+ for(int i=0; i < f.length; ++i)
+ f[i]=map(new real(real x) {return T(min(max(x,m),M));},f[i]);
+ }
+
+ initial=Scale(pic,initial);
+ final=Scale(pic,final);
+
+ pic.addBox(initial,final);
+
+ transform T;
+ if(transpose) {
+ T=swap;
+ initial=T*initial;
+ final=T*final;
+ }
+
+ pic.add(new void(frame F, transform t) {
+ _image(F,f,initial,final,palette,t*T,copy=false,antialias=antialias);
+ },true);
+ return bounds; // Return bounds used for color space
+}
+
+bounds image(picture pic=currentpicture, real f(real, real),
+ range range=Full, pair initial, pair final,
+ int nx=ngraph, int ny=nx, pen[] palette, bool antialias=false)
+{
+ // Generate data, taking scaling into account
+ real xmin=pic.scale.x.T(initial.x);
+ real xmax=pic.scale.x.T(final.x);
+ real ymin=pic.scale.y.T(initial.y);
+ real ymax=pic.scale.y.T(final.y);
+ real[][] data=new real[ny][nx];
+ for(int j=0; j < ny; ++j) {
+ real y=pic.scale.y.Tinv(interp(ymin,ymax,(j+0.5)/ny));
+ scalefcn Tinv=pic.scale.x.Tinv;
+ // Take center point of each bin
+ data[j]=sequence(new real(int i) {
+ return f(Tinv(interp(xmin,xmax,(i+0.5)/nx)),y);
+ },nx);
+ }
+ return image(pic,data,range,initial,final,palette,transpose=false,
+ copy=false,antialias=antialias);
+}
+
+void image(picture pic=currentpicture, pen[][] data, pair initial, pair final,
+ bool transpose=(initial.x < final.x && initial.y < final.y),
+ bool copy=true, bool antialias=false)
+{
+ if(copy) data=copy(data);
+
+ initial=Scale(pic,initial);
+ final=Scale(pic,final);
+
+ pic.addBox(initial,final);
+
+ transform T;
+ if(transpose) {
+ T=swap;
+ initial=T*initial;
+ final=T*final;
+ }
+
+ pic.add(new void(frame F, transform t) {
+ _image(F,data,initial,final,t*T,copy=false,antialias=antialias);
+ },true);
+}
+
+void image(picture pic=currentpicture, pen f(int, int), int width, int height,
+ pair initial, pair final,
+ bool transpose=(initial.x < final.x && initial.y < final.y),
+ bool antialias=false)
+{
+ initial=Scale(pic,initial);
+ final=Scale(pic,final);
+
+ pic.addBox(initial,final);
+
+ transform T;
+ if(transpose) {
+ T=swap;
+ int temp=width;
+ width=height;
+ height=temp;
+ initial=T*initial;
+ final=T*final;
+ }
+
+ pic.add(new void(frame F, transform t) {
+ _image(F,f,width,height,initial,final,t*T,antialias=antialias);
+ },true);
+}
+
+bounds image(picture pic=currentpicture, pair[] z, real[] f,
+ range range=Full, pen[] palette)
+{
+ if(z.length != f.length)
+ abort("z and f arrays have different lengths");
+
+ real m=min(f);
+ real M=max(f);
+ bounds bounds=range(pic,m,M);
+ real rmin=pic.scale.z.T(bounds.min);
+ real rmax=pic.scale.z.T(bounds.max);
+
+ palette=adjust(pic,m,M,rmin,rmax,palette);
+ rmin=max(rmin,m);
+ rmax=min(rmax,M);
+
+ // Crop data to allowed range and scale
+ if(range != Full || pic.scale.z.scale.T != identity ||
+ pic.scale.z.postscale.T != identity) {
+ scalefcn T=pic.scale.z.T;
+ real m=bounds.min;
+ real M=bounds.max;
+ f=map(new real(real x) {return T(min(max(x,m),M));},f);
+ }
+
+ int[] edges={0,0,1};
+ int N=palette.length-1;
+
+ int[][] trn=triangulate(z);
+ real step=rmax == rmin ? 0.0 : N/(rmax-rmin);
+ for(int i=0; i < trn.length; ++i) {
+ int[] trni=trn[i];
+ int i0=trni[0], i1=trni[1], i2=trni[2];
+ pen color(int i) {return palette[round((f[i]-rmin)*step)];}
+ gouraudshade(pic,z[i0]--z[i1]--z[i2]--cycle,
+ new pen[] {color(i0),color(i1),color(i2)},edges);
+ }
+ return bounds; // Return bounds used for color space
+}
+
+bounds image(picture pic=currentpicture, real[] x, real[] y, real[] f,
+ range range=Full, pen[] palette)
+{
+ int n=x.length;
+ if(n != y.length)
+ abort("x and y arrays have different lengths");
+
+ pair[] z=sequence(new pair(int i) {return (x[i],y[i]);},n);
+ return image(pic,z,f,range,palette);
+}
+
+// Construct a pen[] array from f using the specified palette.
+pen[] palette(real[] f, pen[] palette)
+{
+ real Min=min(f);
+ real Max=max(f);
+ if(palette.length == 0) return new pen[];
+ real step=Max == Min ? 0.0 : (palette.length-1)/(Max-Min);
+ return sequence(new pen(int i) {return palette[round((f[i]-Min)*step)];},
+ f.length);
+}
+
+// Construct a pen[][] array from f using the specified palette.
+pen[][] palette(real[][] f, pen[] palette)
+{
+ real Min=min(f);
+ real Max=max(f);
+ int n=f.length;
+ pen[][] p=new pen[n][];
+ real step=(Max == Min) ? 0.0 : (palette.length-1)/(Max-Min);
+ for(int i=0; i < n; ++i) {
+ real[] fi=f[i];
+ p[i]=sequence(new pen(int j) {return palette[round((fi[j]-Min)*step)];},
+ f[i].length);
+ }
+ return p;
+}
+
+typedef ticks paletteticks(int sign=-1);
+
+paletteticks PaletteTicks(Label format="", ticklabel ticklabel=null,
+ bool beginlabel=true, bool endlabel=true,
+ int N=0, int n=0, real Step=0, real step=0,
+ pen pTick=nullpen, pen ptick=nullpen)
+{
+ return new ticks(int sign=-1) {
+ format.align(sign > 0 ? RightSide : LeftSide);
+ return Ticks(sign,format,ticklabel,beginlabel,endlabel,N,n,Step,step,
+ true,true,extend=true,pTick,ptick);
+ };
+}
+
+paletteticks PaletteTicks=PaletteTicks();
+paletteticks NoTicks=new ticks(int sign=-1) {return NoTicks;};
+
+void palette(picture pic=currentpicture, Label L="", bounds bounds,
+ pair initial, pair final, axis axis=Right, pen[] palette,
+ pen p=currentpen, paletteticks ticks=PaletteTicks,
+ bool copy=true, bool antialias=false)
+{
+ real initialz=pic.scale.z.T(bounds.min);
+ real finalz=pic.scale.z.T(bounds.max);
+ bounds mz=autoscale(initialz,finalz,pic.scale.z.scale);
+
+ axisT axis;
+ axis(pic,axis);
+ real angle=degrees(axis.align.dir);
+
+ initial=Scale(pic,initial);
+ final=Scale(pic,final);
+
+ pair lambda=final-initial;
+ bool vertical=(floor((angle+45)/90) % 2 == 0);
+ pair perp,par;
+
+ if(vertical) {perp=E; par=N;} else {perp=N; par=E;}
+
+ path g=(final-dot(lambda,par)*par)--final;
+ path g2=initial--final-dot(lambda,perp)*perp;
+
+ if(sgn(dot(lambda,perp)*dot(axis.align.dir,perp)) == -1) {
+ path tmp=g;
+ g=g2;
+ g2=tmp;
+ }
+
+ if(copy) palette=copy(palette);
+ Label L=L.copy();
+ if(L.defaultposition) L.position(0.5);
+ L.align(axis.align);
+ L.p(p);
+ if(vertical && L.defaulttransform) {
+ frame f;
+ add(f,Label(L.s,(0,0),L.p));
+ if(length(max(f)-min(f)) > ylabelwidth*fontsize(L.p))
+ L.transform(rotate(90));
+ }
+ real[][] pdata={sequence(palette.length)};
+
+ transform T;
+ pair Tinitial,Tfinal;
+ if(vertical) {
+ T=swap;
+ Tinitial=T*initial;
+ Tfinal=T*final;
+ } else {
+ Tinitial=initial;
+ Tfinal=final;
+ }
+
+ pic.add(new void(frame f, transform t) {
+ _image(f,pdata,Tinitial,Tfinal,palette,t*T,copy=false,
+ antialias=antialias);
+ },true);
+
+ ticklocate locate=ticklocate(initialz,finalz,pic.scale.z,mz.min,mz.max);
+ axis(pic,L,g,g2,p,ticks(sgn(axis.side.x*dot(lambda,par))),locate,mz.divisor,
+ true);
+
+ pic.add(new void(frame f, transform t) {
+ pair Z0=t*initial;
+ pair Z1=t*final;
+ draw(f,Z0--(Z0.x,Z1.y)--Z1--(Z1.x,Z0.y)--cycle,p);
+ },true);
+
+ pic.addBox(initial,final);
+}
+
+// A grayscale palette
+pen[] Grayscale(int NColors=256)
+{
+ real ninv=1.0/(NColors-1.0);
+ return sequence(new pen(int i) {return gray(i*ninv);},NColors);
+}
+
+// A color wheel palette
+pen[] Wheel(int NColors=32766)
+{
+ if(settings.gray) return Grayscale(NColors);
+
+ int nintervals=6;
+ int n=-quotient(NColors,-nintervals);
+
+ pen[] Palette;
+ if(n == 0) return Palette;
+
+ Palette=new pen[n*nintervals];
+ real ninv=1.0/n;
+
+ for(int i=0; i < n; ++i) {
+ real ininv=i*ninv;
+ real ininv1=1.0-ininv;
+ Palette[i]=rgb(1.0,0.0,ininv);
+ Palette[n+i]=rgb(ininv1,0.0,1.0);
+ Palette[2n+i]=rgb(0.0,ininv,1.0);
+ Palette[3n+i]=rgb(0.0,1.0,ininv1);
+ Palette[4n+i]=rgb(ininv,1.0,0.0);
+ Palette[5n+i]=rgb(1.0,ininv1,0.0);
+ }
+ return Palette;
+}
+
+// A rainbow palette
+pen[] Rainbow(int NColors=32766)
+{
+ if(settings.gray) return Grayscale(NColors);
+
+ int offset=1;
+ int nintervals=5;
+ int n=-quotient(NColors-1,-nintervals);
+
+ pen[] Palette;
+ if(n == 0) return Palette;
+
+ Palette=new pen[n*nintervals+offset];
+ real ninv=1.0/n;
+
+ for(int i=0; i < n; ++i) {
+ real ininv=i*ninv;
+ real ininv1=1.0-ininv;
+ Palette[i]=rgb(ininv1,0.0,1.0);
+ Palette[n+i]=rgb(0.0,ininv,1.0);
+ Palette[2n+i]=rgb(0.0,1.0,ininv1);
+ Palette[3n+i]=rgb(ininv,1.0,0.0);
+ Palette[4n+i]=rgb(1.0,ininv1,0.0);
+ }
+ Palette[4n+n]=rgb(1.0,0.0,0.0);
+
+ return Palette;
+}
+
+private pen[] BWRainbow(int NColors, bool two)
+{
+ if(settings.gray) return Grayscale(NColors);
+
+ int offset=1;
+ int nintervals=6;
+ int divisor=3;
+
+ if(two) nintervals += 6;
+
+ int num=NColors-offset;
+ int n=-quotient(num,-nintervals*divisor)*divisor;
+ NColors=n*nintervals+offset;
+
+ pen[] Palette;
+ if(n == 0) return Palette;
+
+ Palette=new pen[NColors];
+ real ninv=1.0/n;
+
+ int k=0;
+
+ if(two) {
+ for(int i=0; i < n; ++i) {
+ real ininv=i*ninv;
+ real ininv1=1.0-ininv;
+ Palette[i]=rgb(ininv1,0.0,1.0);
+ Palette[n+i]=rgb(0.0,ininv,1.0);
+ Palette[2n+i]=rgb(0.0,1.0,ininv1);
+ Palette[3n+i]=rgb(ininv,1.0,0.0);
+ Palette[4n+i]=rgb(1.0,ininv1,0.0);
+ Palette[5n+i]=rgb(1.0,0.0,ininv);
+ }
+ k += 6n;
+ }
+
+ if(two)
+ for(int i=0; i < n; ++i)
+ Palette[k+i]=rgb(1.0-i*ninv,0.0,1.0);
+ else {
+ int n3=-quotient(n,-3);
+ int n23=2*n3;
+ real third=n3*ninv;
+ real twothirds=n23*ninv;
+ for(int i=0; i < n3; ++i) {
+ real ininv=i*ninv;
+ Palette[k+i]=rgb(ininv,0.0,ininv);
+ Palette[k+n3+i]=rgb(third,0.0,third+ininv);
+ Palette[k+n23+i]=rgb(third-ininv,0.0,twothirds+ininv);
+ }
+ }
+ k += n;
+
+ for(int i=0; i < n; ++i) {
+ real ininv=i*ninv;
+ real ininv1=1.0-ininv;
+ Palette[k+i]=rgb(0.0,ininv,1.0);
+ Palette[k+n+i]=rgb(0.0,1.0,ininv1);
+ Palette[k+2n+i]=rgb(ininv,1.0,0.0);
+ Palette[k+3n+i]=rgb(1.0,ininv1,0.0);
+ Palette[k+4n+i]=rgb(1.0,ininv,ininv);
+ }
+ Palette[k+5n]=rgb(1.0,1.0,1.0);
+
+ return Palette;
+}
+
+// Quantize palette to exactly n values
+pen[] quantize(pen[] Palette, int n)
+{
+ if(Palette.length == 0) abort("cannot quantize empty palette");
+ if(n <= 1) abort("palette must contain at least two pens");
+ real step=(Palette.length-1)/(n-1);
+ return sequence(new pen(int i) {
+ return Palette[round(i*step)];
+ },n);
+}
+
+// A rainbow palette tapering off to black/white at the spectrum ends,
+pen[] BWRainbow(int NColors=32761)
+{
+ return BWRainbow(NColors,false);
+}
+
+// A double rainbow palette tapering off to black/white at the spectrum ends,
+// with a linearly scaled intensity.
+pen[] BWRainbow2(int NColors=32761)
+{
+ pen[] Palette=BWRainbow(NColors,true);
+ int n=Palette.length;
+ real ninv=1.0/n;
+ for(int i=0; i < n; ++i)
+ Palette[i]=i*ninv*Palette[i];
+ return Palette;
+}
+
+//A palette varying linearly over the specified array of pens, using
+// NColors in each interpolation interval.
+pen[] Gradient(int NColors=256 ... pen[] p)
+{
+ pen[] P;
+ if(p.length < 2) abort("at least 2 colors must be specified");
+ real step=NColors > 1 ? (1/(NColors-1)) : 1;
+ for(int i=0; i < p.length-1; ++i) {
+ pen begin=p[i];
+ pen end=p[i+1];
+ P.append(sequence(new pen(int j) {
+ return interp(begin,end,j*step);
+ },NColors));
+ }
+ return P;
+}
+
+pen[] cmyk(pen[] Palette)
+{
+ int n=Palette.length;
+ for(int i=0; i < n; ++i)
+ Palette[i]=cmyk(Palette[i]);
+ return Palette;
+}
diff --git a/graphics/asymptote/base/patterns.asy b/graphics/asymptote/base/patterns.asy
new file mode 100644
index 0000000000..56fa3bf4a2
--- /dev/null
+++ b/graphics/asymptote/base/patterns.asy
@@ -0,0 +1,102 @@
+// Create a tiling named name from picture pic
+// with optional left-bottom margin lb and right-top margin rt.
+frame tiling(string name, picture pic, pair lb=0, pair rt=0)
+{
+ frame tiling;
+ frame f=pic.fit(identity());
+ pair pmin=min(f)-lb;
+ pair pmax=max(f)+rt;
+ string s="%.6f";
+ postscript(tiling,"<< /PaintType 1 /PatternType 1 /TilingType 1
+/BBox ["+format(s,pmin.x,"C")+" "+format(s,pmin.y,"C")+" "+
+ format(s,pmax.x,"C")+" "+format(s,pmax.y,"C")+"]
+/XStep "+format(s,pmax.x-pmin.x,"C")+"
+/YStep "+format(s,pmax.y-pmin.y,"C")+"
+/PaintProc {pop");
+ add(tiling,f);
+ postscript(tiling,"} >>
+ matrix makepattern
+/"+name+" exch def");
+ return tiling;
+}
+
+// Add to frame preamble a tiling name constructed from picture pic
+// with optional left-bottom margin lb and right-top margin rt.
+void add(string name, picture pic, pair lb=0, pair rt=0)
+{
+ add(currentpatterns,tiling(name,pic,lb,rt));
+}
+
+picture tile(real Hx=5mm, real Hy=0, pen p=currentpen,
+ filltype filltype=NoFill)
+{
+ picture tiling;
+ if(Hy == 0) Hy=Hx;
+ path tile=box((0,0),(Hx,Hy));
+ tiling.add(new void (frame f, transform t) {
+ filltype.fill(f,t*tile,p);
+ });
+ clip(tiling,tile);
+ return tiling;
+}
+
+picture checker(real Hx=5mm, real Hy=0, pen p=currentpen)
+{
+ picture tiling;
+ if(Hy == 0) Hy=Hx;
+ path tile=box((0,0),(Hx,Hy));
+ fill(tiling,tile,p);
+ fill(tiling,shift(Hx,Hy)*tile,p);
+ clip(tiling,box((0,0),(2Hx,2Hy)));
+ return tiling;
+}
+
+picture brick(real Hx=5mm, real Hy=0, pen p=currentpen)
+{
+ picture tiling;
+ if(Hy == 0) Hy=Hx/2;
+ path tile=box((0,0),(Hx,Hy));
+ draw(tiling,tile,p);
+ draw(tiling,(Hx/2,Hy)--(Hx/2,2Hy),p);
+ draw(tiling,(0,2Hy)--(Hx,2Hy),p);
+ clip(tiling,box((0,0),(Hx,2Hy)));
+ return tiling;
+}
+
+real hatchepsilon=1e-4;
+picture hatch(real H=5mm, pair dir=NE, pen p=currentpen)
+{
+ picture tiling;
+ real theta=angle(dir);
+ real s=sin(theta);
+ real c=cos(theta);
+ if(abs(s) <= hatchepsilon) {
+ path g=(0,0)--(H,0);
+ draw(tiling,g,p);
+ draw(tiling,shift(0,H)*g,p);
+ clip(tiling,scale(H)*unitsquare);
+ } else if(abs(c) <= hatchepsilon) {
+ path g=(0,0)--(0,H);
+ draw(tiling,g,p);
+ draw(tiling,shift(H,0)*g,p);
+ clip(tiling,scale(H)*unitsquare);
+ } else {
+ real h=H/s;
+ real y=H/c;
+ path g=(0,0)--(h,y);
+ draw(tiling,g,p);
+ draw(tiling,shift(-h/2,y/2)*g,p);
+ draw(tiling,shift(h/2,-y/2)*g,p);
+ clip(tiling,box((0,0),(h,y)));
+ }
+ return tiling;
+}
+
+picture crosshatch(real H=5mm, pen p=currentpen)
+{
+ picture tiling;
+ add(tiling,hatch(H,p));
+ add(tiling,shift(H*sqrt(2))*rotate(90)*hatch(H,p));
+ return tiling;
+}
+
diff --git a/graphics/asymptote/base/plain.asy b/graphics/asymptote/base/plain.asy
new file mode 100644
index 0000000000..4afe3cb3de
--- /dev/null
+++ b/graphics/asymptote/base/plain.asy
@@ -0,0 +1,302 @@
+/*****
+ * plain.asy
+ * Andy Hammerlindl and John Bowman 2004/08/19
+ *
+ * A package for general purpose drawing, with automatic sizing of pictures.
+ *
+ *****/
+
+access settings;
+
+if(settings.command != "") {
+ string s=settings.command;
+ settings.command="";
+ settings.multipleView=settings.batchView=settings.interactiveView;
+ _eval(s+";",false,true);
+ exit();
+}
+
+include plain_constants;
+
+access version;
+if(version.VERSION != VERSION) {
+ warning("version","using possibly incompatible version "+
+ version.VERSION+" of plain.asy"+'\n');
+ nowarn("version");
+}
+
+include plain_strings;
+include plain_pens;
+include plain_paths;
+include plain_filldraw;
+include plain_margins;
+include plain_picture;
+include plain_Label;
+include plain_shipout;
+include plain_arcs;
+include plain_boxes;
+include plain_markers;
+include plain_arrows;
+include plain_debugger;
+
+typedef void exitfcn();
+
+void updatefunction()
+{
+ implicitshipout=true;
+ if(!currentpicture.uptodate) shipout();
+ implicitshipout=false;
+}
+
+void exitfunction()
+{
+ implicitshipout=true;
+ if(!currentpicture.empty())
+ shipout();
+ implicitshipout=false;
+}
+
+atupdate(updatefunction);
+atexit(exitfunction);
+
+// A restore thunk is a function, that when called, restores the graphics state
+// to what it was when the restore thunk was created.
+typedef void restoreThunk();
+typedef restoreThunk saveFunction();
+saveFunction[] saveFunctions={};
+
+// When save is called, this will be redefined to do the corresponding restore.
+void restore()
+{
+ warning("nomatchingsave","restore called with no matching save");
+}
+
+void addSaveFunction(saveFunction s)
+{
+ saveFunctions.push(s);
+}
+
+restoreThunk buildRestoreThunk()
+{
+ // Call the save functions in reverse order, storing their restore thunks.
+ restoreThunk[] thunks={};
+ for (int i=saveFunctions.length-1; i >= 0; --i)
+ thunks.push(saveFunctions[i]());
+
+ return new void() {
+ // Call the restore thunks in an order matching the saves.
+ for (int i=thunks.length-1; i >= 0; --i)
+ thunks[i]();
+ };
+}
+
+// Add the default save function.
+addSaveFunction(new restoreThunk () {
+ pen defaultpen=defaultpen();
+ pen p=currentpen;
+ picture pic=currentpicture.copy();
+ restoreThunk r=restore;
+ return new void() {
+ defaultpen(defaultpen);
+ currentpen=p;
+ currentpicture=pic;
+ currentpicture.uptodate=false;
+ restore=r;
+ };
+ });
+
+// Save the current state, so that restore will put things back in that state.
+restoreThunk save()
+{
+ return restore=buildRestoreThunk();
+}
+
+void restoredefaults()
+{
+ warning("nomatchingsavedefaults",
+ "restoredefaults called with no matching savedefaults");
+}
+
+restoreThunk buildRestoreDefaults()
+{
+ pen defaultpen=defaultpen();
+ exitfcn atupdate=atupdate();
+ exitfcn atexit=atexit();
+ restoreThunk r=restoredefaults;
+ return new void() {
+ defaultpen(defaultpen);
+ atupdate(atupdate);
+ atexit(atexit);
+ restoredefaults=r;
+ };
+}
+
+// Save the current state, so that restore will put things back in that state.
+restoreThunk savedefaults()
+{
+ return restoredefaults=buildRestoreDefaults();
+}
+
+void initdefaults()
+{
+ savedefaults();
+ resetdefaultpen();
+ atupdate(null);
+ atexit(null);
+}
+
+// Return the sequence n,...m
+int[] sequence(int n, int m)
+{
+ return sequence(new int(int x){return x;},m-n+1)+n;
+}
+
+int[] reverse(int n) {return sequence(new int(int x){return n-1-x;},n);}
+bool[] reverse(bool[] a) {return a[reverse(a.length)];}
+int[] reverse(int[] a) {return a[reverse(a.length)];}
+real[] reverse(real[] a) {return a[reverse(a.length)];}
+pair[] reverse(pair[] a) {return a[reverse(a.length)];}
+triple[] reverse(triple[] a) {return a[reverse(a.length)];}
+string[] reverse(string[] a) {return a[reverse(a.length)];}
+
+// Return a uniform partition dividing [a,b] into n subintervals.
+real[] uniform(real a, real b, int n)
+{
+ if(n <= 0) return new real[];
+ return a+(b-a)/n*sequence(n+1);
+}
+
+void eval(string s, bool embedded=false)
+{
+ if(!embedded) initdefaults();
+ _eval(s+";",embedded);
+ if(!embedded) restoredefaults();
+}
+
+void eval(code s, bool embedded=false)
+{
+ if(!embedded) initdefaults();
+ _eval(s,embedded);
+ if(!embedded) restoredefaults();
+}
+
+// Evaluate user command line option.
+void usersetting()
+{
+ eval(settings.user,true);
+}
+
+string stripsuffix(string f, string suffix=".asy")
+{
+ int n=rfind(f,suffix);
+ if(n != -1) f=erase(f,n,-1);
+ return f;
+}
+
+// Conditionally process each file name in array s in a new environment.
+void asy(string format, bool overwrite=false ... string[] s)
+{
+ for(string f : s) {
+ f=stripsuffix(f);
+ string suffix="."+format;
+ string fsuffix=f+suffix;
+ if(overwrite || error(input(fsuffix,check=false))) {
+ string outformat=settings.outformat;
+ bool interactiveView=settings.interactiveView;
+ bool batchView=settings.batchView;
+ settings.outformat=format;
+ settings.interactiveView=false;
+ settings.batchView=false;
+ string outname=outname();
+ delete(outname+"_"+".aux");
+ eval("import \""+f+"\" as dummy");
+ rename(stripsuffix(outname)+suffix,fsuffix);
+ settings.outformat=outformat;
+ settings.interactiveView=interactiveView;
+ settings.batchView=batchView;
+ }
+ }
+}
+
+void beep()
+{
+ write('\7',flush);
+}
+
+struct processtime {
+ real user;
+ real system;
+}
+
+struct cputime {
+ processtime parent;
+ processtime child;
+ processtime change;
+}
+
+cputime cputime()
+{
+ static processtime last;
+ real [] a=_cputime();
+ cputime cputime;
+ cputime.parent.user=a[0];
+ cputime.parent.system=a[1];
+ cputime.child.user=a[2];
+ cputime.child.system=a[3];
+ real user=a[0]+a[2];
+ real system=a[1]+a[3];
+ cputime.change.user=user-last.user;
+ cputime.change.system=system-last.system;
+ last.user=user;
+ last.system=system;
+ return cputime;
+}
+
+string cputimeformat="%#.2f";
+
+void write(file file, string s="", cputime c, string format=cputimeformat,
+ suffix suffix=none)
+{
+ write(file,s,
+ format(format,c.change.user)+"u "+
+ format(format,c.change.system)+"s "+
+ format(format,c.parent.user+c.child.user)+"U "+
+ format(format,c.parent.system+c.child.system)+"S ",suffix);
+}
+
+void write(string s="", cputime c, string format=cputimeformat,
+ suffix suffix=endl)
+{
+ write(stdout,s,c,format,suffix);
+}
+
+if(settings.autoimport != "") {
+ string s=settings.autoimport;
+ settings.autoimport="";
+ eval("import \""+s+"\" as dummy",true);
+ atupdate(updatefunction);
+ atexit(exitfunction);
+ settings.autoimport=s;
+}
+
+cputime();
+
+void nosetpagesize()
+{
+ static bool initialized=false;
+ if(!initialized && latex()) {
+ // Portably pass nosetpagesize option to graphicx package.
+ texpreamble("\usepackage{ifluatex}\ifluatex
+\ifx\pdfpagewidth\undefined\let\pdfpagewidth\paperwidth\fi
+\ifx\pdfpageheight\undefined\let\pdfpageheight\paperheight\fi\else
+\let\paperwidthsave\paperwidth\let\paperwidth\undefined
+\usepackage{graphicx}
+\let\paperwidth\paperwidthsave\fi");
+ initialized=true;
+ }
+}
+
+nosetpagesize();
+
+if(settings.tex == "luatex")
+ texpreamble("\input luatex85.sty");
diff --git a/graphics/asymptote/base/plain_Label.asy b/graphics/asymptote/base/plain_Label.asy
new file mode 100644
index 0000000000..c50ce003be
--- /dev/null
+++ b/graphics/asymptote/base/plain_Label.asy
@@ -0,0 +1,691 @@
+real angle(transform t)
+{
+ pair z=(2t.xx*t.yy,t.yx*t.yy-t.xx*t.xy);
+ if(t.xx < 0 || t.yy < 0) z=-z;
+ return degrees(z,warn=false);
+}
+
+transform rotation(transform t)
+{
+ return rotate(angle(t));
+}
+
+transform scaleless(transform t)
+{
+ real a=t.xx, b=t.xy, c=t.yx, d=t.yy;
+ real arg=(a-d)^2+4b*c;
+ pair delta=arg >= 0 ? sqrt(arg) : I*sqrt(-arg);
+ real trace=a+d;
+ pair l1=0.5(trace+delta);
+ pair l2=0.5(trace-delta);
+
+ if(abs(delta) < sqrtEpsilon*max(abs(l1),abs(l2))) {
+ real s=abs(0.5trace);
+ return (s != 0) ? scale(1/s)*t : t;
+ }
+
+ if(abs(l1-d) < abs(l2-d)) {pair temp=l1; l1=l2; l2=temp;}
+
+ pair dot(pair[] u, pair[] v) {return conj(u[0])*v[0]+conj(u[1])*v[1];}
+
+ pair[] unit(pair[] u) {
+ real norm2=abs(u[0])^2+abs(u[1])^2;
+ return norm2 != 0 ? u/sqrt(norm2) : u;
+ }
+
+ pair[] u={l1-d,b};
+ pair[] v={c,l2-a};
+ u=unit(u);
+ pair d=dot(u,u);
+ if(d != 0) v -= dot(u,v)/d*u;
+ v=unit(v);
+
+ pair[][] U={{u[0],v[0]},{u[1],v[1]}};
+ pair[][] A={{a,b},{c,d}};
+
+ pair[][] operator *(pair[][] a, pair[][] b) {
+ pair[][] c=new pair[2][2];
+ for(int i=0; i < 2; ++i) {
+ for(int j=0; j < 2; ++j) {
+ c[i][j]=a[i][0]*b[0][j]+a[i][1]*b[1][j];
+ }
+ }
+ return c;
+ }
+
+ pair[][] conj(pair[][] a) {
+ pair[][] c=new pair[2][2];
+ for(int i=0; i < 2; ++i) {
+ for(int j=0; j < 2; ++j) {
+ c[i][j]=conj(a[j][i]);
+ }
+ }
+ return c;
+ }
+
+ A=conj(U)*A*U;
+
+ real D=abs(A[0][0]);
+ if(D != 0) {
+ A[0][0] /= D;
+ A[0][1] /= D;
+ }
+
+ D=abs(A[1][1]);
+ if(D != 0) {
+ A[1][0] /= D;
+ A[1][1] /= D;
+ }
+
+ A=U*A*conj(U);
+
+ return (0,0,A[0][0].x,A[0][1].x,A[1][0].x,A[1][1].x);
+}
+
+struct align {
+ pair dir;
+ triple dir3;
+ bool relative=false;
+ bool default=true;
+ bool is3D=false;
+ void init(pair dir=0, bool relative=false, bool default=false) {
+ this.dir=dir;
+ this.relative=relative;
+ this.default=default;
+ is3D=false;
+ }
+ void init(triple dir=(0,0,0), bool relative=false, bool default=false) {
+ this.dir3=dir;
+ this.relative=relative;
+ this.default=default;
+ is3D=true;
+ }
+ align copy() {
+ align align=new align;
+ align.init(dir,relative,default);
+ align.dir3=dir3;
+ align.is3D=is3D;
+ return align;
+ }
+ void align(align align) {
+ if(!align.default) {
+ bool is3D=align.is3D;
+ init(align.dir,align.relative);
+ dir3=align.dir3;
+ this.is3D=is3D;
+ }
+ }
+ void align(align align, align default) {
+ align(align);
+ if(this.default) {
+ init(default.dir,default.relative,default.default);
+ dir3=default.dir3;
+ is3D=default.is3D;
+ }
+ }
+ void write(file file=stdout, suffix suffix=endl) {
+ if(!default) {
+ if(relative) {
+ write(file,"Relative(");
+ if(is3D)
+ write(file,dir3);
+ else
+ write(file,dir);
+ write(file,")",suffix);
+ } else {
+ if(is3D)
+ write(file,dir3,suffix);
+ else
+ write(file,dir,suffix);
+ }
+ }
+ }
+ bool Center() {
+ return relative && (is3D ? dir3 == (0,0,0) : dir == 0);
+ }
+}
+
+struct side {
+ pair align;
+}
+
+side Relative(explicit pair align)
+{
+ side s;
+ s.align=align;
+ return s;
+}
+
+restricted side NoSide;
+restricted side LeftSide=Relative(W);
+restricted side Center=Relative((0,0));
+restricted side RightSide=Relative(E);
+
+side operator * (real x, side s)
+{
+ side S;
+ S.align=x*s.align;
+ return S;
+}
+
+align operator cast(pair dir) {align A; A.init(dir,false); return A;}
+align operator cast(triple dir) {align A; A.init(dir,false); return A;}
+align operator cast(side side) {align A; A.init(side.align,true); return A;}
+restricted align NoAlign;
+
+void write(file file=stdout, align align, suffix suffix=endl)
+{
+ align.write(file,suffix);
+}
+
+struct position {
+ pair position;
+ bool relative;
+}
+
+position Relative(real position)
+{
+ position p;
+ p.position=position;
+ p.relative=true;
+ return p;
+}
+
+restricted position BeginPoint=Relative(0);
+restricted position MidPoint=Relative(0.5);
+restricted position EndPoint=Relative(1);
+
+position operator cast(pair x) {position P; P.position=x; return P;}
+position operator cast(real x) {return (pair) x;}
+position operator cast(int x) {return (pair) x;}
+
+pair operator cast(position P) {return P.position;}
+
+typedef transform embed(transform);
+transform Shift(transform t) {return identity();}
+transform Rotate(transform t) {return rotation(t);}
+transform Slant(transform t) {return scaleless(t);}
+transform Scale(transform t) {return t;}
+
+embed Rotate(pair z) {
+ return new transform(transform t) {return rotate(degrees(shiftless(t)*z,
+ warn=false));};
+}
+
+path[] texpath(string s, pen p, bool tex=settings.tex != "none",
+ bool bbox=false);
+
+struct Label {
+ string s,size;
+ position position;
+ bool defaultposition=true;
+ align align;
+ pen p=nullpen;
+ transform T;
+ transform3 T3=identity(4);
+ bool defaulttransform=true;
+ bool defaulttransform3=true;
+ embed embed=Rotate; // Shift, Rotate, Slant, or Scale with embedded picture
+ filltype filltype=NoFill;
+
+ void init(string s="", string size="", position position=0,
+ bool defaultposition=true, align align=NoAlign, pen p=nullpen,
+ transform T=identity(), transform3 T3=identity4,
+ bool defaulttransform=true, bool defaulttransform3=true,
+ embed embed=Rotate, filltype filltype=NoFill) {
+ this.s=s;
+ this.size=size;
+ this.position=position;
+ this.defaultposition=defaultposition;
+ this.align=align.copy();
+ this.p=p;
+ this.T=T;
+ this.T3=copy(T3);
+ this.defaulttransform=defaulttransform;
+ this.defaulttransform3=defaulttransform3;
+ this.embed=embed;
+ this.filltype=filltype;
+ }
+
+ void initalign(string s="", string size="", align align, pen p=nullpen,
+ embed embed=Rotate, filltype filltype=NoFill) {
+ init(s,size,align,p,embed,filltype);
+ }
+
+ void transform(transform T) {
+ this.T=T;
+ defaulttransform=false;
+ }
+
+ void transform3(transform3 T) {
+ this.T3=copy(T);
+ defaulttransform3=false;
+ }
+
+ Label copy(transform3 T3=this.T3) {
+ Label L=new Label;
+ L.init(s,size,position,defaultposition,align,p,T,T3,defaulttransform,
+ defaulttransform3,embed,filltype);
+ return L;
+ }
+
+ void position(position pos) {
+ this.position=pos;
+ defaultposition=false;
+ }
+
+ void align(align a) {
+ align.align(a);
+ }
+ void align(align a, align default) {
+ align.align(a,default);
+ }
+
+ void p(pen p0) {
+ if(this.p == nullpen) this.p=p0;
+ }
+
+ void filltype(filltype filltype0) {
+ if(this.filltype == NoFill) this.filltype=filltype0;
+ }
+
+ void label(frame f, transform t=identity(), pair position, pair align) {
+ pen p0=p == nullpen ? currentpen : p;
+ align=length(align)*unit(rotation(t)*align);
+ pair S=t*position+align*labelmargin(p0)+shift(T)*0;
+ if(settings.tex != "none")
+ label(f,s,size,embed(t)*shiftless(T),S,align,p0);
+ else
+ fill(f,align(texpath(s,p0),S,align,p0),p0);
+ }
+
+ void out(frame f, transform t=identity(), pair position=position.position,
+ pair align=align.dir) {
+ if(filltype == NoFill)
+ label(f,t,position,align);
+ else {
+ frame d;
+ label(d,t,position,align);
+ add(f,d,filltype);
+ }
+ }
+
+ void label(picture pic=currentpicture, pair position, pair align) {
+ if(s == "") return;
+ pic.add(new void (frame f, transform t) {
+ out(f,t,position,align);
+ },true);
+ frame f;
+ // Create a picture with label at the origin to extract its bbox truesize.
+ label(f,(0,0),align);
+ pic.addBox(position,position,min(f),max(f));
+ }
+
+ void out(picture pic=currentpicture) {
+ label(pic,position.position,align.dir);
+ }
+
+ void out(picture pic=currentpicture, path g) {
+ bool relative=position.relative;
+ real position=position.position.x;
+ pair Align=align.dir;
+ bool alignrelative=align.relative;
+ if(defaultposition) {relative=true; position=0.5;}
+ if(relative) position=reltime(g,position);
+ if(align.default) {
+ alignrelative=true;
+ Align=position <= sqrtEpsilon ? S :
+ position >= length(g)-sqrtEpsilon ? N : E;
+ }
+
+ pic.add(new void (frame f, transform t) {
+ out(f,t,point(g,position),alignrelative ?
+ inverse(rotation(t))*-Align*dir(t*g,position)*I : Align);
+ },!alignrelative);
+
+ frame f;
+ pair align=alignrelative ? -Align*dir(g,position)*I : Align;
+ label(f,(0,0),align);
+ pair position=point(g,position);
+ pic.addBox(position,position,min(f),max(f));
+ }
+
+ void write(file file=stdout, suffix suffix=endl) {
+ write(file,"\""+s+"\"");
+ if(!defaultposition) write(file,", position=",position.position);
+ if(!align.default) write(file,", align=");
+ write(file,align);
+ if(p != nullpen) write(file,", pen=",p);
+ if(!defaulttransform)
+ write(file,", transform=",T);
+ if(!defaulttransform3) {
+ write(file,", transform3=",endl);
+ write(file,T3);
+ }
+ write(file,"",suffix);
+ }
+
+ real relative() {
+ return defaultposition ? 0.5 : position.position.x;
+ };
+
+ real relative(path g) {
+ return position.relative ? reltime(g,relative()) : relative();
+ };
+}
+
+Label Label;
+
+void add(frame f, transform t=identity(), Label L)
+{
+ L.out(f,t);
+}
+
+void add(picture pic=currentpicture, Label L)
+{
+ L.out(pic);
+}
+
+Label operator * (transform t, Label L)
+{
+ Label tL=L.copy();
+ tL.align.dir=L.align.dir;
+ tL.transform(t*L.T);
+ return tL;
+}
+
+Label operator * (transform3 t, Label L)
+{
+ Label tL=L.copy(t*L.T3);
+ tL.align.dir=L.align.dir;
+ tL.defaulttransform3=false;
+ return tL;
+}
+
+Label Label(string s, string size="", explicit position position,
+ align align=NoAlign, pen p=nullpen, embed embed=Rotate,
+ filltype filltype=NoFill)
+{
+ Label L;
+ L.init(s,size,position,false,align,p,embed,filltype);
+ return L;
+}
+
+Label Label(string s, string size="", pair position, align align=NoAlign,
+ pen p=nullpen, embed embed=Rotate, filltype filltype=NoFill)
+{
+ return Label(s,size,(position) position,align,p,embed,filltype);
+}
+
+Label Label(explicit pair position, align align=NoAlign, pen p=nullpen,
+ embed embed=Rotate, filltype filltype=NoFill)
+{
+ return Label((string) position,position,align,p,embed,filltype);
+}
+
+Label Label(string s="", string size="", align align=NoAlign, pen p=nullpen,
+ embed embed=Rotate, filltype filltype=NoFill)
+{
+ Label L;
+ L.initalign(s,size,align,p,embed,filltype);
+ return L;
+}
+
+Label Label(Label L, align align=NoAlign, pen p=nullpen, embed embed=L.embed,
+ filltype filltype=NoFill)
+{
+ Label L=L.copy();
+ L.align(align);
+ L.p(p);
+ L.embed=embed;
+ L.filltype(filltype);
+ return L;
+}
+
+Label Label(Label L, explicit position position, align align=NoAlign,
+ pen p=nullpen, embed embed=L.embed, filltype filltype=NoFill)
+{
+ Label L=Label(L,align,p,embed,filltype);
+ L.position(position);
+ return L;
+}
+
+Label Label(Label L, pair position, align align=NoAlign,
+ pen p=nullpen, embed embed=L.embed, filltype filltype=NoFill)
+{
+ return Label(L,(position) position,align,p,embed,filltype);
+}
+
+void write(file file=stdout, Label L, suffix suffix=endl)
+{
+ L.write(file,suffix);
+}
+
+void label(frame f, Label L, pair position, align align=NoAlign,
+ pen p=currentpen, filltype filltype=NoFill)
+{
+ add(f,Label(L,position,align,p,filltype));
+}
+
+void label(frame f, Label L, align align=NoAlign,
+ pen p=currentpen, filltype filltype=NoFill)
+{
+ add(f,Label(L,L.position,align,p,filltype));
+}
+
+void label(picture pic=currentpicture, Label L, pair position,
+ align align=NoAlign, pen p=currentpen, filltype filltype=NoFill)
+{
+ Label L=Label(L,position,align,p,filltype);
+ add(pic,L);
+}
+
+void label(picture pic=currentpicture, Label L, align align=NoAlign,
+ pen p=currentpen, filltype filltype=NoFill)
+{
+ label(pic,L,L.position,align,p,filltype);
+}
+
+// Label, but with postscript coords instead of asy
+void label(pair origin, picture pic=currentpicture, Label L, align align=NoAlign,
+ pen p=currentpen, filltype filltype=NoFill)
+{
+ picture opic;
+ label(opic,L,L.position,align,p,filltype);
+ add(pic,opic,origin);
+}
+
+void label(picture pic=currentpicture, Label L, explicit path g,
+ align align=NoAlign, pen p=currentpen, filltype filltype=NoFill)
+{
+ Label L=Label(L,align,p,filltype);
+ L.out(pic,g);
+}
+
+void label(picture pic=currentpicture, Label L, explicit guide g,
+ align align=NoAlign, pen p=currentpen, filltype filltype=NoFill)
+{
+ label(pic,L,(path) g,align,p,filltype);
+}
+
+Label operator cast(string s) {return Label(s);}
+
+// A structure that a string, Label, or frame can be cast to.
+struct object {
+ frame f;
+ Label L=Label;
+ path g; // Bounding path
+
+ void operator init(frame f) {
+ this.f=f;
+ g=box(min(f),max(f));
+ }
+
+ void operator init(Label L) {
+ this.L=L.copy();
+ if(L != Label) L.out(f);
+ g=box(min(f),max(f));
+ }
+}
+
+object operator cast(frame f) {
+ return object(f);
+}
+
+object operator cast(Label L)
+{
+ return object(L);
+}
+
+object operator cast(string s)
+{
+ return object(s);
+}
+
+Label operator cast(object F)
+{
+ return F.L;
+}
+
+frame operator cast(object F)
+{
+ return F.f;
+}
+
+object operator * (transform t, explicit object F)
+{
+ object f;
+ f.f=t*F.f;
+ f.L=t*F.L;
+ f.g=t*F.g;
+ return f;
+}
+
+// Returns a copy of object F aligned in the direction align
+object align(object F, pair align)
+{
+ return shift(F.f,align)*F;
+}
+
+void add(picture dest=currentpicture, object F, pair position=0,
+ bool group=true, filltype filltype=NoFill, bool above=true)
+{
+ add(dest,F.f,position,group,filltype,above);
+}
+
+// Pack a list of objects into a frame.
+frame pack(pair align=2S ... object inset[])
+{
+ frame F;
+ int n=inset.length;
+ pair z;
+ for (int i=0; i < n; ++i) {
+ add(F,inset[i].f,z);
+ z += align+realmult(unit(align),size(inset[i].f));
+ }
+ return F;
+}
+
+path[] texpath(Label L, bool tex=settings.tex != "none", bool bbox=false)
+{
+ struct stringfont
+ {
+ string s;
+ real fontsize;
+ string font;
+
+ void operator init(Label L)
+ {
+ s=replace(L.s,'\n',' ');
+ fontsize=fontsize(L.p);
+ font=font(L.p);
+ }
+
+ pen pen() {return fontsize(fontsize)+fontcommand(font);}
+ }
+
+ bool lexorder(stringfont a, stringfont b) {
+ return a.s < b.s || (a.s == b.s && (a.fontsize < b.fontsize ||
+ (a.fontsize == b.fontsize &&
+ a.font < b.font)));
+ }
+
+ static stringfont[] stringcache;
+ static path[][] pathcache;
+
+ static stringfont[] stringlist;
+ static bool adjust[];
+
+ path[] G;
+
+ stringfont s=stringfont(L);
+ pen p=s.pen();
+
+ int i=search(stringcache,s,lexorder);
+ if(i == -1 || lexorder(stringcache[i],s)) {
+ int k=search(stringlist,s,lexorder);
+ if(k == -1 || lexorder(stringlist[k],s)) {
+ ++k;
+ stringlist.insert(k,s);
+ // PDF tex engines lose track of the baseline.
+ adjust.insert(k,tex && basealign(L.p) == 1 && pdf());
+ }
+ }
+
+ path[] transform(path[] g, Label L) {
+ if(g.length == 0) return g;
+ pair m=min(g);
+ pair M=max(g);
+ pair dir=rectify(inverse(L.T)*-L.align.dir);
+ if(tex && basealign(L.p) == 1)
+ dir -= (0,(1-dir.y)*m.y/(M.y-m.y));
+ pair a=m+realmult(dir,M-m);
+
+ return shift(L.position+L.align.dir*labelmargin(L.p))*L.T*shift(-a)*g;
+ }
+
+ if(tex && bbox) {
+ frame f;
+ label(f,L);
+ return transform(box(min(f),max(f)),L);
+ }
+
+ if(stringlist.length > 0) {
+ path[][] g;
+ int n=stringlist.length;
+ string[] s=new string[n];
+ pen[] p=new pen[n];
+ for(int i=0; i < n; ++i) {
+ stringfont S=stringlist[i];
+ s[i]=adjust[i] ? "."+S.s : S.s;
+ p[i]=adjust[i] ? S.pen()+basealign : S.pen();
+ }
+
+ g=tex ? _texpath(s,p) : textpath(s,p);
+
+ if(tex)
+ for(int i=0; i < n; ++i)
+ if(adjust[i]) {
+ real y=min(g[i][0]).y;
+ g[i].delete(0);
+ g[i]=shift(0,-y)*g[i];
+ }
+
+
+ for(int i=0; i < stringlist.length; ++i) {
+ stringfont s=stringlist[i];
+ int j=search(stringcache,s,lexorder)+1;
+ stringcache.insert(j,s);
+ pathcache.insert(j,g[i]);
+ }
+ stringlist.delete();
+ adjust.delete();
+ }
+
+ return transform(pathcache[search(stringcache,stringfont(L),lexorder)],L);
+}
+
+texpath=new path[](string s, pen p, bool tex=settings.tex != "none", bool bbox=false)
+{
+ return texpath(Label(s,p));
+};
diff --git a/graphics/asymptote/base/plain_arcs.asy b/graphics/asymptote/base/plain_arcs.asy
new file mode 100644
index 0000000000..140bc3cff1
--- /dev/null
+++ b/graphics/asymptote/base/plain_arcs.asy
@@ -0,0 +1,44 @@
+bool CCW=true;
+bool CW=false;
+
+path circle(pair c, real r)
+{
+ return shift(c)*scale(r)*unitcircle;
+}
+
+path ellipse(pair c, real a, real b)
+{
+ return shift(c)*scale(a,b)*unitcircle;
+}
+
+// return an arc centered at c from pair z1 to z2 (assuming |z2-c|=|z1-c|),
+// drawing in the given direction.
+path arc(pair c, explicit pair z1, explicit pair z2, bool direction=CCW)
+{
+ z1 -= c;
+ real r=abs(z1);
+ z1=unit(z1);
+ z2=unit(z2-c);
+
+ real t1=intersect(unitcircle,(0,0)--2*z1)[0];
+ real t2=intersect(unitcircle,(0,0)--2*z2)[0];
+ static int n=length(unitcircle);
+ if(direction) {
+ if (t1 >= t2) t1 -= n;
+ } else if(t2 >= t1) t2 -= n;
+ return shift(c)*scale(r)*subpath(unitcircle,t1,t2);
+}
+
+// return an arc centered at c with radius r from angle1 to angle2 in degrees,
+// drawing in the given direction.
+path arc(pair c, real r, real angle1, real angle2, bool direction)
+{
+ return arc(c,c+r*dir(angle1),c+r*dir(angle2),direction);
+}
+
+// return an arc centered at c with radius r > 0 from angle1 to angle2 in
+// degrees, drawing counterclockwise if angle2 >= angle1 (otherwise clockwise).
+path arc(pair c, real r, real angle1, real angle2)
+{
+ return arc(c,r,angle1,angle2,angle2 >= angle1 ? CCW : CW);
+}
diff --git a/graphics/asymptote/base/plain_arrows.asy b/graphics/asymptote/base/plain_arrows.asy
new file mode 100644
index 0000000000..d7d79217dd
--- /dev/null
+++ b/graphics/asymptote/base/plain_arrows.asy
@@ -0,0 +1,631 @@
+real arrowlength=0.75cm;
+real arrowfactor=15;
+real arrowangle=15;
+real arcarrowfactor=0.5*arrowfactor;
+real arcarrowangle=2*arrowangle;
+real arrowsizelimit=0.5;
+real arrow2sizelimit=1/3;
+real arrowdir=5;
+real arrowbarb=3;
+real arrowhookfactor=1.5;
+real arrowtexfactor=1;
+
+real barfactor=arrowfactor;
+
+real arrowsize(pen p=currentpen)
+{
+ return arrowfactor*linewidth(p);
+}
+
+real arcarrowsize(pen p=currentpen)
+{
+ return arcarrowfactor*linewidth(p);
+}
+
+real barsize(pen p=currentpen)
+{
+ return barfactor*linewidth(p);
+}
+
+struct arrowhead
+{
+ path head(path g, position position=EndPoint, pen p=currentpen,
+ real size=0, real angle=arrowangle);
+ real size(pen p)=arrowsize;
+ real arcsize(pen p)=arcarrowsize;
+ filltype defaultfilltype(pen) {return FillDraw;}
+}
+
+real[] arrowbasepoints(path base, path left, path right, real default=0)
+{
+ real[][] Tl=transpose(intersections(left,base));
+ real[][] Tr=transpose(intersections(right,base));
+ return new real[] {Tl.length > 0 ? Tl[0][0] : default,
+ Tr.length > 0 ? Tr[0][0] : default};
+}
+
+path arrowbase(path r, pair y, real t, real size)
+{
+ pair perp=2*size*I*dir(r,t);
+ return size == 0 ? y : y+perp--y-perp;
+}
+
+arrowhead DefaultHead;
+DefaultHead.head=new path(path g, position position=EndPoint, pen p=currentpen,
+ real size=0, real angle=arrowangle) {
+ if(size == 0) size=DefaultHead.size(p);
+ bool relative=position.relative;
+ real position=position.position.x;
+ if(relative) position=reltime(g,position);
+ path r=subpath(g,position,0);
+ pair x=point(r,0);
+ real t=arctime(r,size);
+ pair y=point(r,t);
+ path base=arrowbase(r,y,t,size);
+ path left=rotate(-angle,x)*r;
+ path right=rotate(angle,x)*r;
+ real[] T=arrowbasepoints(base,left,right);
+ pair denom=point(right,T[1])-y;
+ real factor=denom != 0 ? length((point(left,T[0])-y)/denom) : 1;
+ path left=rotate(-angle*factor,x)*r;
+ path right=rotate(angle*factor,x)*r;
+ real[] T=arrowbasepoints(base,left,right);
+ return subpath(left,0,T[0])--subpath(right,T[1],0)&cycle;
+};
+
+arrowhead SimpleHead;
+SimpleHead.head=new path(path g, position position=EndPoint, pen p=currentpen,
+ real size=0, real angle=arrowangle) {
+ if(size == 0) size=SimpleHead.size(p);
+ bool relative=position.relative;
+ real position=position.position.x;
+ if(relative) position=reltime(g,position);
+ path r=subpath(g,position,0);
+ pair x=point(r,0);
+ real t=arctime(r,size);
+ path left=rotate(-angle,x)*r;
+ path right=rotate(angle,x)*r;
+ return subpath(left,t,0)--subpath(right,0,t);
+};
+
+arrowhead HookHead(real dir=arrowdir, real barb=arrowbarb)
+{
+ arrowhead a;
+ a.head=new path(path g, position position=EndPoint, pen p=currentpen,
+ real size=0, real angle=arrowangle)
+ {
+ if(size == 0) size=a.size(p);
+ angle=min(angle*arrowhookfactor,45);
+ bool relative=position.relative;
+ real position=position.position.x;
+ if(relative) position=reltime(g,position);
+ path r=subpath(g,position,0);
+ pair x=point(r,0);
+ real t=arctime(r,size);
+ pair y=point(r,t);
+ path base=arrowbase(r,y,t,size);
+ path left=rotate(-angle,x)*r;
+ path right=rotate(angle,x)*r;
+ real[] T=arrowbasepoints(base,left,right,1);
+ pair denom=point(right,T[1])-y;
+ real factor=denom != 0 ? length((point(left,T[0])-y)/denom) : 1;
+ path left=rotate(-angle*factor,x)*r;
+ path right=rotate(angle*factor,x)*r;
+ real[] T=arrowbasepoints(base,left,right,1);
+ left=subpath(left,0,T[0]);
+ right=subpath(right,T[1],0);
+ pair pl0=point(left,0), pl1=relpoint(left,1);
+ pair pr0=relpoint(right,0), pr1=relpoint(right,1);
+ pair M=(pl1+pr0)/2;
+ pair v=barb*unit(M-pl0);
+ pl1=pl1+v; pr0=pr0+v;
+ left=pl0{dir(-dir+degrees(M-pl0,false))}..pl1--M;
+ right=M--pr0..pr1{dir(dir+degrees(pr1-M,false))};
+ return left--right&cycle;
+ };
+ return a;
+}
+arrowhead HookHead=HookHead();
+
+arrowhead TeXHead;
+TeXHead.size=new real(pen p)
+{
+ static real hcoef=2.1; // 84/40=abs(base-hint)/base_height
+ return hcoef*arrowtexfactor*linewidth(p);
+};
+TeXHead.arcsize=TeXHead.size;
+
+TeXHead.head=new path(path g, position position=EndPoint, pen p=currentpen,
+ real size=0, real angle=arrowangle) {
+ static real wcoef=1/84; // 1/abs(base-hint)
+ static path texhead=scale(wcoef)*
+ ((0,20) .. controls (-75,75) and (-108,158) ..
+ (-108,166) .. controls (-108,175) and (-100,178) ..
+ (-93,178) .. controls (-82,178) and (-80,173) ..
+ (-77,168) .. controls (-62,134) and (-30,61) ..
+ (70,14) .. controls (82,8) and (84,7) ..
+ (84,0) .. controls (84,-7) and (82,-8) ..
+ (70,-14) .. controls (-30,-61) and (-62,-134) ..
+ (-77,-168) .. controls (-80,-173) and (-82,-178) ..
+ (-93,-178) .. controls (-100,-178) and (-108,-175)..
+ (-108,-166).. controls (-108,-158) and (-75,-75) ..
+ (0,-20)--cycle);
+ if(size == 0) size=TeXHead.size(p);
+ path gp=scale(size)*texhead;
+ bool relative=position.relative;
+ real position=position.position.x;
+ if(relative) position=reltime(g,position);
+ path r=subpath(g,position,0);
+ pair y=point(r,arctime(r,size));
+ return shift(y)*rotate(degrees(-dir(r,arctime(r,0.5*size))))*gp;
+};
+TeXHead.defaultfilltype=new filltype(pen p) {return Fill(p);};
+
+private real position(position position, real size, path g, bool center)
+{
+ bool relative=position.relative;
+ real position=position.position.x;
+ if(relative) {
+ position *= arclength(g);
+ if(center) position += 0.5*size;
+ position=arctime(g,position);
+ } else if(center)
+ position=arctime(g,arclength(subpath(g,0,position))+0.5*size);
+ return position;
+}
+
+void drawarrow(frame f, arrowhead arrowhead=DefaultHead,
+ path g, pen p=currentpen, real size=0,
+ real angle=arrowangle,
+ filltype filltype=null,
+ position position=EndPoint, bool forwards=true,
+ margin margin=NoMargin, bool center=false)
+{
+ if(size == 0) size=arrowhead.size(p);
+ if(filltype == null) filltype=arrowhead.defaultfilltype(p);
+ size=min(arrowsizelimit*arclength(g),size);
+ real position=position(position,size,g,center);
+
+ g=margin(g,p).g;
+ int L=length(g);
+ if(!forwards) {
+ g=reverse(g);
+ position=L-position;
+ }
+ path r=subpath(g,position,0);
+ size=min(arrowsizelimit*arclength(r),size);
+ path head=arrowhead.head(g,position,p,size,angle);
+ bool endpoint=position > L-sqrtEpsilon;
+ if(cyclic(head) && (filltype == NoFill || endpoint)) {
+ if(position > 0)
+ draw(f,subpath(r,arctime(r,size),length(r)),p);
+ if(!endpoint)
+ draw(f,subpath(g,position,L),p);
+ } else draw(f,g,p);
+ filltype.fill(f,head,p+solid);
+}
+
+void drawarrow2(frame f, arrowhead arrowhead=DefaultHead,
+ path g, pen p=currentpen, real size=0,
+ real angle=arrowangle, filltype filltype=null,
+ margin margin=NoMargin)
+{
+ if(size == 0) size=arrowhead.size(p);
+ if(filltype == null) filltype=arrowhead.defaultfilltype(p);
+ g=margin(g,p).g;
+ size=min(arrow2sizelimit*arclength(g),size);
+
+ path r=reverse(g);
+ int L=length(g);
+ path head=arrowhead.head(g,L,p,size,angle);
+ path tail=arrowhead.head(r,L,p,size,angle);
+ if(cyclic(head))
+ draw(f,subpath(r,arctime(r,size),L-arctime(g,size)),p);
+ else draw(f,g,p);
+ filltype.fill(f,head,p+solid);
+ filltype.fill(f,tail,p+solid);
+}
+
+// Add to picture an estimate of the bounding box contribution of arrowhead
+// using the local slope at endpoint and ignoring margin.
+void addArrow(picture pic, arrowhead arrowhead, path g, pen p, real size,
+ real angle, filltype filltype, real position)
+{
+ if(filltype == null) filltype=arrowhead.defaultfilltype(p);
+ pair z=point(g,position);
+ path g=z-(size+linewidth(p))*dir(g,position)--z;
+ frame f;
+ filltype.fill(f,arrowhead.head(g,position,p,size,angle),p);
+ pic.addBox(z,z,min(f)-z,max(f)-z);
+}
+
+picture arrow(arrowhead arrowhead=DefaultHead,
+ path g, pen p=currentpen, real size=0,
+ real angle=arrowangle, filltype filltype=null,
+ position position=EndPoint, bool forwards=true,
+ margin margin=NoMargin, bool center=false)
+{
+ if(size == 0) size=arrowhead.size(p);
+ picture pic;
+ pic.add(new void(frame f, transform t) {
+ drawarrow(f,arrowhead,t*g,p,size,angle,filltype,position,forwards,margin,
+ center);
+ });
+
+ pic.addPath(g,p);
+
+ real position=position(position,size,g,center);
+ path G;
+ if(!forwards) {
+ G=reverse(g);
+ position=length(g)-position;
+ } else G=g;
+ addArrow(pic,arrowhead,G,p,size,angle,filltype,position);
+
+ return pic;
+}
+
+picture arrow2(arrowhead arrowhead=DefaultHead,
+ path g, pen p=currentpen, real size=0,
+ real angle=arrowangle, filltype filltype=null,
+ margin margin=NoMargin)
+{
+ if(size == 0) size=arrowhead.size(p);
+ picture pic;
+ pic.add(new void(frame f, transform t) {
+ drawarrow2(f,arrowhead,t*g,p,size,angle,filltype,margin);
+ });
+
+ pic.addPath(g,p);
+
+ int L=length(g);
+ addArrow(pic,arrowhead,g,p,size,angle,filltype,L);
+ addArrow(pic,arrowhead,reverse(g),p,size,angle,filltype,L);
+
+ return pic;
+}
+
+void bar(picture pic, pair a, pair d, pen p=currentpen)
+{
+ picture opic;
+ Draw(opic,-0.5d--0.5d,p+solid);
+ add(pic,opic,a);
+}
+
+picture bar(pair a, pair d, pen p=currentpen)
+{
+ picture pic;
+ bar(pic,a,d,p);
+ return pic;
+}
+
+typedef bool arrowbar(picture, path, pen, margin);
+
+bool Blank(picture, path, pen, margin)
+{
+ return false;
+}
+
+bool None(picture, path, pen, margin)
+{
+ return true;
+}
+
+arrowbar BeginArrow(arrowhead arrowhead=DefaultHead,
+ real size=0, real angle=arrowangle,
+ filltype filltype=null, position position=BeginPoint)
+{
+ return new bool(picture pic, path g, pen p, margin margin) {
+ add(pic,arrow(arrowhead,g,p,size,angle,filltype,position,forwards=false,
+ margin));
+ return false;
+ };
+}
+
+arrowbar Arrow(arrowhead arrowhead=DefaultHead,
+ real size=0, real angle=arrowangle,
+ filltype filltype=null, position position=EndPoint)
+{
+ return new bool(picture pic, path g, pen p, margin margin) {
+ add(pic,arrow(arrowhead,g,p,size,angle,filltype,position,margin));
+ return false;
+ };
+}
+
+arrowbar EndArrow(arrowhead arrowhead=DefaultHead,
+ real size=0, real angle=arrowangle,
+ filltype filltype=null, position position=EndPoint)=Arrow;
+
+arrowbar MidArrow(arrowhead arrowhead=DefaultHead,
+ real size=0, real angle=arrowangle, filltype filltype=null)
+{
+ return new bool(picture pic, path g, pen p, margin margin) {
+ add(pic,arrow(arrowhead,g,p,size,angle,filltype,MidPoint,margin,
+ center=true));
+ return false;
+ };
+}
+
+arrowbar Arrows(arrowhead arrowhead=DefaultHead,
+ real size=0, real angle=arrowangle,
+ filltype filltype=null)
+{
+ return new bool(picture pic, path g, pen p, margin margin) {
+ add(pic,arrow2(arrowhead,g,p,size,angle,filltype,margin));
+ return false;
+ };
+}
+
+arrowbar BeginArcArrow(arrowhead arrowhead=DefaultHead,
+ real size=0, real angle=arcarrowangle,
+ filltype filltype=null, position position=BeginPoint)
+{
+ return new bool(picture pic, path g, pen p, margin margin) {
+ real size=size == 0 ? arrowhead.arcsize(p) : size;
+ add(pic,arrow(arrowhead,g,p,size,angle,filltype,position,
+ forwards=false,margin));
+ return false;
+ };
+}
+
+arrowbar ArcArrow(arrowhead arrowhead=DefaultHead,
+ real size=0, real angle=arcarrowangle,
+ filltype filltype=null, position position=EndPoint)
+{
+ return new bool(picture pic, path g, pen p, margin margin) {
+ real size=size == 0 ? arrowhead.arcsize(p) : size;
+ add(pic,arrow(arrowhead,g,p,size,angle,filltype,position,margin));
+ return false;
+ };
+}
+
+arrowbar EndArcArrow(arrowhead arrowhead=DefaultHead,
+ real size=0, real angle=arcarrowangle,
+ filltype filltype=null,
+ position position=EndPoint)=ArcArrow;
+
+arrowbar MidArcArrow(arrowhead arrowhead=DefaultHead,
+ real size=0, real angle=arcarrowangle,
+ filltype filltype=null)
+{
+ return new bool(picture pic, path g, pen p, margin margin) {
+ real size=size == 0 ? arrowhead.arcsize(p) : size;
+ add(pic,arrow(arrowhead,g,p,size,angle,filltype,MidPoint,margin,
+ center=true));
+ return false;
+ };
+}
+
+arrowbar ArcArrows(arrowhead arrowhead=DefaultHead,
+ real size=0, real angle=arcarrowangle,
+ filltype filltype=null)
+{
+ return new bool(picture pic, path g, pen p, margin margin) {
+ real size=size == 0 ? arrowhead.arcsize(p) : size;
+ add(pic,arrow2(arrowhead,g,p,size,angle,filltype,margin));
+ return false;
+ };
+}
+
+arrowbar BeginBar(real size=0)
+{
+ return new bool(picture pic, path g, pen p, margin margin) {
+ real size=size == 0 ? barsize(p) : size;
+ bar(pic,point(g,0),size*dir(g,0)*I,p);
+ return true;
+ };
+}
+
+arrowbar Bar(real size=0)
+{
+ return new bool(picture pic, path g, pen p, margin margin) {
+ int L=length(g);
+ real size=size == 0 ? barsize(p) : size;
+ bar(pic,point(g,L),size*dir(g,L)*I,p);
+ return true;
+ };
+}
+
+arrowbar EndBar(real size=0)=Bar;
+
+arrowbar Bars(real size=0)
+{
+ return new bool(picture pic, path g, pen p, margin margin) {
+ real size=size == 0 ? barsize(p) : size;
+ BeginBar(size)(pic,g,p,margin);
+ EndBar(size)(pic,g,p,margin);
+ return true;
+ };
+}
+
+arrowbar BeginArrow=BeginArrow(),
+MidArrow=MidArrow(),
+Arrow=Arrow(),
+EndArrow=Arrow(),
+Arrows=Arrows(),
+BeginArcArrow=BeginArcArrow(),
+MidArcArrow=MidArcArrow(),
+ArcArrow=ArcArrow(),
+EndArcArrow=ArcArrow(),
+ArcArrows=ArcArrows(),
+BeginBar=BeginBar(),
+Bar=Bar(),
+EndBar=Bar(),
+Bars=Bars();
+
+void draw(frame f, path g, pen p=currentpen, arrowbar arrow)
+{
+ picture pic;
+ if(arrow(pic,g,p,NoMargin))
+ draw(f,g,p);
+ add(f,pic.fit());
+}
+
+void draw(picture pic=currentpicture, Label L=null, path g,
+ align align=NoAlign, pen p=currentpen, arrowbar arrow=None,
+ arrowbar bar=None, margin margin=NoMargin, Label legend=null,
+ marker marker=nomarker)
+{
+ // These if statements are ordered in such a way that the most common case
+ // (with just a path and a pen) executes the least bytecode.
+ if (marker == nomarker)
+ {
+ if (arrow == None && bar == None)
+ {
+ if (margin == NoMargin && size(nib(p)) == 0)
+ {
+ pic.addExactAbove(
+ new void(frame f, transform t, transform T, pair, pair) {
+ _draw(f,t*T*g,p);
+ });
+ pic.addPath(g,p);
+
+ // Jumping over else clauses takes time, so test if we can return
+ // here.
+ if (L == null && legend == null)
+ return;
+ }
+ else // With margin or polygonal pen.
+ {
+ _draw(pic, g, p, margin);
+ }
+ }
+ else /* arrow or bar */
+ {
+ // Note we are using & instead of && as both arrow and bar need to be
+ // called.
+ if (arrow(pic, g, p, margin) & bar(pic, g, p, margin))
+ _draw(pic, g, p, margin);
+ }
+
+ if(L != null && L.s != "") {
+ L=L.copy();
+ L.align(align);
+ L.p(p);
+ L.out(pic,g);
+ }
+
+ if(legend != null && legend.s != "") {
+ legend.p(p);
+ pic.legend.push(Legend(legend.s,legend.p,p,marker.f,marker.above));
+ }
+ }
+ else /* marker != nomarker */
+ {
+ if(marker != nomarker && !marker.above) marker.mark(pic,g);
+
+ // Note we are using & instead of && as both arrow and bar need to be
+ // called.
+ if ((arrow == None || arrow(pic, g, p, margin)) &
+ (bar == None || bar(pic, g, p, margin)))
+ {
+ _draw(pic, g, p, margin);
+ }
+
+ if(L != null && L.s != "") {
+ L=L.copy();
+ L.align(align);
+ L.p(p);
+ L.out(pic,g);
+ }
+
+ if(legend != null && legend.s != "") {
+ legend.p(p);
+ pic.legend.push(Legend(legend.s,legend.p,p,marker.f,marker.above));
+ }
+
+ if(marker != nomarker && marker.above) marker.mark(pic,g);
+ }
+}
+
+// Draw a fixed-size line about the user-coordinate 'origin'.
+void draw(pair origin, picture pic=currentpicture, Label L=null, path g,
+ align align=NoAlign, pen p=currentpen, arrowbar arrow=None,
+ arrowbar bar=None, margin margin=NoMargin, Label legend=null,
+ marker marker=nomarker)
+{
+ picture opic;
+ draw(opic,L,g,align,p,arrow,bar,margin,legend,marker);
+ add(pic,opic,origin);
+}
+
+void draw(picture pic=currentpicture, explicit path[] g, pen p=currentpen,
+ Label legend=null, marker marker=nomarker)
+{
+ // This could be optimized to size and draw the entire array as a batch.
+ for(int i=0; i < g.length-1; ++i)
+ draw(pic,g[i],p,marker);
+ if(g.length > 0) draw(pic,g[g.length-1],p,legend,marker);
+}
+
+void draw(picture pic=currentpicture, guide[] g, pen p=currentpen,
+ Label legend=null, marker marker=nomarker)
+{
+ draw(pic,(path[]) g,p,legend,marker);
+}
+
+void draw(pair origin, picture pic=currentpicture, explicit path[] g,
+ pen p=currentpen, Label legend=null, marker marker=nomarker)
+{
+ picture opic;
+ draw(opic,g,p,legend,marker);
+ add(pic,opic,origin);
+}
+
+void draw(pair origin, picture pic=currentpicture, guide[] g, pen p=currentpen,
+ Label legend=null, marker marker=nomarker)
+{
+ draw(origin,pic,(path[]) g,p,legend,marker);
+}
+
+// Align an arrow pointing to b from the direction dir. The arrow is
+// 'length' PostScript units long.
+void arrow(picture pic=currentpicture, Label L=null, pair b, pair dir,
+ real length=arrowlength, align align=NoAlign,
+ pen p=currentpen, arrowbar arrow=Arrow, margin margin=EndMargin)
+{
+ if(L != null && L.s != "") {
+ L=L.copy();
+ if(L.defaultposition) L.position(0);
+ L.align(L.align,dir);
+ L.p(p);
+ }
+ marginT margin=margin(b--b,p); // Extract margin.begin and margin.end
+ pair a=(margin.begin+length+margin.end)*unit(dir);
+ draw(b,pic,L,a--(0,0),align,p,arrow,margin);
+}
+
+// Fit an array of pictures simultaneously using the sizing of picture all.
+frame[] fit2(picture[] pictures, picture all)
+{
+ frame[] out;
+ if(!all.empty2()) {
+ transform t=all.calculateTransform();
+ pair m=all.min(t);
+ pair M=all.max(t);
+ for(picture pic : pictures) {
+ frame f=pic.fit(t);
+ draw(f,m,nullpen);
+ draw(f,M,nullpen);
+ out.push(f);
+ }
+ }
+ return out;
+}
+
+// Fit an array of pictures simultaneously using the size of the first picture.
+// TODO: Remove unused arguments.
+frame[] fit(string prefix="", picture[] pictures, string format="",
+ bool view=true, string options="", string script="",
+ projection P=currentprojection)
+{
+ if(pictures.length == 0)
+ return new frame[];
+
+ picture all;
+ size(all,pictures[0]);
+ for(picture pic : pictures)
+ add(all,pic);
+
+ return fit2(pictures,all);
+}
diff --git a/graphics/asymptote/base/plain_bounds.asy b/graphics/asymptote/base/plain_bounds.asy
new file mode 100644
index 0000000000..b74a6ceee6
--- /dev/null
+++ b/graphics/asymptote/base/plain_bounds.asy
@@ -0,0 +1,788 @@
+include plain_scaling;
+
+// After a transformation, produce new coordinate bounds. For paths that
+// have been added, this is only an approximation since it takes the bounds of
+// their transformed bounding box.
+private void addTransformedCoords(coords2 dest, transform t,
+ coords2 point, coords2 min, coords2 max)
+{
+ dest.push(t, point, point);
+
+ // Add in all 4 corner coords, to properly size rectangular pictures.
+ dest.push(t,min,min);
+ dest.push(t,min,max);
+ dest.push(t,max,min);
+ dest.push(t,max,max);
+}
+
+// Adds another sizing restriction to the coordinates, but only if it is
+// maximal, that is, if under some scaling, this coordinate could be the
+// largest.
+private void addIfMaximal(coord[] coords, real user, real truesize) {
+ // TODO: Test promoting coordinates for efficiency.
+
+ for (coord c : coords)
+ if (user <= c.user && truesize <= c.truesize)
+ // Not maximal.
+ return;
+
+ // The coordinate is not dominated by any existing extreme, so it is
+ // maximal and will be added, but first remove any coords it now dominates.
+ int i = 0;
+ while (i < coords.length) {
+ coord c = coords[i];
+ if (c.user <= user && c.truesize <= truesize)
+ coords.delete(i);
+ else
+ ++i;
+ }
+
+ // Add the coordinate to the extremes.
+ coords.push(coord.build(user, truesize));
+}
+
+private void addIfMaximal(coord[] dest, coord[] src)
+{
+ // This may be inefficient, as it rebuilds the coord struct when adding it.
+ for (coord c : src)
+ addIfMaximal(dest, c.user, c.truesize);
+}
+
+// Same as addIfMaximal, but testing for minimal coords.
+private void addIfMinimal(coord[] coords, real user, real truesize) {
+ for (coord c : coords)
+ if (user >= c.user && truesize >= c.truesize)
+ return;
+
+ int i = 0;
+ while (i < coords.length) {
+ coord c = coords[i];
+ if (c.user >= user && c.truesize >= truesize)
+ coords.delete(i);
+ else
+ ++i;
+ }
+
+ coords.push(coord.build(user, truesize));
+}
+
+private void addIfMinimal(coord[] dest, coord[] src)
+{
+ for (coord c : src)
+ addIfMinimal(dest, c.user, c.truesize);
+}
+
+// This stores a list of sizing bounds for picture data. If the object is
+// frozen, then it cannot be modified further, and therefore can be safely
+// passed by reference and stored in the sizing data for multiple pictures.
+private struct freezableBounds {
+ restricted bool frozen = false;
+ void freeze() {
+ frozen = true;
+ }
+
+ // Optional links to further (frozen) sizing data.
+ private freezableBounds[] links;
+
+ // Links to (frozen) sizing data that is transformed when added here.
+ private static struct transformedBounds {
+ transform t;
+ freezableBounds link;
+ };
+ private transformedBounds[] tlinks;
+
+ // The sizing data. It cannot be modified once this object is frozen.
+ private coords2 point, min, max;
+
+ // A bound represented by a path. Using the path instead of the bounding
+ // box means it will be accurate after a transformation by coordinates.
+ private path[] pathBounds;
+
+ // A bound represented by a path and a pen.
+ // As often many paths use the same pen, we store an array of paths.
+ private static struct pathpen {
+ path[] g; pen p;
+
+ void operator init(path g, pen p) {
+ this.g.push(g);
+ this.p = p;
+ }
+ }
+ private static pathpen operator *(transform t, pathpen pp) {
+ // Should the pen be transformed?
+ pathpen newpp;
+ for (path g : pp.g)
+ newpp.g.push(t*g);
+ newpp.p = pp.p;
+ return newpp;
+ }
+
+ // WARNING: Due to crazy optimizations, if this array is changed between an
+ // empty and non-empty state, the assignment of a method to
+ // addPath(path,pen) must also change.
+ private pathpen[] pathpenBounds;
+
+ // Once frozen, the sizing is immutable, and therefore we can compute and
+ // store the extremal coordinates.
+ public static struct extremes {
+ coord[] left, bottom, right, top;
+
+ void operator init(coord[] left, coord[] bottom,
+ coord[] right, coord[] top) {
+ this.left = left;
+ this.bottom = bottom;
+ this.right = right;
+ this.top = top;
+ }
+
+ }
+ private static void addMaxToExtremes(extremes e, pair user, pair truesize) {
+ addIfMaximal(e.right, user.x, truesize.x);
+ addIfMaximal(e.top, user.y, truesize.y);
+ }
+ private static void addMinToExtremes(extremes e, pair user, pair truesize) {
+ addIfMinimal(e.left, user.x, truesize.x);
+ addIfMinimal(e.bottom, user.y, truesize.y);
+ }
+ private static void addMaxToExtremes(extremes e, coords2 coords) {
+ addIfMaximal(e.right, coords.x);
+ addIfMaximal(e.top, coords.y);
+ }
+ private static void addMinToExtremes(extremes e, coords2 coords) {
+ addIfMinimal(e.left, coords.x);
+ addIfMinimal(e.bottom, coords.y);
+ }
+
+ private extremes cachedExtremes = null;
+
+ // Once frozen, getMutable returns a new object based on this one, which can
+ // be modified.
+ freezableBounds getMutable() {
+ assert(frozen);
+ var f = new freezableBounds;
+ f.links.push(this);
+ return f;
+ }
+
+ freezableBounds transformed(transform t) {
+ // Freeze these bounds, as we are storing a reference to them.
+ freeze();
+
+ var tlink = new transformedBounds;
+ tlink.t = t;
+ tlink.link = this;
+
+ var b = new freezableBounds;
+ b.tlinks.push(tlink);
+
+ return b;
+ }
+
+ void append(freezableBounds b) {
+ // Check that we can modify the object.
+ assert(!frozen);
+
+ //TODO: If b is "small", ie. a single tlink or cliplink, just copy the
+ //link.
+
+ // As we only reference b, we must freeze it to ensure it does not change.
+ b.freeze();
+ links.push(b);
+ }
+
+ void addPoint(pair user, pair truesize) {
+ assert(!frozen);
+ point.push(user, truesize);
+ }
+
+ void addBox(pair userMin, pair userMax, pair trueMin, pair trueMax) {
+ assert(!frozen);
+ this.min.push(userMin, trueMin);
+ this.max.push(userMax, trueMax);
+ }
+
+ void addPath(path g) {
+ // This, and other asserts have been removed to speed things up slightly.
+ //assert(!frozen);
+ this.pathBounds.push(g);
+ }
+
+ void addPath(path[] g) {
+ //assert(!frozen);
+ this.pathBounds.append(g);
+ }
+
+ // To squeeze out a bit more performance, this method is either assigned
+ // addPathToNonEmptyArray or addPathToEmptyArray depending on the state of
+ // the pathpenBounds array.
+ void addPath(path g, pen p);
+
+ private void addPathToNonEmptyArray(path g, pen p) {
+ //assert(!frozen);
+ //assert(!pathpenBounds.empty());
+ var pp = pathpenBounds[0];
+
+ // Test if the pens are equal or have the same bounds.
+ if (pp.p == p || (min(pp.p) == min(p) && max(pp.p) == max(p))) {
+ // If this path has the same pen as the last one, just add it to the
+ // array corresponding to that pen.
+ pp.g.push(g);
+ }
+ else {
+ // A different pen. Start a new bound and put it on the front. Put
+ // the old bound at the end of the array.
+ pathpenBounds[0] = pathpen(g,p);
+ pathpenBounds.push(pp);
+ }
+ }
+ void addPathToEmptyArray(path g, pen p) {
+ //assert(!frozen);
+ //assert(pathpenBounds.empty());
+
+ pathpenBounds.push(pathpen(g,p));
+ addPath = addPathToNonEmptyArray;
+ }
+
+ // Initial setting for addPath.
+ addPath = addPathToEmptyArray;
+
+ // Transform the sizing info by t then add the result to the coords
+ // structure.
+ private void accumulateCoords(transform t, coords2 coords) {
+ for (var link : links)
+ link.accumulateCoords(t, coords);
+
+ for (var tlink : tlinks)
+ tlink.link.accumulateCoords(t*tlink.t, coords);
+
+ addTransformedCoords(coords, t, this.point, this.min, this.max);
+
+ for (var g : pathBounds) {
+ g = t*g;
+ coords.push(min(g), (0,0));
+ coords.push(max(g), (0,0));
+ }
+
+ for (var pp: pathpenBounds) {
+ pair pm = min(pp.p), pM = max(pp.p);
+ for (var g : pp.g) {
+ g = t*g;
+ coords.push(min(g), pm);
+ coords.push(max(g), pM);
+ }
+ }
+ }
+
+ // Add all of the sizing info to the given coords structure.
+ private void accumulateCoords(coords2 coords) {
+ for (var link : links)
+ link.accumulateCoords(coords);
+
+ for (var tlink : tlinks)
+ tlink.link.accumulateCoords(tlink.t, coords);
+
+ coords.append(this.point);
+ coords.append(this.min);
+ coords.append(this.max);
+
+ for (var g : pathBounds) {
+ coords.push(min(g), (0,0));
+ coords.push(max(g), (0,0));
+ }
+
+ for (var pp: pathpenBounds) {
+ pair pm = min(pp.p), pM = max(pp.p);
+ for (var g : pp.g) {
+ coords.push(min(g), pm);
+ coords.push(max(g), pM);
+ }
+ }
+ }
+
+ // Returns all of the coords that this sizing data represents.
+ private coords2 allCoords() {
+ coords2 coords;
+ accumulateCoords(coords);
+ return coords;
+ }
+
+ private void addLocalsToExtremes(transform t, extremes e) {
+ coords2 coords;
+ addTransformedCoords(coords, t, this.point, this.min, this.max);
+ addMinToExtremes(e, coords);
+ addMaxToExtremes(e, coords);
+
+ if (pathBounds.length > 0) {
+ addMinToExtremes(e, minAfterTransform(t, pathBounds), (0,0));
+ addMaxToExtremes(e, maxAfterTransform(t, pathBounds), (0,0));
+ }
+
+ for (var pp : pathpenBounds) {
+ if (pp.g.length > 0) {
+ addMinToExtremes(e, minAfterTransform(t, pp.g), min(pp.p));
+ addMaxToExtremes(e, maxAfterTransform(t, pp.g), max(pp.p));
+ }
+ }
+ }
+
+ private void addToExtremes(transform t, extremes e) {
+ for (var link : links)
+ link.addToExtremes(t, e);
+
+ for (var tlink : tlinks)
+ tlink.link.addToExtremes(t*tlink.t, e);
+
+ addLocalsToExtremes(t, e);
+ }
+
+ private void addLocalsToExtremes(extremes e) {
+ addMinToExtremes(e, point);
+ addMaxToExtremes(e, point);
+ addMinToExtremes(e, min);
+ addMaxToExtremes(e, max);
+
+ if (pathBounds.length > 0) {
+ addMinToExtremes(e, min(pathBounds), (0,0));
+ addMaxToExtremes(e, max(pathBounds), (0,0));
+ }
+
+ for(var pp : pathpenBounds) {
+ pair m=min(pp.p);
+ pair M=max(pp.p);
+ for(path gg : pp.g) {
+ if (size(gg) > 0) {
+ addMinToExtremes(e,min(gg),m);
+ addMaxToExtremes(e,max(gg),M);
+ }
+ }
+ }
+ }
+
+ private void addToExtremes(extremes e) {
+ for (var link : links)
+ link.addToExtremes(e);
+
+ for (var tlink : tlinks)
+ tlink.link.addToExtremes(tlink.t, e);
+
+ addLocalsToExtremes(e);
+ }
+
+ private static void write(extremes e) {
+ static void write(coord[] coords) {
+ for (coord c : coords)
+ write(" " + (string)c.user + " u + " + (string)c.truesize);
+ }
+ write("left:");
+ write(e.left);
+ write("bottom:");
+ write(e.bottom);
+ write("right:");
+ write(e.right);
+ write("top:");
+ write(e.top);
+ }
+
+ // Returns the extremal coordinates of the sizing data.
+ public extremes extremes() {
+ if (cachedExtremes == null) {
+ freeze();
+
+ extremes e;
+ addToExtremes(e);
+ cachedExtremes = e;
+ }
+
+ return cachedExtremes;
+ }
+
+ // Helper functions for computing the usersize bounds. usermin and usermax
+ // would be easily computable from extremes, except that the picture
+ // interface actually allows calls that manually change the usermin and
+ // usermax values. Therefore, we have to compute these values separately.
+ private static struct userbounds {
+ bool areSet=false;
+ pair min;
+ pair max;
+ }
+ private static struct boundsAccumulator {
+ pair[] mins;
+ pair[] maxs;
+
+ void push(pair m, pair M) {
+ mins.push(m);
+ maxs.push(M);
+ }
+
+ void push(userbounds b) {
+ if (b.areSet)
+ push(b.min, b.max);
+ }
+
+ void push(transform t, userbounds b) {
+ if (b.areSet) {
+ pair[] box = { t*(b.min.x,b.max.y), t*b.max,
+ t*b.min, t*(b.max.x,b.min.y) };
+ for (var z : box)
+ push(z,z);
+ }
+ }
+
+ void pushUserCoords(coords2 min, coords2 max) {
+ int n = min.x.length;
+ assert(min.y.length == n);
+ assert(max.x.length == n);
+ assert(max.y.length == n);
+
+ for (int i = 0; i < n; ++i)
+ push((min.x[i].user, min.y[i].user),
+ (max.x[i].user, max.y[i].user));
+ }
+
+ userbounds collapse() {
+ userbounds b;
+ if (mins.length > 0) {
+ b.areSet = true;
+ b.min = minbound(mins);
+ b.max = maxbound(maxs);
+ }
+ else {
+ b.areSet = false;
+ }
+ return b;
+ }
+ }
+
+ // The user bounds already calculated for this data.
+ private userbounds storedUserBounds = null;
+
+ private void accumulateUserBounds(boundsAccumulator acc)
+ {
+ if (storedUserBounds != null) {
+ assert(frozen);
+ acc.push(storedUserBounds);
+ } else {
+ acc.pushUserCoords(point, point);
+ acc.pushUserCoords(min, max);
+ if (pathBounds.length > 0)
+ acc.push(min(pathBounds), max(pathBounds));
+ for (var pp : pathpenBounds)
+ if(size(pp.g) > 0)
+ acc.push(min(pp.g), max(pp.g));
+ for (var link : links)
+ link.accumulateUserBounds(acc);
+
+ // Transforms are handled as they were in the old system.
+ for (var tlink : tlinks) {
+ boundsAccumulator tacc;
+ tlink.link.accumulateUserBounds(tacc);
+ acc.push(tlink.t, tacc.collapse());
+ }
+ }
+ }
+
+ private void computeUserBounds() {
+ freeze();
+ boundsAccumulator acc;
+ accumulateUserBounds(acc);
+ storedUserBounds = acc.collapse();
+ }
+
+ private userbounds userBounds() {
+ if (storedUserBounds == null)
+ computeUserBounds();
+
+ assert(storedUserBounds != null);
+ return storedUserBounds;
+ }
+
+ // userMin/userMax returns the minimal/maximal userspace coordinate of the
+ // sizing data. As coordinates for objects such as labels can have
+ // significant truesize dimensions, this userMin/userMax values may not
+ // correspond closely to the end of the screen, and are of limited use.
+ // userSetx and userSety determine if there is sizing data in order to even
+ // have userMin/userMax defined.
+ public bool userBoundsAreSet() {
+ return userBounds().areSet;
+ }
+
+ public pair userMin() {
+ return userBounds().min;
+ }
+ public pair userMax() {
+ return userBounds().max;
+ }
+
+ // To override the true userMin and userMax bounds, first compute the
+ // userBounds as they should be at this point, then change the values.
+ public void alterUserBound(string which, real val) {
+ // We are changing the bounds data, so it cannot be frozen yet. After the
+ // user bounds are set, however, the sizing data cannot change, so it will
+ // be frozen.
+ assert(!frozen);
+ computeUserBounds();
+ assert(frozen);
+
+ var b = storedUserBounds;
+ if (which == "minx")
+ b.min = (val, b.min.y);
+ else if (which == "miny")
+ b.min = (b.min.x, val);
+ else if (which == "maxx")
+ b.max = (val, b.max.y);
+ else {
+ assert(which == "maxy");
+ b.max = (b.max.x, val);
+ }
+ }
+
+ // A temporary measure. Stuffs all of the data from the links and paths
+ // into the coords.
+ private void flatten() {
+ assert(!frozen);
+
+ // First, compute the user bounds, taking into account any manual
+ // alterations.
+ computeUserBounds();
+
+ // Calculate all coordinates.
+ coords2 coords = allCoords();
+
+ // Erase all the old data.
+ point.erase();
+ min.erase();
+ max.erase();
+ pathBounds.delete();
+ pathpenBounds.delete();
+ addPath = addPathToEmptyArray;
+ links.delete();
+ tlinks.delete();
+
+ // Put all of the coordinates into point.
+ point = coords;
+ }
+
+ void xclip(real Min, real Max) {
+ assert(!frozen);
+ flatten();
+ point.xclip(Min,Max);
+ min.xclip(Min,Max);
+ max.xclip(Min,Max);
+
+ // Cap the userBounds.
+ userbounds b = storedUserBounds;
+ b.min = (max(Min, b.min.x), b.min.y);
+ b.max = (min(Max, b.max.x), b.max.y);
+ }
+
+ void yclip(real Min, real Max) {
+ assert(!frozen);
+ flatten();
+ point.yclip(Min,Max);
+ min.yclip(Min,Max);
+ max.yclip(Min,Max);
+
+ // Cap the userBounds.
+ userbounds b = storedUserBounds;
+ b.min = (b.min.x, max(Min, b.min.y));
+ b.max = (b.max.x, min(Max, b.max.y));
+ }
+
+ // Calculate the min for the final frame, given the coordinate transform.
+ pair min(transform t) {
+ extremes e = extremes();
+ if (e.left.length == 0)
+ return 0;
+
+ pair a=t*(1,1)-t*(0,0), b=t*(0,0);
+ scaling xs=scaling.build(a.x,b.x);
+ scaling ys=scaling.build(a.y,b.y);
+
+ return (min(infinity, xs, e.left), min(infinity, ys, e.bottom));
+ }
+
+ // Calculate the max for the final frame, given the coordinate transform.
+ pair max(transform t) {
+ extremes e = extremes();
+ if (e.right.length == 0)
+ return 0;
+
+ pair a=t*(1,1)-t*(0,0), b=t*(0,0);
+ scaling xs=scaling.build(a.x,b.x);
+ scaling ys=scaling.build(a.y,b.y);
+
+ return (max(-infinity, xs, e.right), max(-infinity, ys, e.top));
+ }
+
+ // Returns the transform for turning user-space pairs into true-space pairs.
+ transform scaling(real xsize, real ysize,
+ real xunitsize, real yunitsize,
+ bool keepAspect, bool warn) {
+ if(xsize == 0 && xunitsize == 0 && ysize == 0 && yunitsize == 0)
+ return identity();
+
+ // Get the extremal coordinates.
+ extremes e = extremes();
+
+ real sx;
+ if(xunitsize == 0) {
+ if(xsize != 0) sx=calculateScaling("x",e.left,e.right,xsize,warn);
+ } else sx=xunitsize;
+
+ /* Possible alternative code :
+ real sx = xunitsize != 0 ? xunitsize :
+ xsize != 0 ? calculateScaling("x", Coords.x, xsize, warn) :
+ 0; */
+
+ real sy;
+ if(yunitsize == 0) {
+ if(ysize != 0) sy=calculateScaling("y",e.bottom,e.top,ysize,warn);
+ } else sy=yunitsize;
+
+ if(sx == 0) {
+ sx=sy;
+ if(sx == 0)
+ return identity();
+ } else if(sy == 0) sy=sx;
+
+
+ if(keepAspect && (xunitsize == 0 || yunitsize == 0))
+ return scale(min(sx,sy));
+ else
+ return scale(sx,sy);
+ }
+}
+
+struct bounds {
+ private var base = new freezableBounds;
+
+ // We should probably put this back into picture.
+ bool exact = true;
+
+ // Called just before modifying the sizing data. It ensures base is
+ // non-frozen.
+ // Note that this is manually inlined for speed reasons in a couple often
+ // called methods below.
+ private void makeMutable() {
+ if (base.frozen)
+ base = base.getMutable();
+ //assert(!base.frozen); // Disabled for speed reasons.
+ }
+
+ void erase() {
+ // Just discard the old bounds.
+ base = new freezableBounds;
+
+ // We don't reset the 'exact' field, for backward compatibility.
+ }
+
+ bounds copy() {
+ // Freeze the underlying bounds and make a shallow copy.
+ base.freeze();
+
+ var b = new bounds;
+ b.base = this.base;
+ b.exact = this.exact;
+ return b;
+ }
+
+ bounds transformed(transform t) {
+ var b = new bounds;
+ b.base = base.transformed(t);
+ b.exact = this.exact;
+ return b;
+ }
+
+ void append(bounds b) {
+ makeMutable();
+ base.append(b.base);
+ }
+
+ void append(transform t, bounds b) {
+ // makeMutable will be called by append.
+ if (t == identity())
+ append(b);
+ else
+ append(b.transformed(t));
+ }
+
+ void addPoint(pair user, pair truesize) {
+ makeMutable();
+ base.addPoint(user, truesize);
+ }
+
+ void addBox(pair userMin, pair userMax, pair trueMin, pair trueMax) {
+ makeMutable();
+ base.addBox(userMin, userMax, trueMin, trueMax);
+ }
+
+ void addPath(path g) {
+ //makeMutable(); // Manually inlined here for speed reasons.
+ if (base.frozen)
+ base = base.getMutable();
+ base.addPath(g);
+ }
+
+ void addPath(path[] g) {
+ //makeMutable(); // Manually inlined here for speed reasons.
+ if (base.frozen)
+ base = base.getMutable();
+ base.addPath(g);
+ }
+
+ void addPath(path g, pen p) {
+ //makeMutable(); // Manually inlined here for speed reasons.
+ if (base.frozen)
+ base = base.getMutable();
+ base.addPath(g, p);
+ }
+
+ public bool userBoundsAreSet() {
+ return base.userBoundsAreSet();
+ }
+ public pair userMin() {
+ return base.userMin();
+ }
+ public pair userMax() {
+ return base.userMax();
+ }
+ public void alterUserBound(string which, real val) {
+ makeMutable();
+ base.alterUserBound(which, val);
+ }
+
+ void xclip(real Min, real Max) {
+ makeMutable();
+ base.xclip(Min,Max);
+ }
+
+ void yclip(real Min, real Max) {
+ makeMutable();
+ base.yclip(Min,Max);
+ }
+
+ void clip(pair Min, pair Max) {
+ // TODO: If the user bounds have been manually altered, they may be
+ // incorrect after the clip.
+ xclip(Min.x,Max.x);
+ yclip(Min.y,Max.y);
+ }
+
+ pair min(transform t) {
+ return base.min(t);
+ }
+
+ pair max(transform t) {
+ return base.max(t);
+ }
+
+ transform scaling(real xsize, real ysize,
+ real xunitsize, real yunitsize,
+ bool keepAspect, bool warn) {
+ return base.scaling(xsize, ysize, xunitsize, yunitsize, keepAspect, warn);
+ }
+}
+
+bounds operator *(transform t, bounds b) {
+ return b.transformed(t);
+}
diff --git a/graphics/asymptote/base/plain_boxes.asy b/graphics/asymptote/base/plain_boxes.asy
new file mode 100644
index 0000000000..c675ab4cde
--- /dev/null
+++ b/graphics/asymptote/base/plain_boxes.asy
@@ -0,0 +1,139 @@
+// Draw and/or fill a box on frame dest using the dimensions of frame src.
+path box(frame dest, frame src=dest, real xmargin=0, real ymargin=xmargin,
+ pen p=currentpen, filltype filltype=NoFill, bool above=true)
+{
+ pair z=(xmargin,ymargin);
+ int sign=filltype == NoFill ? 1 : -1;
+ pair h=0.5*sign*(max(p)-min(p));
+ path g=box(min(src)-h-z,max(src)+h+z);
+ frame F;
+ if(above == false) {
+ filltype.fill(F,g,p);
+ prepend(dest,F);
+ } else filltype.fill(dest,g,p);
+ return g;
+}
+
+path roundbox(frame dest, frame src=dest, real xmargin=0, real ymargin=xmargin,
+ pen p=currentpen, filltype filltype=NoFill, bool above=true)
+{
+ pair m=min(src);
+ pair M=max(src);
+ pair bound=M-m;
+ int sign=filltype == NoFill ? 1 : -1;
+ real a=bound.x+2*xmargin;
+ real b=bound.y+2*ymargin;
+ real ds=0;
+ real dw=min(a,b)*0.3;
+ path g=shift(m-(xmargin,ymargin))*((0,dw)--(0,b-dw){up}..{right}
+ (dw,b)--(a-dw,b){right}..{down}
+ (a,b-dw)--(a,dw){down}..{left}
+ (a-dw,0)--(dw,0){left}..{up}cycle);
+
+ frame F;
+ if(above == false) {
+ filltype.fill(F,g,p);
+ prepend(dest,F);
+ } else filltype.fill(dest,g,p);
+ return g;
+}
+
+path ellipse(frame dest, frame src=dest, real xmargin=0, real ymargin=xmargin,
+ pen p=currentpen, filltype filltype=NoFill, bool above=true)
+{
+ pair m=min(src);
+ pair M=max(src);
+ pair D=M-m;
+ static real factor=0.5*sqrt(2);
+ int sign=filltype == NoFill ? 1 : -1;
+ pair h=0.5*sign*(max(p)-min(p));
+ path g=ellipse(0.5*(M+m),factor*D.x+h.x+xmargin,factor*D.y+h.y+ymargin);
+ frame F;
+ if(above == false) {
+ filltype.fill(F,g,p);
+ prepend(dest,F);
+ } else filltype.fill(dest,g,p);
+ return g;
+}
+
+path box(frame f, Label L, real xmargin=0, real ymargin=xmargin,
+ pen p=currentpen, filltype filltype=NoFill, bool above=true)
+{
+ add(f,L);
+ return box(f,xmargin,ymargin,p,filltype,above);
+}
+
+path roundbox(frame f, Label L, real xmargin=0, real ymargin=xmargin,
+ pen p=currentpen, filltype filltype=NoFill, bool above=true)
+{
+ add(f,L);
+ return roundbox(f,xmargin,ymargin,p,filltype,above);
+}
+
+path ellipse(frame f, Label L, real xmargin=0, real ymargin=xmargin,
+ pen p=currentpen, filltype filltype=NoFill, bool above=true)
+{
+ add(f,L);
+ return ellipse(f,xmargin,ymargin,p,filltype,above);
+}
+
+typedef path envelope(frame dest, frame src=dest, real xmargin=0,
+ real ymargin=xmargin, pen p=currentpen,
+ filltype filltype=NoFill, bool above=true);
+
+object object(Label L, envelope e, real xmargin=0, real ymargin=xmargin,
+ pen p=currentpen, filltype filltype=NoFill, bool above=true)
+{
+ object F;
+ F.L=L.copy();
+ Label L0=L.copy();
+ L0.position(0);
+ L0.p(p);
+ add(F.f,L0);
+ F.g=e(F.f,xmargin,ymargin,p,filltype);
+ return F;
+}
+
+object draw(picture pic=currentpicture, Label L, envelope e,
+ real xmargin=0, real ymargin=xmargin, pen p=currentpen,
+ filltype filltype=NoFill, bool above=true)
+{
+ object F=object(L,e,xmargin,ymargin,p,filltype,above);
+ pic.add(new void (frame f, transform t) {
+ frame d;
+ add(d,t,F.L);
+ e(f,d,xmargin,ymargin,p,filltype,above);
+ add(f,d);
+ },true);
+ pic.addBox(L.position,L.position,min(F.f),max(F.f));
+ return F;
+}
+
+object draw(picture pic=currentpicture, Label L, envelope e, pair position,
+ real xmargin=0, real ymargin=xmargin, pen p=currentpen,
+ filltype filltype=NoFill, bool above=true)
+{
+ return draw(pic,Label(L,position),e,xmargin,ymargin,p,filltype,above);
+}
+
+pair point(object F, pair dir, transform t=identity())
+{
+ pair m=min(F.g);
+ pair M=max(F.g);
+ pair c=0.5*(m+M);
+ pair z=t*F.L.position;
+ real[] T=intersect(F.g,c--2*(m+realmult(rectify(dir),M-m))-c);
+ if(T.length == 0) return z;
+ return z+point(F.g,T[0]);
+}
+
+frame bbox(picture pic=currentpicture,
+ real xmargin=0, real ymargin=xmargin,
+ pen p=currentpen, filltype filltype=NoFill)
+{
+ real penwidth=linewidth(p);
+ frame f=pic.fit(max(pic.xsize-2*(xmargin+penwidth),0),
+ max(pic.ysize-2*(ymargin+penwidth),0));
+ box(f,xmargin,ymargin,p,filltype,above=false);
+ return f;
+}
diff --git a/graphics/asymptote/base/plain_constants.asy b/graphics/asymptote/base/plain_constants.asy
new file mode 100644
index 0000000000..136b354021
--- /dev/null
+++ b/graphics/asymptote/base/plain_constants.asy
@@ -0,0 +1,169 @@
+restricted int undefined=(intMax % 2 == 1) ? intMax : intMax-1;
+
+restricted real inches=72;
+restricted real inch=inches;
+restricted real cm=inches/2.54;
+restricted real mm=0.1cm;
+restricted real bp=1; // A PostScript point.
+restricted real pt=72.0/72.27; // A TeX pt; smaller than a PostScript bp.
+restricted pair I=(0,1);
+
+restricted pair right=(1,0);
+restricted pair left=(-1,0);
+restricted pair up=(0,1);
+restricted pair down=(0,-1);
+
+restricted pair E=(1,0);
+restricted pair N=(0,1);
+restricted pair W=(-1,0);
+restricted pair S=(0,-1);
+
+restricted pair NE=unit(N+E);
+restricted pair NW=unit(N+W);
+restricted pair SW=unit(S+W);
+restricted pair SE=unit(S+E);
+
+restricted pair ENE=unit(E+NE);
+restricted pair NNE=unit(N+NE);
+restricted pair NNW=unit(N+NW);
+restricted pair WNW=unit(W+NW);
+restricted pair WSW=unit(W+SW);
+restricted pair SSW=unit(S+SW);
+restricted pair SSE=unit(S+SE);
+restricted pair ESE=unit(E+SE);
+
+restricted real sqrtEpsilon=sqrt(realEpsilon);
+restricted pair Align=sqrtEpsilon*NE;
+restricted int mantissaBits=ceil(-log(realEpsilon)/log(2))+1;
+
+restricted transform identity;
+restricted transform zeroTransform=(0,0,0,0,0,0);
+
+int min(... int[] a) {return min(a);}
+int max(... int[] a) {return max(a);}
+
+real min(... real[] a) {return min(a);}
+real max(... real[] a) {return max(a);}
+
+bool finite(real x)
+{
+ return abs(x) < infinity;
+}
+
+bool finite(pair z)
+{
+ return abs(z.x) < infinity && abs(z.y) < infinity;
+}
+
+bool finite(triple v)
+{
+ return abs(v.x) < infinity && abs(v.y) < infinity && abs(v.z) < infinity;
+}
+
+restricted file stdin=input();
+restricted file stdout=output();
+
+void none(file file) {}
+void endl(file file) {write(file,'\n',flush);}
+void newl(file file) {write(file,'\n');}
+void DOSendl(file file) {write(file,'\r\n',flush);}
+void DOSnewl(file file) {write(file,'\r\n');}
+void tab(file file) {write(file,'\t');}
+void comma(file file) {write(file,',');}
+typedef void suffix(file);
+
+// Used by interactive write to warn that the outputted type is the resolution
+// of an overloaded name.
+void overloadedMessage(file file) {
+ write(file,' <overloaded>');
+ endl(file);
+}
+
+void write(suffix suffix=endl) {suffix(stdout);}
+void write(file file, suffix suffix=none) {suffix(file);}
+
+path box(pair a, pair b)
+{
+ return a--(b.x,a.y)--b--(a.x,b.y)--cycle;
+}
+
+restricted path unitsquare=box((0,0),(1,1));
+
+restricted path unitcircle=E..N..W..S..cycle;
+restricted real circleprecision=0.0006;
+
+restricted transform invert=reflect((0,0),(1,0));
+
+restricted pen defaultpen;
+
+// A type that takes on one of the values true, false, or default.
+struct bool3 {
+ bool value;
+ bool set;
+}
+
+void write(file file, string s="", bool3 b, suffix suffix=none)
+{
+ if(b.set) write(b.value,suffix);
+ else write("default",suffix);
+}
+
+void write(string s="", bool3 b, suffix suffix=endl)
+{
+ write(stdout,s,b,suffix);
+}
+
+restricted bool3 default;
+
+bool operator cast(bool3 b)
+{
+ return b.set && b.value;
+}
+
+bool3 operator cast(bool b)
+{
+ bool3 B;
+ B.value=b;
+ B.set=true;
+ return B;
+}
+
+bool operator == (bool3 a, bool3 b)
+{
+ return a.set == b.set && (!a.set || (a.value == b.value));
+}
+
+bool operator != (bool3 a, bool3 b)
+{
+ return a.set != b.set || (a.set && (a.value != b.value));
+}
+
+bool operator == (bool3 a, bool b)
+{
+ return a.set && a.value == b;
+}
+
+bool operator != (bool3 a, bool b)
+{
+ return !a.set || a.value != b;
+}
+
+bool operator == (bool a, bool3 b)
+{
+ return b.set && b.value == a;
+}
+
+bool operator != (bool a, bool3 b)
+{
+ return !b.set || b.value != a;
+}
+
+bool[] operator cast(bool3[] b)
+{
+ return sequence(new bool(int i) {return b[i];},b.length);
+}
+
+bool3[] operator cast(bool[] b)
+{
+ return sequence(new bool3(int i) {return b[i];},b.length);
+}
diff --git a/graphics/asymptote/base/plain_debugger.asy b/graphics/asymptote/base/plain_debugger.asy
new file mode 100644
index 0000000000..8568ee0bd8
--- /dev/null
+++ b/graphics/asymptote/base/plain_debugger.asy
@@ -0,0 +1,86 @@
+int debuggerlines=5;
+
+int sourceline(string file, string text)
+{
+ string file=locatefile(file);
+ string[] source=input(file);
+ for(int line=0; line < source.length; ++line)
+ if(find(source[line],text) >= 0) return line+1;
+ write("no matching line in "+file+": \""+text+"\"");
+ return 0;
+}
+
+void stop(string file, string text, code s=quote{})
+{
+ int line=sourceline(file,text);
+ if(line > 0) stop(file,line,s);
+}
+
+void clear(string file, string text)
+{
+ int line=sourceline(file,text);
+ if(line > 0) clear(file,line);
+}
+
+// Enable debugging.
+bool debugging=true;
+
+// Variables used by conditional expressions:
+// e.g. stop("test",2,quote{ignore=(++count <= 10);});
+
+bool ignore;
+int count=0;
+
+string debugger(string file, int line, int column, code s=quote{})
+{
+ int verbose=settings.verbose;
+ settings.verbose=0;
+ _eval(s,true);
+ if(ignore) {
+ ignore=false;
+ settings.verbose=verbose;
+ return "c";
+ }
+ static string s;
+ if(debugging) {
+ static string lastfile;
+ static string[] source;
+ bool help=false;
+ while(true) {
+ if(file != lastfile && file != "-") {source=input(file); lastfile=file;}
+ write();
+ for(int i=max(line-debuggerlines,0); i < min(line,source.length); ++i)
+ write(source[i]);
+ for(int i=0; i < column-1; ++i)
+ write(" ",none);
+ write("^"+(verbose == 5 ? " trace" : ""));
+
+ if(help) {
+ write("c:continue f:file h:help i:inst n:next r:return s:step t:trace q:quit x:exit");
+ help=false;
+ }
+
+ string Prompt=file+": "+(string) line+"."+(string) column;
+ Prompt += "? [%s] ";
+ s=getstring(name="debug",default="h",prompt=Prompt,store=false);
+ if(s == "h" || s == "?") {help=true; continue;}
+ if(s == "c" || s == "s" || s == "n" || s == "i" || s == "f" || s == "r")
+ break;
+ if(s == "q") {debugging=false; abort();} // quit
+ if(s == "x") {debugging=false; return "";} // exit
+ if(s == "t") { // trace
+ if(verbose == 0) {
+ verbose=5;
+ } else {
+ verbose=0;
+ }
+ continue;
+ }
+ _eval(s+";",true);
+ }
+ }
+ settings.verbose=verbose;
+ return s;
+}
+
+atbreakpoint(debugger);
diff --git a/graphics/asymptote/base/plain_filldraw.asy b/graphics/asymptote/base/plain_filldraw.asy
new file mode 100644
index 0000000000..026f2cee6c
--- /dev/null
+++ b/graphics/asymptote/base/plain_filldraw.asy
@@ -0,0 +1,248 @@
+// Draw path g on frame f with user-constructed pen p.
+void makedraw(frame f, path g, pen p, int depth=mantissaBits)
+{
+ if(depth == 0) return;
+ --depth;
+
+ path n=nib(p);
+ for(int i=0; i < size(g); ++i)
+ fill(f,shift(point(g,i))*n,p);
+
+ static real epsilon=1000*realEpsilon;
+ int L=length(g);
+ real stop=L-epsilon;
+ int N=length(n);
+ pair first=point(n,0);
+ pair n0=first;
+
+ for(int i=0; i < N; ++i) {
+ pair n1=point(n,i+1);
+ pair dir=unit(n1-n0);
+ real t=dirtime(g,-dir)-epsilon;
+ if(straight(g,(int) t)) t=ceil(t);
+ if(t > epsilon && t < stop) {
+ makedraw(f,subpath(g,0,t),p,depth);
+ makedraw(f,subpath(g,t,L),p,depth);
+ return;
+ }
+ real t=dirtime(g,dir);
+ if(straight(g,(int) t)) t=ceil(t);
+ if(t > epsilon && t < stop) {
+ makedraw(f,subpath(g,0,t),p,depth);
+ makedraw(f,subpath(g,t,L),p,depth);
+ return;
+ }
+ n0=n1;
+ }
+
+ n0=first;
+ for(int i=0; i < N; ++i) {
+ pair n1=point(n,i+1);
+ fill(f,shift(n0)*g--shift(n1)*reverse(g)--cycle,p);
+ n0=n1;
+ }
+}
+
+void draw(frame f, path g, pen p=currentpen)
+{
+ if(size(nib(p)) == 0) _draw(f,g,p);
+ else {
+ begingroup(f);
+ makedraw(f,g,p);
+ endgroup(f);
+ }
+}
+
+void draw(frame f, explicit path[] g, pen p=currentpen)
+{
+ for(int i=0; i < g.length; ++i) draw(f,g[i],p);
+}
+
+void draw(frame f, guide[] g, pen p=currentpen)
+{
+ for(int i=0; i < g.length; ++i) draw(f,g[i],p);
+}
+
+void filldraw(frame f, path[] g, pen fillpen=currentpen,
+ pen drawpen=currentpen)
+{
+ begingroup(f);
+ fill(f,g,fillpen);
+ draw(f,g,drawpen);
+ endgroup(f);
+}
+
+path[] complement(frame f, path[] g)
+{
+ static pair margin=(0.5,0.5);
+ return box(minbound(min(f),min(g))-margin,maxbound(max(f),max(g))+margin)^^g;
+}
+
+void unfill(frame f, path[] g, bool copy=true)
+{
+ clip(f,complement(f,g),evenodd,copy);
+}
+
+void filloutside(frame f, path[] g, pen p=currentpen, bool copy=true)
+{
+ fill(f,complement(f,g),p+evenodd,copy);
+}
+
+struct filltype
+{
+ typedef void fill2(frame f, path[] g, pen fillpen);
+ fill2 fill2;
+ pen fillpen;
+ pen drawpen;
+
+ int type;
+ static int Fill=1;
+ static int FillDraw=2;
+ static int Draw=3;
+ static int NoFill=4;
+ static int UnFill=5;
+
+ void operator init(int type=0, pen fillpen=nullpen, pen drawpen=nullpen,
+ fill2 fill2) {
+ this.type=type;
+ this.fillpen=fillpen;
+ this.drawpen=drawpen;
+ this.fill2=fill2;
+ }
+ void fill(frame f, path[] g, pen p) {fill2(f,g,p);}
+}
+
+path[] margin(path[] g, real xmargin, real ymargin)
+{
+ if(xmargin != 0 || ymargin != 0) {
+ pair M=max(g);
+ pair m=min(g);
+ real width=M.x-m.x;
+ real height=M.y-m.y;
+ real xfactor=width > 0 ? (width+2xmargin)/width : 1;
+ real yfactor=height > 0 ? (height+2ymargin)/height : 1;
+ g=scale(xfactor,yfactor)*g;
+ g=shift(0.5*(M+m)-0.5*(max(g)+min(g)))*g;
+ }
+ return g;
+}
+
+filltype Fill(real xmargin=0, real ymargin=xmargin, pen p=nullpen)
+{
+ return filltype(filltype.Fill,p,new void(frame f, path[] g, pen fillpen) {
+ if(p != nullpen) fillpen=p;
+ if(fillpen == nullpen) fillpen=currentpen;
+ fill(f,margin(g,xmargin,ymargin),fillpen);
+ });
+}
+
+filltype FillDraw(real xmargin=0, real ymargin=xmargin,
+ pen fillpen=nullpen, pen drawpen=nullpen)
+{
+ return filltype(filltype.FillDraw,fillpen,drawpen,
+ new void(frame f, path[] g, pen Drawpen) {
+ if(drawpen != nullpen) Drawpen=drawpen;
+ pen Fillpen=fillpen == nullpen ? Drawpen : fillpen;
+ if(Fillpen == nullpen) Fillpen=currentpen;
+ if(Drawpen == nullpen) Drawpen=Fillpen;
+ if(cyclic(g[0]))
+ filldraw(f,margin(g,xmargin,ymargin),Fillpen,Drawpen);
+ else
+ draw(f,margin(g,xmargin,ymargin),Drawpen);
+ });
+}
+
+filltype Draw(real xmargin=0, real ymargin=xmargin, pen p=nullpen)
+{
+ return filltype(filltype.Draw,drawpen=p,
+ new void(frame f, path[] g, pen drawpen) {
+ pen drawpen=p == nullpen ? drawpen : p;
+ if(drawpen == nullpen) drawpen=currentpen;
+ draw(f,margin(g,xmargin,ymargin),drawpen);
+ });
+}
+
+filltype NoFill=filltype(filltype.NoFill,new void(frame f, path[] g, pen p) {
+ draw(f,g,p);
+ });
+
+
+filltype UnFill(real xmargin=0, real ymargin=xmargin)
+{
+ return filltype(filltype.UnFill,new void(frame f, path[] g, pen) {
+ unfill(f,margin(g,xmargin,ymargin));
+ });
+}
+
+filltype FillDraw=FillDraw(), Fill=Fill(), Draw=Draw(), UnFill=UnFill();
+
+// Fill varying radially from penc at the center of the bounding box to
+// penr at the edge.
+filltype RadialShade(pen penc, pen penr)
+{
+ return filltype(new void(frame f, path[] g, pen) {
+ pair c=(min(g)+max(g))/2;
+ radialshade(f,g,penc,c,0,penr,c,abs(max(g)-min(g))/2);
+ });
+}
+
+filltype RadialShadeDraw(real xmargin=0, real ymargin=xmargin,
+ pen penc, pen penr, pen drawpen=nullpen)
+{
+ return filltype(new void(frame f, path[] g, pen Drawpen) {
+ if(drawpen != nullpen) Drawpen=drawpen;
+ if(Drawpen == nullpen) Drawpen=penc;
+ pair c=(min(g)+max(g))/2;
+ if(cyclic(g[0]))
+ radialshade(f,margin(g,xmargin,ymargin),penc,c,0,penr,c,
+ abs(max(g)-min(g))/2);
+ draw(f,margin(g,xmargin,ymargin),Drawpen);
+ });
+}
+
+// Fill the region in frame dest underneath frame src and return the
+// boundary of src.
+path fill(frame dest, frame src, filltype filltype=NoFill,
+ real xmargin=0, real ymargin=xmargin)
+{
+ pair z=(xmargin,ymargin);
+ path g=box(min(src)-z,max(src)+z);
+ filltype.fill(dest,g,nullpen);
+ return g;
+}
+
+// Add frame dest to frame src with optional grouping and background fill.
+void add(frame dest, frame src, bool group, filltype filltype=NoFill,
+ bool above=true)
+{
+ if(above) {
+ if(filltype != NoFill) fill(dest,src,filltype);
+ if(group) begingroup(dest);
+ add(dest,src);
+ if(group) endgroup(dest);
+ } else {
+ if(group) {
+ frame f;
+ endgroup(f);
+ prepend(dest,f);
+ }
+ prepend(dest,src);
+ if(group) {
+ frame f;
+ begingroup(f);
+ prepend(dest,f);
+ }
+ if(filltype != NoFill) {
+ frame f;
+ fill(f,src,filltype);
+ prepend(dest,f);
+ }
+ }
+}
+
+void add(frame dest, frame src, filltype filltype,
+ bool above=filltype.type != filltype.UnFill)
+{
+ if(filltype != NoFill) fill(dest,src,filltype);
+ (above ? add : prepend)(dest,src);
+}
diff --git a/graphics/asymptote/base/plain_margins.asy b/graphics/asymptote/base/plain_margins.asy
new file mode 100644
index 0000000000..c452275448
--- /dev/null
+++ b/graphics/asymptote/base/plain_margins.asy
@@ -0,0 +1,99 @@
+struct marginT {
+ path g;
+ real begin,end;
+};
+
+typedef marginT margin(path, pen);
+
+path trim(path g, real begin, real end) {
+ real a=arctime(g,begin);
+ real b=arctime(g,arclength(g)-end);
+ return a <= b ? subpath(g,a,b) : point(g,a);
+}
+
+margin operator +(margin ma, margin mb)
+{
+ return new marginT(path g, pen p) {
+ marginT margin;
+ real ba=ma(g,p).begin < 0 ? 0 : ma(g,p).begin;
+ real bb=mb(g,p).begin < 0 ? 0 : mb(g,p).begin;
+ real ea=ma(g,p).end < 0 ? 0 : ma(g,p).end;
+ real eb=mb(g,p).end < 0 ? 0 : mb(g,p).end;
+ margin.begin=ba+bb;
+ margin.end=ea+eb;
+ margin.g=trim(g,margin.begin,margin.end);
+ return margin;
+ };
+}
+
+margin NoMargin()
+{
+ return new marginT(path g, pen) {
+ marginT margin;
+ margin.begin=margin.end=0;
+ margin.g=g;
+ return margin;
+ };
+}
+
+margin Margin(real begin, real end)
+{
+ return new marginT(path g, pen p) {
+ marginT margin;
+ real factor=labelmargin(p);
+ margin.begin=begin*factor;
+ margin.end=end*factor;
+ margin.g=trim(g,margin.begin,margin.end);
+ return margin;
+ };
+}
+
+margin PenMargin(real begin, real end)
+{
+ return new marginT(path g, pen p) {
+ marginT margin;
+ real factor=linewidth(p);
+ margin.begin=(begin+0.5)*factor;
+ margin.end=(end+0.5)*factor;
+ margin.g=trim(g,margin.begin,margin.end);
+ return margin;
+ };
+}
+
+margin DotMargin(real begin, real end)
+{
+ return new marginT(path g, pen p) {
+ marginT margin;
+ real margindot(real x) {return x > 0 ? dotfactor*x : x;}
+ real factor=linewidth(p);
+ margin.begin=(margindot(begin)+0.5)*factor;
+ margin.end=(margindot(end)+0.5)*factor;
+ margin.g=trim(g,margin.begin,margin.end);
+ return margin;
+ };
+}
+
+margin TrueMargin(real begin, real end)
+{
+ return new marginT(path g, pen p) {
+ marginT margin;
+ margin.begin=begin;
+ margin.end=end;
+ margin.g=trim(g,begin,end);
+ return margin;
+ };
+}
+
+margin NoMargin=NoMargin(),
+ BeginMargin=Margin(1,0),
+ Margin=Margin(0,1),
+ EndMargin=Margin,
+ Margins=Margin(1,1),
+ BeginPenMargin=PenMargin(0.5,-0.5),
+ PenMargin=PenMargin(-0.5,0.5),
+ EndPenMargin=PenMargin,
+ PenMargins=PenMargin(0.5,0.5),
+ BeginDotMargin=DotMargin(0.5,-0.5),
+ DotMargin=DotMargin(-0.5,0.5),
+ EndDotMargin=DotMargin,
+ DotMargins=DotMargin(0.5,0.5);
diff --git a/graphics/asymptote/base/plain_markers.asy b/graphics/asymptote/base/plain_markers.asy
new file mode 100644
index 0000000000..250e1701eb
--- /dev/null
+++ b/graphics/asymptote/base/plain_markers.asy
@@ -0,0 +1,401 @@
+real legendlinelength=50;
+real legendhskip=1.2;
+real legendvskip=legendhskip;
+real legendmargin=10;
+real legendmaxrelativewidth=1;
+
+// Return a unit polygon with n sides.
+path polygon(int n)
+{
+ guide g;
+ for(int i=0; i < n; ++i) g=g--expi(2pi*(i+0.5)/n-0.5*pi);
+ return g--cycle;
+}
+
+// Return a unit n-point cyclic cross, with optional inner radius r and
+// end rounding.
+path cross(int n, bool round=true, real r=0)
+{
+ assert(n > 1);
+ real r=min(r,1);
+ real theta=pi/n;
+ real s=sin(theta);
+ real c=cos(theta);
+ pair z=(c,s);
+ transform mirror=reflect(0,z);
+ pair p1=(r,0);
+ path elementary;
+ if(round) {
+ pair e1=p1+z*max(1-r*(s+c),0);
+ elementary=p1--e1..(c,s)..mirror*e1--mirror*p1;
+ } else {
+ pair p2=p1+z*(max(sqrt(1-(r*s)^2)-r*c),0);
+ elementary=p1--p2--mirror*p2--mirror*p1;
+ }
+
+ guide g;
+ real step=360/n;
+ for(int i=0; i < n; ++i)
+ g=g--rotate(i*step-90)*elementary;
+
+ return g--cycle;
+}
+
+path[] plus=(-1,0)--(1,0)^^(0,-1)--(0,1);
+
+typedef void markroutine(picture pic=currentpicture, frame f, path g);
+
+// On picture pic, add frame f about every node of path g.
+void marknodes(picture pic=currentpicture, frame f, path g) {
+ for(int i=0; i < size(g); ++i)
+ add(pic,f,point(g,i));
+}
+
+// On picture pic, add n copies of frame f to path g, evenly spaced in
+// arclength.
+// If rotated=true, the frame will be rotated by the angle of the tangent
+// to the path at the points where the frame will be added.
+// If centered is true, center the frames within n evenly spaced arclength
+// intervals.
+markroutine markuniform(bool centered=false, int n, bool rotated=false) {
+ return new void(picture pic=currentpicture, frame f, path g) {
+ if(n <= 0) return;
+ void add(real x) {
+ real t=reltime(g,x);
+ add(pic,rotated ? rotate(degrees(dir(g,t)))*f : f,point(g,t));
+ }
+ if(centered) {
+ real width=1/n;
+ for(int i=0; i < n; ++i) add((i+0.5)*width);
+ } else {
+ if(n == 1) add(0.5);
+ else {
+ real width=1/(n-1);
+ for(int i=0; i < n; ++i)
+ add(i*width);
+ }
+ }
+ };
+}
+
+// On picture pic, add frame f at points z(t) for n evenly spaced values of
+// t in [a,b].
+markroutine markuniform(pair z(real t), real a, real b, int n)
+{
+ return new void(picture pic=currentpicture, frame f, path) {
+ real width=b-a;
+ for(int i=0; i <= n; ++i) {
+ add(pic,f,z(a+i/n*width));
+ }
+ };
+}
+
+struct marker {
+ frame f;
+ bool above=true;
+ markroutine markroutine=marknodes;
+ void mark(picture pic=currentpicture, path g) {
+ markroutine(pic,f,g);
+ };
+}
+
+marker marker(frame f=newframe, markroutine markroutine=marknodes,
+ bool above=true)
+{
+ marker m=new marker;
+ m.f=f;
+ m.above=above;
+ m.markroutine=markroutine;
+ return m;
+}
+
+marker marker(path[] g, markroutine markroutine=marknodes, pen p=currentpen,
+ filltype filltype=NoFill, bool above=true)
+{
+ frame f;
+ filltype.fill(f,g,p);
+ return marker(f,markroutine,above);
+}
+
+// On picture pic, add path g with opacity thinning about every node.
+marker markthin(path g, pen p=currentpen,
+ real thin(real fraction)=new real(real x) {return x^2;},
+ filltype filltype=NoFill) {
+ marker M=new marker;
+ M.above=true;
+ filltype.fill(M.f,g,p);
+ real factor=1/abs(size(M.f));
+ M.markroutine=new void(picture pic=currentpicture, frame, path G) {
+ transform t=pic.calculateTransform();
+ int n=size(G);
+ for(int i=0; i < n; ++i) {
+ pair z=point(G,i);
+ frame f;
+ real fraction=1;
+ if(i > 0) fraction=min(fraction,abs(t*(z-point(G,i-1)))*factor);
+ if(i < n-1) fraction=min(fraction,abs(t*(point(G,i+1)-z))*factor);
+ filltype.fill(f,g,p+opacity(thin(fraction)));
+ add(pic,f,point(G,i));
+ }
+ };
+ return M;
+}
+
+marker nomarker;
+
+real circlescale=0.85;
+
+path[] MarkPath={scale(circlescale)*unitcircle,
+ polygon(3),polygon(4),polygon(5),invert*polygon(3),
+ cross(4),cross(6)};
+
+marker[] Mark=sequence(new marker(int i) {return marker(MarkPath[i]);},
+ MarkPath.length);
+
+marker[] MarkFill=sequence(new marker(int i) {return marker(MarkPath[i],Fill);},
+ MarkPath.length-2);
+
+marker Mark(int n)
+{
+ n=n % (Mark.length+MarkFill.length);
+ if(n < Mark.length) return Mark[n];
+ else return MarkFill[n-Mark.length];
+}
+
+picture legenditem(Legend legenditem, real linelength)
+{
+ picture pic;
+ pair z1=(0,0);
+ pair z2=z1+(linelength,0);
+ if(!legenditem.above && !empty(legenditem.mark))
+ marknodes(pic,legenditem.mark,interp(z1,z2,0.5));
+ if(linelength > 0)
+ Draw(pic,z1--z2,legenditem.p);
+ if(legenditem.above && !empty(legenditem.mark))
+ marknodes(pic,legenditem.mark,interp(z1,z2,0.5));
+ if(legenditem.plabel != invisible)
+ label(pic,legenditem.label,z2,E,legenditem.plabel);
+ else
+ label(pic,legenditem.label,z2,E,currentpen);
+ return pic;
+}
+
+picture legend(Legend[] Legend, int perline=1, real linelength,
+ real hskip, real vskip, real maxwidth=0, real maxheight=0,
+ bool hstretch=false, bool vstretch=false)
+{
+ if(maxwidth <= 0) hstretch=false;
+ if(maxheight <= 0) vstretch=false;
+ if(Legend.length <= 1) vstretch=hstretch=false;
+
+ picture inset;
+ size(inset,0,0,IgnoreAspect);
+
+ if(Legend.length == 0)
+ return inset;
+
+ // Check for legend entries with lines:
+ bool bLineEntriesAvailable=false;
+ for(int i=0; i < Legend.length; ++i) {
+ if(Legend[i].p != invisible) {
+ bLineEntriesAvailable=true;
+ break;
+ }
+ }
+
+ real markersize=0;
+ for(int i=0; i < Legend.length; ++i)
+ markersize=max(markersize,size(Legend[i].mark).x);
+
+ // If no legend has a line, set the line length to zero
+ if(!bLineEntriesAvailable)
+ linelength=0;
+
+ linelength=max(linelength,markersize*(linelength == 0 ? 1 : 2));
+
+ // Get the maximum dimensions per legend entry;
+ // calculate line length for a one-line legend
+ real heightPerEntry=0;
+ real widthPerEntry=0;
+ real totalwidth=0;
+ for(int i=0; i < Legend.length; ++i) {
+ picture pic=legenditem(Legend[i],linelength);
+ pair lambda=size(pic);
+ heightPerEntry=max(heightPerEntry,lambda.y);
+ widthPerEntry=max(widthPerEntry,lambda.x);
+ if(Legend[i].p != invisible)
+ totalwidth += lambda.x;
+ else {
+ // Legend entries without leading line need less space in one-line legends
+ picture pic=legenditem(Legend[i],0);
+ totalwidth += size(pic).x;
+ }
+ }
+ // Does everything fit into one line?
+ if(((perline < 1) || (perline >= Legend.length)) &&
+ (maxwidth >= totalwidth+(totalwidth/Legend.length)*
+ (Legend.length-1)*(hskip-1))) {
+ // One-line legend
+ real currPosX=0;
+ real itemDistance;
+ if(hstretch)
+ itemDistance=(maxwidth-totalwidth)/(Legend.length-1);
+ else
+ itemDistance=(totalwidth/Legend.length)*(hskip-1);
+ for(int i=0; i < Legend.length; ++i) {
+ picture pic=legenditem(Legend[i],
+ Legend[i].p == invisible ? 0 : linelength);
+ add(inset,pic,(currPosX,0));
+ currPosX += size(pic).x+itemDistance;
+ }
+ } else {
+ // multiline legend
+ if(maxwidth > 0) {
+ int maxperline=floor(maxwidth/(widthPerEntry*hskip));
+ if((perline < 1) || (perline > maxperline))
+ perline=maxperline;
+ }
+ if(perline < 1) // This means: maxwidth < widthPerEntry
+ perline=1;
+
+ if(perline <= 1) hstretch=false;
+ if(hstretch) hskip=(maxwidth/widthPerEntry-perline)/(perline-1)+1;
+ if(vstretch) {
+ int rows=ceil(Legend.length/perline);
+ vskip=(maxheight/heightPerEntry-rows)/(rows-1)+1;
+ }
+
+ if(hstretch && (perline == 1)) {
+ Draw(inset,(0,0)--(maxwidth,0),invisible());
+ for(int i=0; i < Legend.length; ++i)
+ add(inset,legenditem(Legend[i],linelength),
+ (0.5*(maxwidth-widthPerEntry),
+ -quotient(i,perline)*heightPerEntry*vskip));
+ } else
+ for(int i=0; i < Legend.length; ++i)
+ add(inset,legenditem(Legend[i],linelength),
+ ((i%perline)*widthPerEntry*hskip,
+ -quotient(i,perline)*heightPerEntry*vskip));
+ }
+
+ return inset;
+}
+
+frame legend(picture pic=currentpicture, int perline=1,
+ real xmargin=legendmargin, real ymargin=xmargin,
+ real linelength=legendlinelength,
+ real hskip=legendhskip, real vskip=legendvskip,
+ real maxwidth=perline == 0 ?
+ legendmaxrelativewidth*size(pic).x : 0, real maxheight=0,
+ bool hstretch=false, bool vstretch=false, pen p=currentpen)
+{
+ frame F;
+ if(pic.legend.length == 0) return F;
+ F=legend(pic.legend,perline,linelength,hskip,vskip,
+ max(maxwidth-2xmargin,0),
+ max(maxheight-2ymargin,0),
+ hstretch,vstretch).fit();
+ box(F,xmargin,ymargin,p);
+ return F;
+}
+
+pair[] pairs(real[] x, real[] y)
+{
+ if(x.length != y.length) abort("arrays have different lengths");
+ return sequence(new pair(int i) {return (x[i],y[i]);},x.length);
+}
+
+filltype dotfilltype = Fill;
+
+void dot(frame f, pair z, pen p=currentpen, filltype filltype=dotfilltype)
+{
+ if(filltype == Fill)
+ draw(f,z,dotsize(p)+p);
+ else {
+ real s=0.5*(dotsize(p)-linewidth(p));
+ if(s <= 0) return;
+ path g=shift(z)*scale(s)*unitcircle;
+ begingroup(f);
+ filltype.fill(f,g,p);
+ draw(f,g,p);
+ endgroup(f);
+ }
+}
+
+void dot(picture pic=currentpicture, pair z, pen p=currentpen,
+ filltype filltype=dotfilltype)
+{
+ pic.add(new void(frame f, transform t) {
+ dot(f,t*z,p,filltype);
+ },true);
+ pic.addPoint(z,dotsize(p)+p);
+}
+
+void dot(picture pic=currentpicture, Label L, pair z, align align=NoAlign,
+ string format=defaultformat, pen p=currentpen, filltype filltype=dotfilltype)
+{
+ Label L=L.copy();
+ L.position(z);
+ if(L.s == "") {
+ if(format == "") format=defaultformat;
+ L.s="("+format(format,z.x)+","+format(format,z.y)+")";
+ }
+ L.align(align,E);
+ L.p(p);
+ dot(pic,z,p,filltype);
+ add(pic,L);
+}
+
+void dot(picture pic=currentpicture, Label[] L=new Label[], pair[] z,
+ align align=NoAlign, string format=defaultformat, pen p=currentpen,
+ filltype filltype=dotfilltype)
+{
+ int stop=min(L.length,z.length);
+ for(int i=0; i < stop; ++i)
+ dot(pic,L[i],z[i],align,format,p,filltype);
+ for(int i=stop; i < z.length; ++i)
+ dot(pic,z[i],p,filltype);
+}
+
+void dot(picture pic=currentpicture, Label[] L=new Label[],
+ explicit path g, align align=RightSide, string format=defaultformat,
+ pen p=currentpen, filltype filltype=dotfilltype)
+{
+ int n=size(g);
+ int stop=min(L.length,n);
+ for(int i=0; i < stop; ++i)
+ dot(pic,L[i],point(g,i),-sgn(align.dir.x)*I*dir(g,i),format,p,filltype);
+ for(int i=stop; i < n; ++i)
+ dot(pic,point(g,i),p,filltype);
+}
+
+void dot(picture pic=currentpicture, path[] g, pen p=currentpen,
+ filltype filltype=dotfilltype)
+{
+ for(int i=0; i < g.length; ++i)
+ dot(pic,g[i],p,filltype);
+}
+
+void dot(picture pic=currentpicture, Label L, pen p=currentpen,
+ filltype filltype=dotfilltype)
+{
+ dot(pic,L,L.position,p,filltype);
+}
+
+// A dot in a frame.
+frame dotframe(pen p=currentpen, filltype filltype=dotfilltype)
+{
+ frame f;
+ dot(f,(0,0),p,filltype);
+ return f;
+}
+
+frame dotframe=dotframe();
+
+marker dot(pen p=currentpen, filltype filltype=dotfilltype)
+{
+ return marker(dotframe(p,filltype));
+}
+
+marker dot=dot();
+
diff --git a/graphics/asymptote/base/plain_paths.asy b/graphics/asymptote/base/plain_paths.asy
new file mode 100644
index 0000000000..8bb5250db5
--- /dev/null
+++ b/graphics/asymptote/base/plain_paths.asy
@@ -0,0 +1,397 @@
+path nullpath;
+
+typedef guide interpolate(... guide[]);
+
+// These numbers identify the side of a specifier in an operator spec or
+// operator curl expression:
+// a{out} .. {in}b
+restricted int JOIN_OUT=0;
+restricted int JOIN_IN=1;
+
+// Define a.. tension t ..b to be equivalent to
+// a.. tension t and t ..b
+// and likewise with controls.
+tensionSpecifier operator tension(real t, bool atLeast)
+{
+ return operator tension(t,t,atLeast);
+}
+
+guide operator controls(pair z)
+{
+ return operator controls(z,z);
+}
+
+guide[] operator cast(pair[] z)
+{
+ return sequence(new guide(int i) {return z[i];},z.length);
+}
+
+path[] operator cast(pair[] z)
+{
+ return sequence(new path(int i) {return z[i];},z.length);
+}
+
+path[] operator cast(guide[] g)
+{
+ return sequence(new path(int i) {return g[i];},g.length);
+}
+
+guide[] operator cast(path[] g)
+{
+ return sequence(new guide(int i) {return g[i];},g.length);
+}
+
+path[] operator cast(path p)
+{
+ return new path[] {p};
+}
+
+path[] operator cast(guide g)
+{
+ return new path[] {(path) g};
+}
+
+path[] operator ^^ (path p, path q)
+{
+ return new path[] {p,q};
+}
+
+path[] operator ^^ (path p, explicit path[] q)
+{
+ return concat(new path[] {p},q);
+}
+
+path[] operator ^^ (explicit path[] p, path q)
+{
+ return concat(p,new path[] {q});
+}
+
+path[] operator ^^ (explicit path[] p, explicit path[] q)
+{
+ return concat(p,q);
+}
+
+path[] operator * (transform t, explicit path[] p)
+{
+ return sequence(new path(int i) {return t*p[i];},p.length);
+}
+
+pair[] operator * (transform t, pair[] z)
+{
+ return sequence(new pair(int i) {return t*z[i];},z.length);
+}
+
+void write(file file, string s="", explicit path[] x, suffix suffix=none)
+{
+ write(file,s);
+ if(x.length > 0) write(file,x[0]);
+ for(int i=1; i < x.length; ++i) {
+ write(file,endl);
+ write(file," ^^");
+ write(file,x[i]);
+ }
+ write(file,suffix);
+}
+
+void write(string s="", explicit path[] x, suffix suffix=endl)
+{
+ write(stdout,s,x,suffix);
+}
+
+void write(file file, string s="", explicit guide[] x, suffix suffix=none)
+{
+ write(file,s);
+ if(x.length > 0) write(file,x[0]);
+ for(int i=1; i < x.length; ++i) {
+ write(file,endl);
+ write(file," ^^");
+ write(file,x[i]);
+ }
+ write(file,suffix);
+}
+
+void write(string s="", explicit guide[] x, suffix suffix=endl)
+{
+ write(stdout,s,x,suffix);
+}
+
+interpolate operator ..(tensionSpecifier t)
+{
+ return new guide(... guide[] a) {
+ if(a.length == 0) return nullpath;
+ guide g=a[0];
+ for(int i=1; i < a.length; ++i)
+ g=g..t..a[i];
+ return g;
+ };
+}
+
+interpolate operator ::=operator ..(operator tension(1,true));
+interpolate operator ---=operator ..(operator tension(infinity,true));
+
+// return an arbitrary intersection point of paths p and q
+pair intersectionpoint(path p, path q, real fuzz=-1)
+{
+ real[] t=intersect(p,q,fuzz);
+ if(t.length == 0) abort("paths do not intersect");
+ return point(p,t[0]);
+}
+
+// return an array containing all intersection points of the paths p and q
+pair[] intersectionpoints(path p, path q, real fuzz=-1)
+{
+ real[][] t=intersections(p,q,fuzz);
+ return sequence(new pair(int i) {return point(p,t[i][0]);},t.length);
+}
+
+pair[] intersectionpoints(explicit path[] p, explicit path[] q, real fuzz=-1)
+{
+ pair[] z;
+ for(int i=0; i < p.length; ++i)
+ for(int j=0; j < q.length; ++j)
+ z.append(intersectionpoints(p[i],q[j],fuzz));
+ return z;
+}
+
+struct slice {
+ path before,after;
+}
+
+slice cut(path p, path knife, int n)
+{
+ slice s;
+ real[][] T=intersections(p,knife);
+ if(T.length == 0) {s.before=p; s.after=nullpath; return s;}
+ T.cyclic=true;
+ real t=T[n][0];
+ s.before=subpath(p,0,t);
+ s.after=subpath(p,t,length(p));
+ return s;
+}
+
+slice firstcut(path p, path knife)
+{
+ return cut(p,knife,0);
+}
+
+slice lastcut(path p, path knife)
+{
+ return cut(p,knife,-1);
+}
+
+pair dir(path p)
+{
+ return dir(p,length(p));
+}
+
+pair dir(path p, path q)
+{
+ return unit(dir(p)+dir(q));
+}
+
+// return the point on path p at arclength L
+pair arcpoint(path p, real L)
+{
+ return point(p,arctime(p,L));
+}
+
+// return the direction on path p at arclength L
+pair arcdir(path p, real L)
+{
+ return dir(p,arctime(p,L));
+}
+
+// return the time on path p at the relative fraction l of its arclength
+real reltime(path p, real l)
+{
+ return arctime(p,l*arclength(p));
+}
+
+// return the point on path p at the relative fraction l of its arclength
+pair relpoint(path p, real l)
+{
+ return point(p,reltime(p,l));
+}
+
+// return the direction of path p at the relative fraction l of its arclength
+pair reldir(path p, real l)
+{
+ return dir(p,reltime(p,l));
+}
+
+// return the initial point of path p
+pair beginpoint(path p)
+{
+ return point(p,0);
+}
+
+// return the point on path p at half of its arclength
+pair midpoint(path p)
+{
+ return relpoint(p,0.5);
+}
+
+// return the final point of path p
+pair endpoint(path p)
+{
+ return point(p,length(p));
+}
+
+path operator &(path p, cycleToken tok)
+{
+ int n=length(p);
+ if(n < 0) return nullpath;
+ if(n == 0) return p--cycle;
+ if(cyclic(p)) return p;
+ return straight(p,n-1) ? subpath(p,0,n-1)--cycle :
+ subpath(p,0,n-1)..controls postcontrol(p,n-1) and precontrol(p,n)..cycle;
+}
+
+// return a cyclic path enclosing a region bounded by a list of two or more
+// consecutively intersecting paths
+path buildcycle(... path[] p)
+{
+ int n=p.length;
+ if(n < 2) return nullpath;
+ real[] ta=new real[n];
+ real[] tb=new real[n];
+ if(n == 2) {
+ real[][] t=intersections(p[0],p[1]);
+ if(t.length < 2)
+ return nullpath;
+ int k=t.length-1;
+ ta[0]=t[0][0]; tb[0]=t[k][0];
+ ta[1]=t[k][1]; tb[1]=t[0][1];
+ } else {
+ int j=n-1;
+ for(int i=0; i < n; ++i) {
+ real[][] t=intersections(p[i],p[j]);
+ if(t.length == 0)
+ return nullpath;
+ ta[i]=t[0][0]; tb[j]=t[0][1];
+ j=i;
+ }
+ }
+
+ pair c;
+ for(int i=0; i < n ; ++i)
+ c += point(p[i],ta[i]);
+ c /= n;
+
+ path G;
+ for(int i=0; i < n ; ++i) {
+ real Ta=ta[i];
+ real Tb=tb[i];
+ if(cyclic(p[i])) {
+ int L=length(p[i]);
+ real t=Tb-L;
+ if(abs(c-point(p[i],0.5(Ta+t))) <
+ abs(c-point(p[i],0.5(Ta+Tb)))) Tb=t;
+ while(Tb < Ta) Tb += L;
+ }
+ G=G&subpath(p[i],Ta,Tb);
+ }
+ return G&cycle;
+}
+
+// return 1 if p strictly contains q,
+// -1 if q strictly contains p,
+// 0 otherwise.
+int inside(path p, path q, pen fillrule=currentpen)
+{
+ if(intersect(p,q).length > 0) return 0;
+ if(cyclic(p) && inside(p,point(q,0),fillrule)) return 1;
+ if(cyclic(q) && inside(q,point(p,0),fillrule)) return -1;
+ return 0;
+}
+
+// Return an arbitrary point strictly inside a cyclic path p according to
+// the specified fill rule.
+pair inside(path p, pen fillrule=currentpen)
+{
+ if(!cyclic(p)) abort("path is not cyclic");
+ int n=length(p);
+ for(int i=0; i < n; ++i) {
+ pair z=point(p,i);
+ pair dir=dir(p,i);
+ if(dir == 0) continue;
+ real[] T=intersections(p,z,z+I*dir);
+ // Check midpoints of line segments formed between the
+ // corresponding intersection points and z.
+ for(int j=0; j < T.length; ++j) {
+ if(T[j] != i) {
+ pair w=point(p,T[j]);
+ pair m=0.5*(z+w);
+ if(interior(windingnumber(p,m),fillrule)) return m;
+ }
+ }
+ }
+ // cannot find an interior point: path is degenerate
+ return point(p,0);
+}
+
+// Return all intersection times of path g with the vertical line through (x,0).
+real[] times(path p, real x)
+{
+ return intersections(p,(x,0),(x,1));
+}
+
+// Return all intersection times of path g with the horizontal line through
+// (0,z.y).
+real[] times(path p, explicit pair z)
+{
+ return intersections(p,(0,z.y),(1,z.y));
+}
+
+path randompath(int n, bool cumulate=true, interpolate join=operator ..)
+{
+ guide g;
+ pair w;
+ for(int i=0; i <= n; ++i) {
+ pair z=(unitrand()-0.5,unitrand()-0.5);
+ if(cumulate) w += z;
+ else w=z;
+ g=join(g,w);
+ }
+ return g;
+}
+
+path[] strokepath(path g, pen p=currentpen)
+{
+ path[] G=_strokepath(g,p);
+ if(G.length == 0) return G;
+ pair center(path g) {return 0.5*(min(g)+max(g));}
+ pair center(path[] g) {return 0.5*(min(g)+max(g));}
+ return shift(center(g)-center(G))*G;
+}
+
+real braceinnerangle=radians(60);
+real braceouterangle=radians(70);
+real bracemidangle=radians(0);
+real bracedefaultratio=0.14;
+path brace(pair a, pair b, real amplitude=bracedefaultratio*length(b-a))
+{
+ real length=length(b-a);
+ real sign=sgn(amplitude);
+ real hamplitude=0.5*amplitude;
+ real hlength=0.5*length;
+ path brace;
+ if(abs(amplitude) < bracedefaultratio*length) {
+ real slope=2*bracedefaultratio;
+ real controldist=(abs(hamplitude))/slope;
+ brace=(0,0){expi(sign*braceouterangle)}::
+ {expi(sign*bracemidangle)}(controldist,hamplitude)::
+ {expi(sign*bracemidangle)}(hlength-controldist,hamplitude)::
+ {expi(sign*braceinnerangle)}(hlength,amplitude) {expi(-sign*braceinnerangle)}::
+ {expi(-sign*bracemidangle)}(hlength+controldist,hamplitude)::
+ {expi(-sign*bracemidangle)}(length-controldist,hamplitude)::
+ {expi(-sign*braceouterangle)}(length,0);
+ } else {
+ brace=(0,0){expi(sign*braceouterangle)}::
+ {expi(sign*bracemidangle)}(0.25*length,hamplitude)::
+ {expi(sign*braceinnerangle)}(hlength,amplitude){expi(-sign*braceinnerangle)}::
+ {expi(-sign*bracemidangle)}(0.75*length,hamplitude)::
+ {expi(-sign*braceouterangle)}(length,0);
+ }
+ return shift(a)*rotate(degrees(b-a,warn=false))*brace;
+}
diff --git a/graphics/asymptote/base/plain_pens.asy b/graphics/asymptote/base/plain_pens.asy
new file mode 100644
index 0000000000..b846569658
--- /dev/null
+++ b/graphics/asymptote/base/plain_pens.asy
@@ -0,0 +1,368 @@
+real labelmargin=0.3;
+real dotfactor=6;
+
+pen solid=linetype(new real[]);
+pen dotted=linetype(new real[] {0,4});
+pen dashed=linetype(new real[] {8,8});
+pen longdashed=linetype(new real[] {24,8});
+pen dashdotted=linetype(new real[] {8,8,0,8});
+pen longdashdotted=linetype(new real[] {24,8,0,8});
+
+pen linetype(string pattern, real offset=0, bool scale=true, bool adjust=true)
+{
+ return linetype((real[]) split(pattern),offset,scale,adjust);
+}
+
+void defaultpen(real w) {defaultpen(linewidth(w));}
+pen operator +(pen p, real w) {return p+linewidth(w);}
+pen operator +(real w, pen p) {return linewidth(w)+p;}
+
+pen Dotted(pen p=currentpen) {return linetype(new real[] {0,3})+2*linewidth(p);}
+pen Dotted=Dotted();
+
+restricted pen squarecap=linecap(0);
+restricted pen roundcap=linecap(1);
+restricted pen extendcap=linecap(2);
+
+restricted pen miterjoin=linejoin(0);
+restricted pen roundjoin=linejoin(1);
+restricted pen beveljoin=linejoin(2);
+
+restricted pen zerowinding=fillrule(0);
+restricted pen evenodd=fillrule(1);
+
+bool interior(int windingnumber, pen fillrule)
+{
+ return windingnumber != undefined &&
+ (fillrule(fillrule) == 1 ? windingnumber % 2 == 1 : windingnumber != 0);
+}
+
+restricted pen nobasealign=basealign(0);
+restricted pen basealign=basealign(1);
+
+pen invisible=invisible();
+pen thin() {return settings.thin ? linewidth(0) : defaultpen;}
+pen thick(pen p=currentpen) {return linewidth(linewidth(p));}
+pen nullpen=linewidth(0)+invisible;
+
+pen black=gray(0);
+pen white=gray(1);
+pen gray=gray(0.5);
+
+pen red=rgb(1,0,0);
+pen green=rgb(0,1,0);
+pen blue=rgb(0,0,1);
+
+pen Cyan=cmyk(1,0,0,0);
+pen Magenta=cmyk(0,1,0,0);
+pen Yellow=cmyk(0,0,1,0);
+pen Black=cmyk(0,0,0,1);
+
+pen cyan=rgb(0,1,1);
+pen magenta=rgb(1,0,1);
+pen yellow=rgb(1,1,0);
+
+pen palered=rgb(1,0.75,0.75);
+pen palegreen=rgb(0.75,1,0.75);
+pen paleblue=rgb(0.75,0.75,1);
+pen palecyan=rgb(0.75,1,1);
+pen palemagenta=rgb(1,0.75,1);
+pen paleyellow=rgb(1,1,0.75);
+pen palegray=gray(0.95);
+
+pen lightred=rgb(1,0.5,0.5);
+pen lightgreen=rgb(0.5,1,0.5);
+pen lightblue=rgb(0.5,0.5,1);
+pen lightcyan=rgb(0.5,1,1);
+pen lightmagenta=rgb(1,0.5,1);
+pen lightyellow=rgb(1,1,0.5);
+pen lightgray=gray(0.9);
+
+pen mediumred=rgb(1,0.25,0.25);
+pen mediumgreen=rgb(0.25,1,0.25);
+pen mediumblue=rgb(0.25,0.25,1);
+pen mediumcyan=rgb(0.25,1,1);
+pen mediummagenta=rgb(1,0.25,1);
+pen mediumyellow=rgb(1,1,0.25);
+pen mediumgray=gray(0.75);
+
+pen heavyred=rgb(0.75,0,0);
+pen heavygreen=rgb(0,0.75,0);
+pen heavyblue=rgb(0,0,0.75);
+pen heavycyan=rgb(0,0.75,0.75);
+pen heavymagenta=rgb(0.75,0,0.75);
+pen lightolive=rgb(0.75,0.75,0);
+pen heavygray=gray(0.25);
+
+pen deepred=rgb(0.5,0,0);
+pen deepgreen=rgb(0,0.5,0);
+pen deepblue=rgb(0,0,0.5);
+pen deepcyan=rgb(0,0.5,0.5);
+pen deepmagenta=rgb(0.5,0,0.5);
+pen deepyellow=rgb(0.5,0.5,0);
+pen deepgray=gray(0.1);
+
+pen darkred=rgb(0.25,0,0);
+pen darkgreen=rgb(0,0.25,0);
+pen darkblue=rgb(0,0,0.25);
+pen darkcyan=rgb(0,0.25,0.25);
+pen darkmagenta=rgb(0.25,0,0.25);
+pen darkolive=rgb(0.25,0.25,0);
+pen darkgray=gray(0.05);
+
+pen orange=rgb(1,0.5,0);
+pen fuchsia=rgb(1,0,0.5);
+
+pen chartreuse=rgb(0.5,1,0);
+pen springgreen=rgb(0,1,0.5);
+
+pen purple=rgb(0.5,0,1);
+pen royalblue=rgb(0,0.5,1);
+
+// Synonyms:
+
+pen salmon=lightred;
+pen brown=deepred;
+pen olive=deepyellow;
+pen darkbrown=darkred;
+pen pink=palemagenta;
+pen palegrey=palegray;
+pen lightgrey=lightgray;
+pen mediumgrey=mediumgray;
+pen grey=gray;
+pen heavygrey=heavygray;
+pen deepgrey=deepgray;
+pen darkgrey=darkgray;
+
+// Options for handling label overwriting
+restricted int Allow=0;
+restricted int Suppress=1;
+restricted int SuppressQuiet=2;
+restricted int Move=3;
+restricted int MoveQuiet=4;
+
+pen[] colorPen={red,blue,green,magenta,cyan,orange,purple,brown,
+ deepblue,deepgreen,chartreuse,fuchsia,lightred,
+ lightblue,black,pink,yellow,gray};
+
+colorPen.cyclic=true;
+
+pen[] monoPen={solid,dashed,dotted,longdashed,dashdotted,
+ longdashdotted};
+monoPen.cyclic=true;
+
+pen Pen(int n)
+{
+ return (settings.gray || settings.bw) ? monoPen[n] : colorPen[n];
+}
+
+pen Pentype(int n)
+{
+ return (settings.gray || settings.bw) ? monoPen[n] : monoPen[n]+colorPen[n];
+}
+
+real dotsize(pen p=currentpen)
+{
+ return dotfactor*linewidth(p);
+}
+
+pen fontsize(real size)
+{
+ return fontsize(size,1.2*size);
+}
+
+real labelmargin(pen p=currentpen)
+{
+ return labelmargin*fontsize(p);
+}
+
+void write(file file=stdout, string s="", pen[] p)
+{
+ for(int i=0; i < p.length; ++i)
+ write(file,s,p[i],endl);
+}
+
+void usetypescript(string s, string encoding="")
+{
+ string s="\usetypescript["+s+"]";
+ if(encoding != "") s +="["+encoding+"]";
+ texpreamble(s);
+}
+
+pen font(string name, string options="")
+{
+ // Work around misalignment in ConTeXt switchtobodyfont if font is not found.
+ return fontcommand(settings.tex == "context" ?
+ "\switchtobodyfont["+name+
+ (options == "" ? "" : ","+options)+
+ "]\removeunwantedspaces" :
+ "\font\ASYfont="+name+"\ASYfont");
+}
+
+pen font(string name, real size, string options="")
+{
+ string s=(string) (size/pt)+"pt";
+ if(settings.tex == "context")
+ return fontsize(size)+font(name+","+s,options);
+ return fontsize(size)+font(name+" at "+s);
+}
+
+pen font(string encoding, string family, string series, string shape)
+{
+ return fontcommand("\usefont{"+encoding+"}{"+family+"}{"+series+"}{"+shape+
+ "}");
+}
+
+pen AvantGarde(string series="m", string shape="n")
+{
+ return font("OT1","pag",series,shape);
+}
+pen Bookman(string series="m", string shape="n")
+{
+ return font("OT1","pbk",series,shape);
+}
+pen Courier(string series="m", string shape="n")
+{
+ return font("OT1","pcr",series,shape);
+}
+pen Helvetica(string series="m", string shape="n")
+{
+ return font("OT1","phv",series,shape);
+}
+pen NewCenturySchoolBook(string series="m", string shape="n")
+{
+ return font("OT1","pnc",series,shape);
+}
+pen Palatino(string series="m", string shape="n")
+{
+ return font("OT1","ppl",series,shape);
+}
+pen TimesRoman(string series="m", string shape="n")
+{
+ return font("OT1","ptm",series,shape);
+}
+pen ZapfChancery(string series="m", string shape="n")
+{
+ return font("OT1","pzc",series,shape);
+}
+pen Symbol(string series="m", string shape="n")
+{
+ return font("OT1","psy",series,shape);
+}
+pen ZapfDingbats(string series="m", string shape="n")
+{
+ return font("OT1","pzd",series,shape);
+}
+
+pen squarepen=makepen(shift(-0.5,-0.5)*unitsquare);
+
+struct hsv {
+ real h;
+ real v;
+ real s;
+ void operator init(real h, real s, real v) {
+ this.h=h;
+ this.s=s;
+ this.v=v;
+ }
+ void operator init(pen p) {
+ real[] c=colors(rgb(p));
+ real r=c[0];
+ real g=c[1];
+ real b=c[2];
+ real M=max(r,g,b);
+ real m=min(r,g,b);
+ if(M == m) this.h=0;
+ else {
+ real denom=1/(M-m);
+ if(M == r) {
+ this.h=60*(g-b)*denom;
+ if(g < b) h += 360;
+ } else if(M == g) {
+ this.h=60*(b-r)*denom+120;
+ } else
+ this.h=60*(r-g)*denom+240;
+ }
+ this.s=M == 0 ? 0 : 1-m/M;
+ this.v=M;
+ }
+ // return an rgb pen corresponding to h in [0,360) and s and v in [0,1].
+ pen rgb() {
+ real H=(h % 360)/60;
+ int i=floor(H) % 6;
+ real f=H-i;
+ real[] V={v,v*(1-s),v*(1-(i % 2 == 0 ? 1-f : f)*s)};
+ int[] a={0,2,1,1,2,0};
+ int[] b={2,0,0,2,1,1};
+ int[] c={1,1,2,0,0,2};
+ return rgb(V[a[i]],V[b[i]],V[c[i]]);
+ }
+}
+
+pen operator cast(hsv hsv)
+{
+ return hsv.rgb();
+}
+
+hsv operator cast(pen p)
+{
+ return hsv(p);
+}
+
+real[] rgba(pen p)
+{
+ real[] a=colors(rgb(p));
+ a.push(opacity(p));
+ return a;
+}
+
+pen rgba(real[] a)
+{
+ return rgb(a[0],a[1],a[2])+opacity(a[3]);
+}
+
+// Return a pen corresponding to a given 6-character RGB hexadecimal string.
+pen rgb(string s)
+{
+ int offset=substr(s,0,1) == '#' ? 1 : 0;
+ real value(string s, int i) {return hex(substr(s,2i+offset,2))/255;}
+ return rgb(value(s,0),value(s,1),value(s,2));
+}
+
+pen RGB(int r, int g, int b)
+{
+ return rgb(r/255,g/255,b/255);
+}
+
+pen[] operator +(pen[] a, pen b)
+{
+ return sequence(new pen(int i) {return a[i]+b;},a.length);
+}
+
+pen[] operator +(pen a, pen[] b)
+{
+ return sequence(new pen(int i) {return a+b[i];},b.length);
+}
+
+// Interpolate an array of pens in rgb space using by default their minimum
+// opacity.
+pen mean(pen[] p, real opacity(real[])=min)
+{
+ if(p.length == 0) return nullpen;
+ real[] a=rgba(p[0]);
+ real[] t=new real[p.length];
+ t[0]=a[3];
+ for(int i=1; i < p.length; ++i) {
+ real[] b=rgba(p[i]);
+ a += b;
+ t[i]=b[3];
+ }
+ a /= p.length;
+ return rgb(a[0],a[1],a[2])+opacity(opacity(t));
+}
+
+pen[] mean(pen[][] palette, real opacity(real[])=min)
+{
+ return sequence(new pen(int i) {return mean(palette[i],opacity);},
+ palette.length);
+}
diff --git a/graphics/asymptote/base/plain_picture.asy b/graphics/asymptote/base/plain_picture.asy
new file mode 100644
index 0000000000..025e6741cf
--- /dev/null
+++ b/graphics/asymptote/base/plain_picture.asy
@@ -0,0 +1,1694 @@
+// Pre picture <<<1
+import plain_scaling;
+import plain_bounds;
+
+include plain_prethree;
+
+// This variable is required by asymptote.sty.
+pair viewportsize=0; // Horizontal and vertical viewport limits.
+
+restricted bool Aspect=true;
+restricted bool IgnoreAspect=false;
+
+struct coords3 {
+ coord[] x,y,z;
+ void erase() {
+ x.delete();
+ y.delete();
+ z.delete();
+ }
+ // Only a shallow copy of the individual elements of x and y
+ // is needed since, once entered, they are never modified.
+ coords3 copy() {
+ coords3 c=new coords3;
+ c.x=copy(x);
+ c.y=copy(y);
+ c.z=copy(z);
+ return c;
+ }
+ void append(coords3 c) {
+ x.append(c.x);
+ y.append(c.y);
+ z.append(c.z);
+ }
+ void push(triple user, triple truesize) {
+ x.push(coord.build(user.x,truesize.x));
+ y.push(coord.build(user.y,truesize.y));
+ z.push(coord.build(user.z,truesize.z));
+ }
+ void push(coord cx, coord cy, coord cz) {
+ x.push(cx);
+ y.push(cy);
+ z.push(cz);
+ }
+ void push(transform3 t, coords3 c1, coords3 c2, coords3 c3) {
+ for(int i=0; i < c1.x.length; ++i) {
+ coord cx=c1.x[i], cy=c2.y[i], cz=c3.z[i];
+ triple tinf=shiftless(t)*(0,0,0);
+ triple z=t*(cx.user,cy.user,cz.user);
+ triple w=(cx.truesize,cy.truesize,cz.truesize);
+ w=length(w)*unit(shiftless(t)*w);
+ coord Cx,Cy,Cz;
+ Cx.user=z.x;
+ Cy.user=z.y;
+ Cz.user=z.z;
+ Cx.truesize=w.x;
+ Cy.truesize=w.y;
+ Cz.truesize=w.z;
+ push(Cx,Cy,Cz);
+ }
+ }
+}
+
+// scaleT and Legend <<<
+typedef real scalefcn(real x);
+
+struct scaleT {
+ scalefcn T,Tinv;
+ bool logarithmic;
+ bool automin,automax;
+ void operator init(scalefcn T, scalefcn Tinv, bool logarithmic=false,
+ bool automin=false, bool automax=false) {
+ this.T=T;
+ this.Tinv=Tinv;
+ this.logarithmic=logarithmic;
+ this.automin=automin;
+ this.automax=automax;
+ }
+ scaleT copy() {
+ scaleT dest=scaleT(T,Tinv,logarithmic,automin,automax);
+ return dest;
+ }
+};
+
+scaleT operator init()
+{
+ scaleT S=scaleT(identity,identity);
+ return S;
+}
+
+typedef void boundRoutine();
+
+struct autoscaleT {
+ scaleT scale;
+ scaleT postscale;
+ real tickMin=-infinity, tickMax=infinity;
+ boundRoutine[] bound; // Optional routines to recompute the bounding box.
+ bool automin=false, automax=false;
+ bool automin() {return automin && scale.automin;}
+ bool automax() {return automax && scale.automax;}
+
+ real T(real x) {return postscale.T(scale.T(x));}
+ scalefcn T() {return scale.logarithmic ? postscale.T : T;}
+ real Tinv(real x) {return scale.Tinv(postscale.Tinv(x));}
+
+ autoscaleT copy() {
+ autoscaleT dest=new autoscaleT;
+ dest.scale=scale.copy();
+ dest.postscale=postscale.copy();
+ dest.tickMin=tickMin;
+ dest.tickMax=tickMax;
+ dest.bound=copy(bound);
+ dest.automin=(bool) automin;
+ dest.automax=(bool) automax;
+ return dest;
+ }
+}
+
+struct ScaleT {
+ bool set;
+ autoscaleT x;
+ autoscaleT y;
+ autoscaleT z;
+
+ ScaleT copy() {
+ ScaleT dest=new ScaleT;
+ dest.set=set;
+ dest.x=x.copy();
+ dest.y=y.copy();
+ dest.z=z.copy();
+ return dest;
+ }
+};
+
+struct Legend {
+ string label;
+ pen plabel;
+ pen p;
+ frame mark;
+ bool above;
+ void operator init(string label, pen plabel=currentpen, pen p=nullpen,
+ frame mark=newframe, bool above=true) {
+ this.label=label;
+ this.plabel=plabel;
+ this.p=(p == nullpen) ? plabel : p;
+ this.mark=mark;
+ this.above=above;
+ }
+}
+
+// >>>
+
+// Frame Alignment was here
+
+triple min3(pen p)
+{
+ return linewidth(p)*(-0.5,-0.5,-0.5);
+}
+
+triple max3(pen p)
+{
+ return linewidth(p)*(0.5,0.5,0.5);
+}
+
+// A function that draws an object to frame pic, given that the transform
+// from user coordinates to true-size coordinates is t.
+typedef void drawer(frame f, transform t);
+
+// A generalization of drawer that includes the final frame's bounds.
+// TODO: Add documentation as to what T is.
+typedef void drawerBound(frame f, transform t, transform T, pair lb, pair rt);
+
+struct node {
+ drawerBound d;
+ string key;
+ void operator init(drawerBound d, string key=xasyKEY()) {
+ this.d=d;
+ this.key=key;
+ }
+}
+
+// PairOrTriple <<<1
+// This struct is used to represent a userMin/userMax which serves as both a
+// pair and a triple depending on the context.
+struct pairOrTriple {
+ real x,y,z;
+ void init() { x = y = z = 0; }
+};
+void copyPairOrTriple(pairOrTriple dest, pairOrTriple src)
+{
+ dest.x = src.x;
+ dest.y = src.y;
+ dest.z = src.z;
+}
+pair operator cast (pairOrTriple a) {
+ return (a.x, a.y);
+};
+triple operator cast (pairOrTriple a) {
+ return (a.x, a.y, a.z);
+}
+void write(pairOrTriple a) {
+ write((triple) a);
+}
+
+struct picture { // <<<1
+ // Nodes <<<2
+ // Three-dimensional version of drawer and drawerBound:
+ typedef void drawer3(frame f, transform3 t, picture pic, projection P);
+ typedef void drawerBound3(frame f, transform3 t, transform3 T,
+ picture pic, projection P, triple lb, triple rt);
+
+ struct node3 {
+ drawerBound3 d;
+ string key;
+ void operator init(drawerBound3 d, string key=xasyKEY()) {
+ this.d=d;
+ this.key=key;
+ }
+ }
+
+ // The functions to do the deferred drawing.
+ node[] nodes;
+ node3[] nodes3;
+
+ bool uptodate=true;
+
+ struct bounds3 {
+ coords3 point,min,max;
+ bool exact=true; // An accurate picture bounds is provided by the user.
+ void erase() {
+ point.erase();
+ min.erase();
+ max.erase();
+ }
+ bounds3 copy() {
+ bounds3 b=new bounds3;
+ b.point=point.copy();
+ b.min=min.copy();
+ b.max=max.copy();
+ b.exact=exact;
+ return b;
+ }
+ }
+
+ bounds bounds;
+ bounds3 bounds3;
+
+ // Other Fields <<<2
+ // Transform to be applied to this picture.
+ transform T;
+ transform3 T3;
+
+ // The internal representation of the 3D user bounds.
+ private pairOrTriple umin, umax;
+ private bool usetx, usety, usetz;
+
+ ScaleT scale; // Needed by graph
+ Legend[] legend;
+
+ pair[] clipmax; // Used by beginclip/endclip
+ pair[] clipmin;
+
+ // The maximum sizes in the x, y, and z directions; zero means no restriction.
+ real xsize=0, ysize=0;
+
+ real xsize3=0, ysize3=0, zsize3=0;
+
+ // Fixed unitsizes in the x y, and z directions; zero means use
+ // xsize, ysize, and zsize.
+ real xunitsize=0, yunitsize=0, zunitsize=0;
+
+ // If true, the x and y directions must be scaled by the same amount.
+ bool keepAspect=true;
+
+ // A fixed scaling transform.
+ bool fixed;
+ transform fixedscaling;
+
+ // Init and erase <<<2
+ void init() {
+ umin.init();
+ umax.init();
+ usetx=usety=usetz=false;
+ T3=identity(4);
+ }
+ init();
+
+ // Erase the current picture, retaining bounds.
+ void clear() {
+ nodes.delete();
+ nodes3.delete();
+ legend.delete();
+ }
+
+ // Erase the current picture, retaining any size specification.
+ void erase() {
+ clear();
+ bounds.erase();
+ bounds3.erase();
+ T=identity();
+ scale=new ScaleT;
+ init();
+ }
+
+ // Empty <<<2
+ bool empty2() {
+ return nodes.length == 0;
+ }
+
+ bool empty3() {
+ return nodes3.length == 0;
+ }
+
+ bool empty() {
+ return empty2() && empty3();
+ }
+
+ // User min/max <<<2
+ pair userMin2() {return bounds.userMin(); }
+ pair userMax2() {return bounds.userMax(); }
+
+ bool userSetx2() { return bounds.userBoundsAreSet(); }
+ bool userSety2() { return bounds.userBoundsAreSet(); }
+
+ triple userMin3() { return umin; }
+ triple userMax3() { return umax; }
+
+ bool userSetx3() { return usetx; }
+ bool userSety3() { return usety; }
+ bool userSetz3() { return usetz; }
+
+ private typedef real binop(real, real);
+
+ // Helper functions for finding the minimum/maximum of two data, one of
+ // which may not be defined.
+ private static real merge(real x1, bool set1, real x2, bool set2, binop m)
+ {
+ return set1 ? (set2 ? m(x1,x2) : x1) : x2;
+ }
+ private pairOrTriple userExtreme(pair u2(), triple u3(), binop m)
+ {
+ bool setx2 = userSetx2();
+ bool sety2 = userSety2();
+ bool setx3 = userSetx3();
+ bool sety3 = userSety3();
+
+ pair p;
+ if (setx2 || sety2)
+ p = u2();
+ triple t = u3();
+
+ pairOrTriple r;
+ r.x = merge(p.x, setx2, t.x, setx3, m);
+ r.y = merge(p.y, sety2, t.y, sety3, m);
+ r.z = t.z;
+
+ return r;
+ }
+
+ // The combination of 2D and 3D data.
+ pairOrTriple userMin() {
+ return userExtreme(userMin2, userMin3, min);
+ }
+ pairOrTriple userMax() {
+ return userExtreme(userMax2, userMax3, max);
+ }
+
+ bool userSetx() { return userSetx2() || userSetx3(); }
+ bool userSety() { return userSety2() || userSety3(); }
+ bool userSetz() = userSetz3;
+
+ // Functions for setting the user bounds.
+ void userMinx3(real x) {
+ umin.x=x;
+ usetx=true;
+ }
+
+ void userMiny3(real y) {
+ umin.y=y;
+ usety=true;
+ }
+
+ void userMinz3(real z) {
+ umin.z=z;
+ usetz=true;
+ }
+
+ void userMaxx3(real x) {
+ umax.x=x;
+ usetx=true;
+ }
+
+ void userMaxy3(real y) {
+ umax.y=y;
+ usety=true;
+ }
+
+ void userMaxz3(real z) {
+ umax.z=z;
+ usetz=true;
+ }
+
+ void userMinx2(real x) { bounds.alterUserBound("minx", x); }
+ void userMinx(real x) { userMinx2(x); userMinx3(x); }
+ void userMiny2(real y) { bounds.alterUserBound("miny", y); }
+ void userMiny(real y) { userMiny2(y); userMiny3(y); }
+ void userMaxx2(real x) { bounds.alterUserBound("maxx", x); }
+ void userMaxx(real x) { userMaxx2(x); userMaxx3(x); }
+ void userMaxy2(real y) { bounds.alterUserBound("maxy", y); }
+ void userMaxy(real y) { userMaxy2(y); userMaxy3(y); }
+ void userMinz(real z) = userMinz3;
+ void userMaxz(real z) = userMaxz3;
+
+ void userCorners3(triple c000, triple c001, triple c010, triple c011,
+ triple c100, triple c101, triple c110, triple c111) {
+ umin.x = min(c000.x,c001.x,c010.x,c011.x,c100.x,c101.x,c110.x,c111.x);
+ umin.y = min(c000.y,c001.y,c010.y,c011.y,c100.y,c101.y,c110.y,c111.y);
+ umin.z = min(c000.z,c001.z,c010.z,c011.z,c100.z,c101.z,c110.z,c111.z);
+ umax.x = max(c000.x,c001.x,c010.x,c011.x,c100.x,c101.x,c110.x,c111.x);
+ umax.y = max(c000.y,c001.y,c010.y,c011.y,c100.y,c101.y,c110.y,c111.y);
+ umax.z = max(c000.z,c001.z,c010.z,c011.z,c100.z,c101.z,c110.z,c111.z);
+ }
+
+ // Cache the current user-space bounding box x coodinates
+ void userBoxX3(real min, real max, binop m=min, binop M=max) {
+ if (usetx) {
+ umin.x=m(umin.x,min);
+ umax.x=M(umax.x,max);
+ } else {
+ umin.x=min;
+ umax.x=max;
+ usetx=true;
+ }
+ }
+
+ // Cache the current user-space bounding box y coodinates
+ void userBoxY3(real min, real max, binop m=min, binop M=max) {
+ if (usety) {
+ umin.y=m(umin.y,min);
+ umax.y=M(umax.y,max);
+ } else {
+ umin.y=min;
+ umax.y=max;
+ usety=true;
+ }
+ }
+
+ // Cache the current user-space bounding box z coodinates
+ void userBoxZ3(real min, real max, binop m=min, binop M=max) {
+ if (usetz) {
+ umin.z=m(umin.z,min);
+ umax.z=M(umax.z,max);
+ } else {
+ umin.z=min;
+ umax.z=max;
+ usetz=true;
+ }
+ }
+
+ // Cache the current user-space bounding box
+ void userBox3(triple min, triple max) {
+ userBoxX3(min.x,max.x);
+ userBoxY3(min.y,max.y);
+ userBoxZ3(min.z,max.z);
+ }
+
+ // Add drawer <<<2
+ void add(drawerBound d, bool exact=false, bool above=true) {
+ uptodate=false;
+ if(!exact) bounds.exact=false;
+ if(above)
+ nodes.push(node(d));
+ else
+ nodes.insert(0,node(d));
+ }
+
+ // Faster implementation of most common case.
+ void addExactAbove(drawerBound d) {
+ uptodate=false;
+ nodes.push(node(d));
+ }
+
+ void add(drawer d, bool exact=false, bool above=true) {
+ add(new void(frame f, transform t, transform T, pair, pair) {
+ d(f,t*T);
+ },exact,above);
+ }
+
+ void add(drawerBound3 d, bool exact=false, bool above=true) {
+ uptodate=false;
+ if(!exact) bounds.exact=false;
+ if(above)
+ nodes3.push(node3(d));
+ else
+ nodes3.insert(0,node3(d));
+ }
+
+ void add(drawer3 d, bool exact=false, bool above=true) {
+ add(new void(frame f, transform3 t, transform3 T, picture pic,
+ projection P, triple, triple) {
+ d(f,t*T,pic,P);
+ },exact,above);
+ }
+
+ // Clip <<<2
+ void clip(pair min, pair max, drawer d, bool exact=false) {
+ bounds.clip(min, max);
+ this.add(d,exact);
+ }
+
+ void clip(pair min, pair max, drawerBound d, bool exact=false) {
+ bounds.clip(min, max);
+ this.add(d,exact);
+ }
+
+ // Add sizing <<<2
+ // Add a point to the sizing.
+ void addPoint(pair user, pair truesize=0) {
+ bounds.addPoint(user,truesize);
+ //userBox(user,user);
+ }
+
+ // Add a point to the sizing, accounting also for the size of the pen.
+ void addPoint(pair user, pair truesize=0, pen p) {
+ addPoint(user,truesize+min(p));
+ addPoint(user,truesize+max(p));
+ }
+
+ void addPoint(triple user, triple truesize=(0,0,0)) {
+ bounds3.point.push(user,truesize);
+ userBox3(user,user);
+ }
+
+ void addPoint(triple user, triple truesize=(0,0,0), pen p) {
+ addPoint(user,truesize+min3(p));
+ addPoint(user,truesize+max3(p));
+ }
+
+ // Add a box to the sizing.
+ void addBox(pair userMin, pair userMax, pair trueMin=0, pair trueMax=0) {
+ bounds.addBox(userMin, userMax, trueMin, trueMax);
+ }
+
+ void addBox(triple userMin, triple userMax, triple trueMin=(0,0,0),
+ triple trueMax=(0,0,0)) {
+ bounds3.min.push(userMin,trueMin);
+ bounds3.max.push(userMax,trueMax);
+ userBox3(userMin,userMax);
+ }
+
+ // For speed reason, we unravel the addPath routines from bounds. This
+ // avoids an extra function call.
+ from bounds unravel addPath;
+
+ // Size commands <<<2
+ void size(real x, real y=x, bool keepAspect=this.keepAspect) {
+ if(!empty()) uptodate=false;
+ xsize=x;
+ ysize=y;
+ this.keepAspect=keepAspect;
+ }
+
+ void size3(real x, real y=x, real z=y, bool keepAspect=this.keepAspect) {
+ if(!empty3()) uptodate=false;
+ xsize3=x;
+ ysize3=y;
+ zsize3=z;
+ this.keepAspect=keepAspect;
+ }
+
+ void unitsize(real x, real y=x, real z=y) {
+ uptodate=false;
+ xunitsize=x;
+ yunitsize=y;
+ zunitsize=z;
+ }
+
+ // min/max of picture <<<2
+ // Calculate the min for the final frame, given the coordinate transform.
+ pair min(transform t) {
+ return bounds.min(t);
+ }
+
+ // Calculate the max for the final frame, given the coordinate transform.
+ pair max(transform t) {
+ return bounds.max(t);
+ }
+
+ // Calculate the min for the final frame, given the coordinate transform.
+ triple min(transform3 t) {
+ if(bounds3.min.x.length == 0 && bounds3.point.x.length == 0 &&
+ bounds3.max.x.length == 0) return (0,0,0);
+ triple a=t*(1,1,1)-t*(0,0,0), b=t*(0,0,0);
+ scaling xs=scaling.build(a.x,b.x);
+ scaling ys=scaling.build(a.y,b.y);
+ scaling zs=scaling.build(a.z,b.z);
+ return (min(min(min(infinity,xs,bounds3.point.x),xs,bounds3.min.x),
+ xs,bounds3.max.x),
+ min(min(min(infinity,ys,bounds3.point.y),ys,bounds3.min.y),
+ ys,bounds3.max.y),
+ min(min(min(infinity,zs,bounds3.point.z),zs,bounds3.min.z),
+ zs,bounds3.max.z));
+ }
+
+ // Calculate the max for the final frame, given the coordinate transform.
+ triple max(transform3 t) {
+ if(bounds3.min.x.length == 0 && bounds3.point.x.length == 0 &&
+ bounds3.max.x.length == 0) return (0,0,0);
+ triple a=t*(1,1,1)-t*(0,0,0), b=t*(0,0,0);
+ scaling xs=scaling.build(a.x,b.x);
+ scaling ys=scaling.build(a.y,b.y);
+ scaling zs=scaling.build(a.z,b.z);
+ return (max(max(max(-infinity,xs,bounds3.point.x),xs,bounds3.min.x),
+ xs,bounds3.max.x),
+ max(max(max(-infinity,ys,bounds3.point.y),ys,bounds3.min.y),
+ ys,bounds3.max.y),
+ max(max(max(-infinity,zs,bounds3.point.z),zs,bounds3.min.z),
+ zs,bounds3.max.z));
+ }
+
+ void append(coords3 point, coords3 min, coords3 max, transform3 t,
+ bounds3 bounds)
+ {
+ // Add the coord info to this picture.
+ if(t == identity4) {
+ point.append(bounds.point);
+ min.append(bounds.min);
+ max.append(bounds.max);
+ } else {
+ point.push(t,bounds.point,bounds.point,bounds.point);
+ // Add in all 8 corner points, to properly size cuboid pictures.
+ point.push(t,bounds.min,bounds.min,bounds.min);
+ point.push(t,bounds.min,bounds.min,bounds.max);
+ point.push(t,bounds.min,bounds.max,bounds.min);
+ point.push(t,bounds.min,bounds.max,bounds.max);
+ point.push(t,bounds.max,bounds.min,bounds.min);
+ point.push(t,bounds.max,bounds.min,bounds.max);
+ point.push(t,bounds.max,bounds.max,bounds.min);
+ point.push(t,bounds.max,bounds.max,bounds.max);
+ }
+ }
+
+ // Scaling and Fit <<<2
+ // Returns the transform for turning user-space pairs into true-space pairs.
+ transform scaling(real xsize, real ysize, bool keepAspect=true,
+ bool warn=true) {
+ bounds b = (T == identity()) ? this.bounds : T * this.bounds;
+
+ return b.scaling(xsize, ysize, xunitsize, yunitsize, keepAspect, warn);
+ }
+
+ transform scaling(bool warn=true) {
+ return scaling(xsize,ysize,keepAspect,warn);
+ }
+
+ // Returns the transform for turning user-space pairs into true-space triples.
+ transform3 scaling(real xsize, real ysize, real zsize, bool keepAspect=true,
+ bool warn=true) {
+ if(xsize == 0 && xunitsize == 0 && ysize == 0 && yunitsize == 0
+ && zsize == 0 && zunitsize == 0)
+ return identity(4);
+
+ coords3 Coords;
+
+ append(Coords,Coords,Coords,T3,bounds3);
+
+ real sx;
+ if(xunitsize == 0) {
+ if(xsize != 0) sx=calculateScaling("x",Coords.x,xsize,warn);
+ } else sx=xunitsize;
+
+ real sy;
+ if(yunitsize == 0) {
+ if(ysize != 0) sy=calculateScaling("y",Coords.y,ysize,warn);
+ } else sy=yunitsize;
+
+ real sz;
+ if(zunitsize == 0) {
+ if(zsize != 0) sz=calculateScaling("z",Coords.z,zsize,warn);
+ } else sz=zunitsize;
+
+ if(sx == 0) {
+ sx=max(sy,sz);
+ if(sx == 0)
+ return identity(4);
+ }
+ if(sy == 0) sy=max(sz,sx);
+ if(sz == 0) sz=max(sx,sy);
+
+ if(keepAspect && (xunitsize == 0 || yunitsize == 0 || zunitsize == 0))
+ return scale3(min(sx,sy,sz));
+ else
+ return scale(sx,sy,sz);
+ }
+
+ transform3 scaling3(bool warn=true) {
+ return scaling(xsize3,ysize3,zsize3,keepAspect,warn);
+ }
+
+ frame fit(transform t, transform T0=T, pair m, pair M) {
+ frame f;
+ for(node n : nodes) {
+ xasyKEY(n.key);
+ n.d(f,t,T0,m,M);
+ }
+ return f;
+ }
+
+ frame fit3(transform3 t, transform3 T0=T3, picture pic, projection P,
+ triple m, triple M) {
+ frame f;
+ for(node3 n : nodes3) {
+ xasyKEY(n.key);
+ n.d(f,t,T0,pic,P,m,M);
+ }
+ return f;
+ }
+
+ // Returns a rigid version of the picture using t to transform user coords
+ // into truesize coords.
+ frame fit(transform t) {
+ return fit(t,min(t),max(t));
+ }
+
+ frame fit3(transform3 t, picture pic, projection P) {
+ return fit3(t,pic,P,min(t),max(t));
+ }
+
+ // Add drawer wrappers <<<2
+ void add(void d(picture, transform), bool exact=false) {
+ add(new void(frame f, transform t) {
+ picture opic=new picture;
+ d(opic,t);
+ add(f,opic.fit(identity()));
+ },exact);
+ }
+
+ void add(void d(picture, transform3), bool exact=false, bool above=true) {
+ add(new void(frame f, transform3 t, picture pic2, projection P) {
+ picture opic=new picture;
+ d(opic,t);
+ add(f,opic.fit3(identity4,pic2,P));
+ },exact,above);
+ }
+
+ void add(void d(picture, transform3, transform3, triple, triple),
+ bool exact=false, bool above=true) {
+ add(new void(frame f, transform3 t, transform3 T, picture pic2,
+ projection P, triple lb, triple rt) {
+ picture opic=new picture;
+ d(opic,t,T,lb,rt);
+ add(f,opic.fit3(identity4,pic2,P));
+ },exact,above);
+ }
+
+ // More scaling <<<2
+ frame scaled() {
+ frame f=fit(fixedscaling);
+ pair d=size(f);
+ static real epsilon=100*realEpsilon;
+ if(d.x > xsize*(1+epsilon))
+ warning("xlimit","frame exceeds xlimit: "+(string) d.x+" > "+
+ (string) xsize);
+ if(d.y > ysize*(1+epsilon))
+ warning("ylimit","frame exceeds ylimit: "+(string) d.y+" > "+
+ (string) ysize);
+ return f;
+ }
+
+ // Calculate additional scaling required if only an approximate picture
+ // size estimate is available.
+ transform scale(frame f, real xsize=this.xsize, real ysize=this.ysize,
+ bool keepaspect=this.keepAspect) {
+ if(bounds.exact) return identity();
+ pair m=min(f);
+ pair M=max(f);
+ real width=M.x-m.x;
+ real height=M.y-m.y;
+ real xgrow=xsize == 0 || width == 0 ? 1 : xsize/width;
+ real ygrow=ysize == 0 || height == 0 ? 1 : ysize/height;
+ if(keepAspect) {
+ real[] grow;
+ if(xsize > 0) grow.push(xgrow);
+ if(ysize > 0) grow.push(ygrow);
+ return scale(grow.length == 0 ? 1 : min(grow));
+ } else return scale(xgrow,ygrow);
+
+ }
+
+ // Calculate additional scaling required if only an approximate picture
+ // size estimate is available.
+ transform3 scale3(frame f, real xsize3=this.xsize3,
+ real ysize3=this.ysize3, real zsize3=this.zsize3,
+ bool keepaspect=this.keepAspect) {
+ if(bounds3.exact) return identity(4);
+ triple m=min3(f);
+ triple M=max3(f);
+ real width=M.x-m.x;
+ real height=M.y-m.y;
+ real depth=M.z-m.z;
+ real xgrow=xsize3 == 0 || width == 0 ? 1 : xsize3/width;
+ real ygrow=ysize3 == 0 || height == 0 ? 1 : ysize3/height;
+ real zgrow=zsize3 == 0 || depth == 0 ? 1 : zsize3/depth;
+ if(keepAspect) {
+ real[] grow;
+ if(xsize3 > 0) grow.push(xgrow);
+ if(ysize3 > 0) grow.push(ygrow);
+ if(zsize3 > 0) grow.push(zgrow);
+ return scale3(grow.length == 0 ? 1 : min(grow));
+ } else return scale(xgrow,ygrow,zgrow);
+ }
+
+ // calculateTransform with scaling <<<2
+ // Return the transform that would be used to fit the picture to a frame
+ transform calculateTransform(real xsize, real ysize, bool keepAspect=true,
+ bool warn=true) {
+ transform t=scaling(xsize,ysize,keepAspect,warn);
+ return scale(fit(t),xsize,ysize,keepAspect)*t;
+ }
+
+ transform calculateTransform(bool warn=true) {
+ if(fixed) return fixedscaling;
+ return calculateTransform(xsize,ysize,keepAspect,warn);
+ }
+
+ transform3 calculateTransform3(real xsize=xsize3, real ysize=ysize3,
+ real zsize=zsize3,
+ bool keepAspect=true, bool warn=true,
+ projection P=currentprojection) {
+ transform3 t=scaling(xsize,ysize,zsize,keepAspect,warn);
+ return scale3(fit3(t,null,P),keepAspect)*t;
+ }
+
+ // min/max with xsize and ysize <<<2
+ // NOTE: These are probably very slow as implemented.
+ pair min(real xsize=this.xsize, real ysize=this.ysize,
+ bool keepAspect=this.keepAspect, bool warn=true) {
+ return min(calculateTransform(xsize,ysize,keepAspect,warn));
+ }
+
+ pair max(real xsize=this.xsize, real ysize=this.ysize,
+ bool keepAspect=this.keepAspect, bool warn=true) {
+ return max(calculateTransform(xsize,ysize,keepAspect,warn));
+ }
+
+ triple min3(real xsize=this.xsize3, real ysize=this.ysize3,
+ real zsize=this.zsize3, bool keepAspect=this.keepAspect,
+ bool warn=true, projection P) {
+ return min(calculateTransform3(xsize,ysize,zsize,keepAspect,warn,P));
+ }
+
+ triple max3(real xsize=this.xsize3, real ysize=this.ysize3,
+ real zsize=this.zsize3, bool keepAspect=this.keepAspect,
+ bool warn=true, projection P) {
+ return max(calculateTransform3(xsize,ysize,zsize,keepAspect,warn,P));
+ }
+
+ // More Fitting <<<2
+ // Returns the 2D picture fit to the requested size.
+ frame fit2(real xsize=this.xsize, real ysize=this.ysize,
+ bool keepAspect=this.keepAspect) {
+ if(fixed) return scaled();
+ if(empty2())
+ return newframe;
+
+ transform t=scaling(xsize,ysize,keepAspect);
+ frame f=fit(t);
+ transform s=scale(f,xsize,ysize,keepAspect);
+ if(s == identity()) return f;
+ return fit(s*t);
+ }
+
+ static frame fitter(string,picture,string,real,real,bool,bool,string,string,
+ light,projection);
+ frame fit(string prefix="", string format="",
+ real xsize=this.xsize, real ysize=this.ysize,
+ bool keepAspect=this.keepAspect, bool view=false,
+ string options="", string script="", light light=currentlight,
+ projection P=currentprojection) {
+ return fitter == null ? fit2(xsize,ysize,keepAspect) :
+ fitter(prefix,this,format,xsize,ysize,keepAspect,view,options,script,
+ light,P);
+ }
+
+ // Fit a 3D picture.
+ frame fit3(projection P=currentprojection) {
+ if(settings.render == 0) return fit(P);
+ if(fixed) return scaled();
+ if(empty3()) return newframe;
+ transform3 t=scaling(xsize3,ysize3,zsize3,keepAspect);
+ frame f=fit3(t,null,P);
+ transform3 s=scale3(f,xsize3,ysize3,zsize3,keepAspect);
+ if(s == identity4) return f;
+ return fit3(s*t,null,P);
+ }
+
+ // In case only an approximate picture size estimate is available, return the
+ // fitted frame slightly scaled (including labels and true size distances)
+ // so that it precisely meets the given size specification.
+ frame scale(real xsize=this.xsize, real ysize=this.ysize,
+ bool keepAspect=this.keepAspect) {
+ frame f=fit(xsize,ysize,keepAspect);
+ transform s=scale(f,xsize,ysize,keepAspect);
+ if(s == identity()) return f;
+ return s*f;
+ }
+
+ // Copying <<<2
+
+ // Copies enough information to yield the same userMin/userMax.
+ void userCopy2(picture pic) {
+ userMinx2(pic.userMin2().x);
+ userMiny2(pic.userMin2().y);
+ userMaxx2(pic.userMax2().x);
+ userMaxy2(pic.userMax2().y);
+ }
+
+ void userCopy3(picture pic) {
+ copyPairOrTriple(umin, pic.umin);
+ copyPairOrTriple(umax, pic.umax);
+ usetx=pic.usetx;
+ usety=pic.usety;
+ usetz=pic.usetz;
+ }
+
+ void userCopy(picture pic) {
+ userCopy2(pic);
+ userCopy3(pic);
+ }
+
+ // Copies the drawing information, but not the sizing information into a new
+ // picture. Fitting this picture will not scale as the original picture would.
+ picture drawcopy() {
+ picture dest=new picture;
+ dest.nodes=copy(nodes);
+ dest.nodes3=copy(nodes3);
+ dest.T=T;
+ dest.T3=T3;
+
+ // TODO: User bounds are sizing info, which probably shouldn't be part of
+ // a draw copy. Should we move this down to copy()?
+ dest.userCopy3(this);
+
+ dest.scale=scale.copy();
+ dest.legend=copy(legend);
+
+ return dest;
+ }
+
+ // A deep copy of this picture. Modifying the copied picture will not affect
+ // the original.
+ picture copy() {
+ picture dest=drawcopy();
+
+ dest.uptodate=uptodate;
+ dest.bounds=bounds.copy();
+ dest.bounds3=bounds3.copy();
+
+ dest.xsize=xsize; dest.ysize=ysize;
+ dest.xsize3=xsize; dest.ysize3=ysize3; dest.zsize3=zsize3;
+ dest.keepAspect=keepAspect;
+ dest.xunitsize=xunitsize; dest.yunitsize=yunitsize;
+ dest.zunitsize=zunitsize;
+ dest.fixed=fixed; dest.fixedscaling=fixedscaling;
+
+ return dest;
+ }
+
+ // Helper function for defining transformed pictures. Do not call it
+ // directly.
+ picture transformed(transform t) {
+ picture dest=drawcopy();
+
+ // Replace nodes with a single drawer that realizes the transform.
+ node[] oldnodes = dest.nodes;
+ void drawAll(frame f, transform tt, transform T, pair lb, pair rt) {
+ transform Tt = T*t;
+ for (node n : oldnodes) {
+ xasyKEY(n.key);
+ n.d(f,tt,Tt,lb,rt);
+ }
+ }
+ dest.nodes = new node[] {node(drawAll)};
+
+ dest.uptodate=uptodate;
+ dest.bounds=bounds.transformed(t);
+ dest.bounds3=bounds3.copy();
+
+ dest.bounds.exact=false;
+
+ dest.xsize=xsize; dest.ysize=ysize;
+ dest.xsize3=xsize; dest.ysize3=ysize3; dest.zsize3=zsize3;
+ dest.keepAspect=keepAspect;
+ dest.xunitsize=xunitsize; dest.yunitsize=yunitsize;
+ dest.zunitsize=zunitsize;
+ dest.fixed=fixed; dest.fixedscaling=fixedscaling;
+
+ return dest;
+ }
+
+ // Add Picture <<<2
+ // Add a picture to this picture, such that the user coordinates will be
+ // scaled identically when fitted
+ void add(picture src, bool group=true, filltype filltype=NoFill,
+ bool above=true) {
+ // Copy the picture. Only the drawing function closures are needed, so we
+ // only copy them. This needs to be a deep copy, as src could later have
+ // objects added to it that should not be included in this picture.
+
+ if(src == this) abort("cannot add picture to itself");
+
+ uptodate=false;
+
+ picture srcCopy=src.drawcopy();
+ // Draw by drawing the copied picture.
+ if(srcCopy.nodes.length > 0) {
+ nodes.push(node(new void(frame f, transform t, transform T,
+ pair m, pair M) {
+ add(f,srcCopy.fit(t,T*srcCopy.T,m,M),group,filltype,above);
+ }));
+ }
+
+ if(srcCopy.nodes3.length > 0) {
+ nodes3.push(node3(new void(frame f, transform3 t, transform3 T3,
+ picture pic, projection P, triple m, triple M)
+ {
+ add(f,srcCopy.fit3(t,T3*srcCopy.T3,pic,P,m,M),group,above);
+ }));
+ }
+
+ legend.append(src.legend);
+
+ if(src.usetx) userBoxX3(src.umin.x,src.umax.x);
+ if(src.usety) userBoxY3(src.umin.y,src.umax.y);
+ if(src.usetz) userBoxZ3(src.umin.z,src.umax.z);
+
+ bounds.append(srcCopy.T, src.bounds);
+ //append(bounds.point,bounds.min,bounds.max,srcCopy.T,src.bounds);
+ append(bounds3.point,bounds3.min,bounds3.max,srcCopy.T3,src.bounds3);
+
+ //if(!src.bounds.exact) bounds.exact=false;
+ if(!src.bounds3.exact) bounds3.exact=false;
+ }
+}
+
+// Post Struct <<<1
+picture operator * (transform t, picture orig)
+{
+ return orig.transformed(t);
+}
+
+picture operator * (transform3 t, picture orig)
+{
+ picture pic=orig.copy();
+ pic.T3=t*pic.T3;
+ triple umin=pic.userMin3(), umax=pic.userMax3();
+ pic.userCorners3(t*umin,
+ t*(umin.x,umin.y,umax.z),
+ t*(umin.x,umax.y,umin.z),
+ t*(umin.x,umax.y,umax.z),
+ t*(umax.x,umin.y,umin.z),
+ t*(umax.x,umin.y,umax.z),
+ t*(umax.x,umax.y,umin.z),
+ t*umax);
+ pic.bounds3.exact=false;
+ return pic;
+}
+
+picture currentpicture;
+
+void size(picture pic=currentpicture, real x, real y=x,
+ bool keepAspect=pic.keepAspect)
+{
+ pic.size(x,y,keepAspect);
+}
+
+void size(picture pic=currentpicture, transform t)
+{
+ if(pic.empty3()) {
+ pair z=size(pic.fit(t));
+ pic.size(z.x,z.y);
+ }
+}
+
+void size3(picture pic=currentpicture, real x, real y=x, real z=y,
+ bool keepAspect=pic.keepAspect)
+{
+ pic.size3(x,y,z,keepAspect);
+}
+
+void unitsize(picture pic=currentpicture, real x, real y=x, real z=y)
+{
+ pic.unitsize(x,y,z);
+}
+
+void size(picture pic=currentpicture, real xsize, real ysize,
+ pair min, pair max)
+{
+ pair size=max-min;
+ pic.unitsize(size.x != 0 ? xsize/size.x : 0,
+ size.y != 0 ? ysize/size.y : 0);
+}
+
+void size(picture dest, picture src)
+{
+ dest.size(src.xsize,src.ysize,src.keepAspect);
+ dest.size3(src.xsize3,src.ysize3,src.zsize3,src.keepAspect);
+ dest.unitsize(src.xunitsize,src.yunitsize,src.zunitsize);
+}
+
+pair min(picture pic, bool user=false)
+{
+ transform t=pic.calculateTransform();
+ pair z=pic.min(t);
+ return user ? inverse(t)*z : z;
+}
+
+pair max(picture pic, bool user=false)
+{
+ transform t=pic.calculateTransform();
+ pair z=pic.max(t);
+ return user ? inverse(t)*z : z;
+}
+
+pair size(picture pic, bool user=false)
+{
+ transform t=pic.calculateTransform();
+ pair M=pic.max(t);
+ pair m=pic.min(t);
+ if(!user) return M-m;
+ t=inverse(t);
+ return t*M-t*m;
+}
+
+// Frame Alignment <<<
+pair rectify(pair dir)
+{
+ real scale=max(abs(dir.x),abs(dir.y));
+ if(scale != 0) dir *= 0.5/scale;
+ dir += (0.5,0.5);
+ return dir;
+}
+
+pair point(frame f, pair dir)
+{
+ pair m=min(f);
+ pair M=max(f);
+ return m+realmult(rectify(dir),M-m);
+}
+
+path[] align(path[] g, transform t=identity(), pair position,
+ pair align, pen p=currentpen)
+{
+ if(g.length == 0) return g;
+ pair m=min(g);
+ pair M=max(g);
+ pair dir=rectify(inverse(t)*-align);
+ if(basealign(p) == 1)
+ dir -= (0,m.y/(M.y-m.y));
+ pair a=m+realmult(dir,M-m);
+ return shift(position+align*labelmargin(p))*t*shift(-a)*g;
+}
+
+// Returns a transform for aligning frame f in the direction align
+transform shift(frame f, pair align)
+{
+ return shift(align-point(f,-align));
+}
+
+// Returns a copy of frame f aligned in the direction align
+frame align(frame f, pair align)
+{
+ return shift(f,align)*f;
+}
+// >>>
+
+pair point(picture pic=currentpicture, pair dir, bool user=true)
+{
+ pair umin = pic.userMin2();
+ pair umax = pic.userMax2();
+
+ pair z=umin+realmult(rectify(dir),umax-umin);
+ return user ? z : pic.calculateTransform()*z;
+}
+
+pair truepoint(picture pic=currentpicture, pair dir, bool user=true)
+{
+ transform t=pic.calculateTransform();
+ pair m=pic.min(t);
+ pair M=pic.max(t);
+ pair z=m+realmult(rectify(dir),M-m);
+ return user ? inverse(t)*z : z;
+}
+
+// Transform coordinate in [0,1]x[0,1] to current user coordinates.
+pair relative(picture pic=currentpicture, pair z)
+{
+ return pic.userMin2()+realmult(z,pic.userMax2()-pic.userMin2());
+}
+
+void add(picture pic=currentpicture, drawer d, bool exact=false)
+{
+ pic.add(d,exact);
+}
+
+typedef void drawer3(frame f, transform3 t, picture pic, projection P);
+void add(picture pic=currentpicture, drawer3 d, bool exact=false)
+{
+ pic.add(d,exact);
+}
+
+void add(picture pic=currentpicture, void d(picture,transform),
+ bool exact=false)
+{
+ pic.add(d,exact);
+}
+
+void add(picture pic=currentpicture, void d(picture,transform3),
+ bool exact=false)
+{
+ pic.add(d,exact);
+}
+
+void begingroup(picture pic=currentpicture)
+{
+ pic.add(new void(frame f, transform) {
+ begingroup(f);
+ },true);
+}
+
+void endgroup(picture pic=currentpicture)
+{
+ pic.add(new void(frame f, transform) {
+ endgroup(f);
+ },true);
+}
+
+void Draw(picture pic=currentpicture, path g, pen p=currentpen)
+{
+ pic.add(new void(frame f, transform t) {
+ draw(f,t*g,p);
+ },true);
+ pic.addPath(g,p);
+}
+
+// Default arguments have been removed to increase speed.
+void _draw(picture pic, path g, pen p, margin margin)
+{
+ if (size(nib(p)) == 0 && margin==NoMargin) {
+ // Inline the drawerBound wrapper for speed.
+ pic.addExactAbove(new void(frame f, transform t, transform T, pair, pair) {
+ _draw(f,t*T*g,p);
+ });
+ } else {
+ pic.add(new void(frame f, transform t) {
+ draw(f,margin(t*g,p).g,p);
+ },true);
+ }
+ pic.addPath(g,p);
+}
+
+void Draw(picture pic=currentpicture, explicit path[] g, pen p=currentpen)
+{
+ // Could optimize this by adding one drawer.
+ for(int i=0; i < g.length; ++i) Draw(pic,g[i],p);
+}
+
+void fill(picture pic=currentpicture, path[] g, pen p=currentpen,
+ bool copy=true)
+{
+ if(copy)
+ g=copy(g);
+ pic.add(new void(frame f, transform t) {
+ fill(f,t*g,p,false);
+ },true);
+ pic.addPath(g);
+}
+
+void drawstrokepath(picture pic=currentpicture, path g, pen strokepen,
+ pen p=currentpen)
+{
+ pic.add(new void(frame f, transform t) {
+ draw(f,strokepath(t*g,strokepen),p);
+ },true);
+ pic.addPath(g,p);
+}
+
+void latticeshade(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen fillrule=currentpen, pen[][] p, bool copy=true)
+{
+ if(copy) {
+ g=copy(g);
+ p=copy(p);
+ }
+ pic.add(new void(frame f, transform t) {
+ latticeshade(f,t*g,stroke,fillrule,p,t,false);
+ },true);
+ pic.addPath(g);
+}
+
+void axialshade(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen pena, pair a, bool extenda=true,
+ pen penb, pair b, bool extendb=true, bool copy=true)
+{
+ if(copy)
+ g=copy(g);
+ pic.add(new void(frame f, transform t) {
+ axialshade(f,t*g,stroke,pena,t*a,extenda,penb,t*b,extendb,false);
+ },true);
+ pic.addPath(g);
+}
+
+void radialshade(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen pena, pair a, real ra, bool extenda=true,
+ pen penb, pair b, real rb, bool extendb=true, bool copy=true)
+{
+ if(copy)
+ g=copy(g);
+ pic.add(new void(frame f, transform t) {
+ pair A=t*a, B=t*b;
+ real RA=abs(t*(a+ra)-A);
+ real RB=abs(t*(b+rb)-B);
+ radialshade(f,t*g,stroke,pena,A,RA,extenda,penb,B,RB,extendb,false);
+ },true);
+ pic.addPath(g);
+}
+
+void gouraudshade(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen fillrule=currentpen, pen[] p, pair[] z, int[] edges,
+ bool copy=true)
+{
+ if(copy) {
+ g=copy(g);
+ p=copy(p);
+ z=copy(z);
+ edges=copy(edges);
+ }
+ pic.add(new void(frame f, transform t) {
+ gouraudshade(f,t*g,stroke,fillrule,p,t*z,edges,false);
+ },true);
+ pic.addPath(g);
+}
+
+void gouraudshade(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen fillrule=currentpen, pen[] p, int[] edges, bool copy=true)
+{
+ if(copy) {
+ g=copy(g);
+ p=copy(p);
+ edges=copy(edges);
+ }
+ pic.add(new void(frame f, transform t) {
+ gouraudshade(f,t*g,stroke,fillrule,p,edges,false);
+ },true);
+ pic.addPath(g);
+}
+
+void tensorshade(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen fillrule=currentpen, pen[][] p, path[] b=g,
+ pair[][] z=new pair[][], bool copy=true)
+{
+ if(copy) {
+ g=copy(g);
+ p=copy(p);
+ b=copy(b);
+ z=copy(z);
+ }
+ pic.add(new void(frame f, transform t) {
+ pair[][] Z=new pair[z.length][];
+ for(int i=0; i < z.length; ++i)
+ Z[i]=t*z[i];
+ tensorshade(f,t*g,stroke,fillrule,p,t*b,Z,false);
+ },true);
+ pic.addPath(g);
+}
+
+void tensorshade(frame f, path[] g, bool stroke=false,
+ pen fillrule=currentpen, pen[] p,
+ path b=g.length > 0 ? g[0] : nullpath)
+{
+ tensorshade(f,g,stroke,fillrule,new pen[][] {p},b);
+}
+
+void tensorshade(frame f, path[] g, bool stroke=false,
+ pen fillrule=currentpen, pen[] p,
+ path b=g.length > 0 ? g[0] : nullpath, pair[] z)
+{
+ tensorshade(f,g,stroke,fillrule,new pen[][] {p},b,new pair[][] {z});
+}
+
+void tensorshade(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen fillrule=currentpen, pen[] p,
+ path b=g.length > 0 ? g[0] : nullpath)
+{
+ tensorshade(pic,g,stroke,fillrule,new pen[][] {p},b);
+}
+
+void tensorshade(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen fillrule=currentpen, pen[] p,
+ path b=g.length > 0 ? g[0] : nullpath, pair[] z)
+{
+ tensorshade(pic,g,stroke,fillrule,new pen[][] {p},b,new pair[][] {z});
+}
+
+// Smoothly shade the regions between consecutive paths of a sequence using a
+// given array of pens:
+void draw(picture pic=currentpicture, path[] g, pen fillrule=currentpen,
+ pen[] p)
+{
+ path[] G;
+ pen[][] P;
+ string differentlengths="arrays have different lengths";
+ if(g.length != p.length) abort(differentlengths);
+ for(int i=0; i < g.length-1; ++i) {
+ path g0=g[i];
+ path g1=g[i+1];
+ if(length(g0) != length(g1)) abort(differentlengths);
+ for(int j=0; j < length(g0); ++j) {
+ G.push(subpath(g0,j,j+1)--reverse(subpath(g1,j,j+1))--cycle);
+ P.push(new pen[] {p[i],p[i],p[i+1],p[i+1]});
+ }
+ }
+ tensorshade(pic,G,fillrule,P);
+}
+
+void functionshade(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen fillrule=currentpen, string shader, bool copy=true)
+{
+ if(copy)
+ g=copy(g);
+ pic.add(new void(frame f, transform t) {
+ functionshade(f,t*g,stroke,fillrule,shader);
+ },true);
+ pic.addPath(g);
+}
+
+void filldraw(picture pic=currentpicture, path[] g, pen fillpen=currentpen,
+ pen drawpen=currentpen)
+{
+ begingroup(pic);
+ fill(pic,g,fillpen);
+ Draw(pic,g,drawpen);
+ endgroup(pic);
+}
+
+void clip(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen fillrule=currentpen, bool copy=true)
+{
+ if(copy)
+ g=copy(g);
+ pic.clip(min(g), max(g),
+ new void(frame f, transform t) {
+ clip(f,t*g,stroke,fillrule,false);
+ },
+ true);
+}
+
+void beginclip(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen fillrule=currentpen, bool copy=true)
+{
+ if(copy)
+ g=copy(g);
+
+ pic.clipmin.push(min(g));
+ pic.clipmax.push(max(g));
+
+ pic.add(new void(frame f, transform t) {
+ beginclip(f,t*g,stroke,fillrule,false);
+ },true);
+}
+
+void endclip(picture pic=currentpicture)
+{
+ pair min,max;
+ if (pic.clipmin.length > 0 && pic.clipmax.length > 0)
+ {
+ min = pic.clipmin.pop();
+ max = pic.clipmax.pop();
+ }
+ else
+ {
+ // We should probably abort here, since the PostScript output will be
+ // garbage.
+ warning("endclip", "endclip without beginclip");
+ min = pic.userMin2();
+ max = pic.userMax2();
+ }
+
+ pic.clip(min, max,
+ new void(frame f, transform) {
+ endclip(f);
+ },
+ true);
+}
+
+void unfill(picture pic=currentpicture, path[] g, bool copy=true)
+{
+ if(copy)
+ g=copy(g);
+ pic.add(new void(frame f, transform t) {
+ unfill(f,t*g,false);
+ },true);
+}
+
+void filloutside(picture pic=currentpicture, path[] g, pen p=currentpen,
+ bool copy=true)
+{
+ if(copy)
+ g=copy(g);
+ pic.add(new void(frame f, transform t) {
+ filloutside(f,t*g,p,false);
+ },true);
+ pic.addPath(g);
+}
+
+// Use a fixed scaling to map user coordinates in box(min,max) to the
+// desired picture size.
+transform fixedscaling(picture pic=currentpicture, pair min, pair max,
+ pen p=nullpen, bool warn=false)
+{
+ Draw(pic,min,p+invisible);
+ Draw(pic,max,p+invisible);
+ pic.fixed=true;
+ return pic.fixedscaling=pic.calculateTransform(pic.xsize,pic.ysize,
+ pic.keepAspect);
+}
+
+// Add frame src to frame dest about position with optional grouping.
+void add(frame dest, frame src, pair position, bool group=false,
+ filltype filltype=NoFill, bool above=true)
+{
+ add(dest,shift(position)*src,group,filltype,above);
+}
+
+// Add frame src to picture dest about position with optional grouping.
+void add(picture dest=currentpicture, frame src, pair position=0,
+ bool group=true, filltype filltype=NoFill, bool above=true)
+{
+ if(is3D(src)) {
+ dest.add(new void(frame f, transform3, picture, projection) {
+ add(f,src); // always add about 3D origin (ignore position)
+ },true);
+ dest.addBox((0,0,0),(0,0,0),min3(src),max3(src));
+ } else {
+ dest.add(new void(frame f, transform t) {
+ add(f,shift(t*position)*src,group,filltype,above);
+ },true);
+ dest.addBox(position,position,min(src),max(src));
+ }
+}
+
+// Like add(picture,frame,pair) but extend picture to accommodate frame.
+void attach(picture dest=currentpicture, frame src, pair position=0,
+ bool group=true, filltype filltype=NoFill, bool above=true)
+{
+ transform t=dest.calculateTransform();
+ add(dest,src,position,group,filltype,above);
+ pair s=size(dest.fit(t));
+ size(dest,dest.xsize != 0 ? s.x : 0,dest.ysize != 0 ? s.y : 0);
+}
+
+// Like add(picture,frame,pair) but align frame in direction align.
+void add(picture dest=currentpicture, frame src, pair position, pair align,
+ bool group=true, filltype filltype=NoFill, bool above=true)
+{
+ add(dest,align(src,align),position,group,filltype,above);
+}
+
+// Like add(frame,frame,pair) but align frame in direction align.
+void add(frame dest, frame src, pair position, pair align,
+ bool group=true, filltype filltype=NoFill, bool above=true)
+{
+ add(dest,align(src,align),position,group,filltype,above);
+}
+
+// Like add(picture,frame,pair,pair) but extend picture to accommodate frame;
+void attach(picture dest=currentpicture, frame src, pair position,
+ pair align, bool group=true, filltype filltype=NoFill,
+ bool above=true)
+{
+ attach(dest,align(src,align),position,group,filltype,above);
+}
+
+// Add a picture to another such that user coordinates in both will be scaled
+// identically in the shipout.
+void add(picture dest, picture src, bool group=true, filltype filltype=NoFill,
+ bool above=true)
+{
+ dest.add(src,group,filltype,above);
+}
+
+void add(picture src, bool group=true, filltype filltype=NoFill,
+ bool above=true)
+{
+ currentpicture.add(src,group,filltype,above);
+}
+
+// Fit the picture src using the identity transformation (so user
+// coordinates and truesize coordinates agree) and add it about the point
+// position to picture dest.
+void add(picture dest, picture src, pair position, bool group=true,
+ filltype filltype=NoFill, bool above=true)
+{
+ add(dest,src.fit(identity()),position,group,filltype,above);
+}
+
+void add(picture src, pair position, bool group=true, filltype filltype=NoFill,
+ bool above=true)
+{
+ add(currentpicture,src,position,group,filltype,above);
+}
+
+// Fill a region about the user-coordinate 'origin'.
+void fill(pair origin, picture pic=currentpicture, path[] g, pen p=currentpen)
+{
+ picture opic;
+ fill(opic,g,p);
+ add(pic,opic,origin);
+}
+
+void postscript(picture pic=currentpicture, string s)
+{
+ pic.add(new void(frame f, transform) {
+ postscript(f,s);
+ },true);
+}
+
+void postscript(picture pic=currentpicture, string s, pair min, pair max)
+{
+ pic.add(new void(frame f, transform t) {
+ postscript(f,s,t*min,t*max);
+ },true);
+}
+
+void tex(picture pic=currentpicture, string s)
+{
+ // Force TeX string s to be evaluated immediately (in case it is a macro).
+ frame g;
+ tex(g,s);
+ size(g);
+ pic.add(new void(frame f, transform) {
+ tex(f,s);
+ },true);
+}
+
+void tex(picture pic=currentpicture, string s, pair min, pair max)
+{
+ frame g;
+ tex(g,s);
+ size(g);
+ pic.add(new void(frame f, transform t) {
+ tex(f,s,t*min,t*max);
+ },true);
+}
+
+void layer(picture pic=currentpicture)
+{
+ pic.add(new void(frame f, transform) {
+ layer(f);
+ },true);
+}
+
+void erase(picture pic=currentpicture)
+{
+ pic.uptodate=false;
+ pic.erase();
+}
+
+void begin(picture pic=currentpicture, string name, string id="",
+ bool visible=true)
+{
+ if(!latex() || !pdf()) return;
+ settings.twice=true;
+ if(id == "") id=string(++ocgindex);
+ tex(pic,"\begin{ocg}{"+name+"}{"+id+"}{"+(visible ? "1" : "0")+"}");
+ layer(pic);
+}
+
+void end(picture pic=currentpicture)
+{
+ if(!latex() || !pdf()) return;
+ tex(pic,"\end{ocg}");
+ layer(pic);
+}
+
+// For users of the LaTeX babel package.
+void deactivatequote(picture pic=currentpicture)
+{
+ tex(pic,"\catcode`\"=12");
+}
+
+void activatequote(picture pic=currentpicture)
+{
+ tex(pic,"\catcode`\"=13");
+}
diff --git a/graphics/asymptote/base/plain_prethree.asy b/graphics/asymptote/base/plain_prethree.asy
new file mode 100644
index 0000000000..bc40792cf0
--- /dev/null
+++ b/graphics/asymptote/base/plain_prethree.asy
@@ -0,0 +1,216 @@
+// Critical definitions for transform3 needed by projection and picture.
+
+pair viewportmargin=(0.1,0.1); // Horizontal and vertical 3D viewport margins.
+
+typedef real[][] transform3;
+restricted transform3 identity4=identity(4);
+
+// A uniform 3D scaling.
+transform3 scale3(real s)
+{
+ transform3 t=identity(4);
+ t[0][0]=t[1][1]=t[2][2]=s;
+ return t;
+}
+
+// Simultaneous 3D scalings in the x, y, and z directions.
+transform3 scale(real x, real y, real z)
+{
+ transform3 t=identity(4);
+ t[0][0]=x;
+ t[1][1]=y;
+ t[2][2]=z;
+ return t;
+}
+
+transform3 shiftless(transform3 t)
+{
+ transform3 T=copy(t);
+ T[0][3]=T[1][3]=T[2][3]=0;
+ return T;
+}
+
+real camerafactor=2; // Factor used for camera adjustment.
+
+struct transformation {
+ transform3 modelview; // For orientation and positioning
+ transform3 projection; // For 3D to 2D projection
+ bool infinity;
+ void operator init(transform3 modelview) {
+ this.modelview=modelview;
+ this.projection=identity4;
+ infinity=true;
+ }
+ void operator init(transform3 modelview, transform3 projection) {
+ this.modelview=modelview;
+ this.projection=projection;
+ infinity=false;
+ }
+ transform3 compute() {
+ return infinity ? modelview : projection*modelview;
+ }
+ transformation copy() {
+ transformation T=new transformation;
+ T.modelview=copy(modelview);
+ T.projection=copy(projection);
+ T.infinity=infinity;
+ return T;
+ }
+}
+
+struct projection {
+ transform3 t; // projection*modelview (cached)
+ bool infinity;
+ bool absolute=false;
+ triple camera; // Position of camera.
+ triple up; // A vector that should be projected to direction (0,1).
+ triple target; // Point where camera is looking at.
+ triple normal; // Normal vector from target to projection plane.
+ pair viewportshift; // Fractional viewport shift.
+ real zoom=1; // Zoom factor.
+ real angle; // Lens angle (for perspective projection).
+ bool showtarget=true; // Expand bounding volume to include target?
+ typedef transformation projector(triple camera, triple up, triple target);
+ projector projector;
+ bool autoadjust=true; // Adjust camera to lie outside bounding volume?
+ bool center=false; // Center target within bounding volume?
+ int ninterpolate; // Used for projecting nurbs to 2D Bezier curves.
+ bool bboxonly=true; // Typeset label bounding box only.
+
+ transformation T;
+
+ void calculate() {
+ T=projector(camera,up,target);
+ t=T.compute();
+ infinity=T.infinity;
+ ninterpolate=infinity ? 1 : 16;
+ }
+
+ triple vector() {
+ return camera-target;
+ }
+
+ void operator init(triple camera, triple up=(0,0,1), triple target=(0,0,0),
+ triple normal=camera-target,
+ real zoom=1, real angle=0, pair viewportshift=0,
+ bool showtarget=true, bool autoadjust=true,
+ bool center=false, projector projector) {
+ this.camera=camera;
+ this.up=up;
+ this.target=target;
+ this.normal=normal;
+ this.zoom=zoom;
+ this.angle=angle;
+ this.viewportshift=viewportshift;
+ this.showtarget=showtarget;
+ this.autoadjust=autoadjust;
+ this.center=center;
+ this.projector=projector;
+ calculate();
+ }
+
+ projection copy() {
+ projection P=new projection;
+ P.t=t;
+ P.infinity=infinity;
+ P.absolute=absolute;
+ P.camera=camera;
+ P.up=up;
+ P.target=target;
+ P.normal=normal;
+ P.zoom=zoom;
+ P.angle=angle;
+ P.viewportshift=viewportshift;
+ P.showtarget=showtarget;
+ P.autoadjust=autoadjust;
+ P.center=center;
+ P.projector=projector;
+ P.ninterpolate=ninterpolate;
+ P.bboxonly=bboxonly;
+ P.T=T.copy();
+ return P;
+ }
+
+ // Return the maximum distance of box(m,M) from target.
+ real distance(triple m, triple M) {
+ triple[] c={m,(m.x,m.y,M.z),(m.x,M.y,m.z),(m.x,M.y,M.z),
+ (M.x,m.y,m.z),(M.x,m.y,M.z),(M.x,M.y,m.z),M};
+ return max(abs(c-target));
+ }
+
+
+ // This is redefined here to make projection as self-contained as possible.
+ static private real sqrtEpsilon = sqrt(realEpsilon);
+
+ // Move the camera so that the box(m,M) rotated about target will always
+ // lie in front of the clipping plane.
+ bool adjust(triple m, triple M) {
+ triple v=camera-target;
+ real d=distance(m,M);
+ static real lambda=camerafactor*(1-sqrtEpsilon);
+ if(lambda*d >= abs(v)) {
+ camera=target+camerafactor*d*unit(v);
+ calculate();
+ return true;
+ }
+ return false;
+ }
+}
+
+projection currentprojection;
+
+struct light {
+ real[][] diffuse;
+ real[][] specular;
+ pen background=nullpen; // Background color of the 3D canvas.
+ real specularfactor;
+ triple[] position; // Only directional lights are currently implemented.
+
+ transform3 T=identity(4); // Transform to apply to normal vectors.
+
+ bool on() {return position.length > 0;}
+
+ void operator init(pen[] diffuse,
+ pen[] specular=diffuse, pen background=nullpen,
+ real specularfactor=1,
+ triple[] position) {
+ int n=diffuse.length;
+ assert(specular.length == n && position.length == n);
+
+ this.diffuse=new real[n][];
+ this.specular=new real[n][];
+ this.background=background;
+ this.position=new triple[n];
+ for(int i=0; i < position.length; ++i) {
+ this.diffuse[i]=rgba(diffuse[i]);
+ this.specular[i]=rgba(specular[i]);
+ this.position[i]=unit(position[i]);
+ }
+ this.specularfactor=specularfactor;
+ }
+
+ void operator init(pen diffuse=white, pen specular=diffuse,
+ pen background=nullpen, real specularfactor=1 ...triple[] position) {
+ int n=position.length;
+ operator init(array(n,diffuse),array(n,specular),
+ background,specularfactor,position);
+ }
+
+ void operator init(pen diffuse=white, pen specular=diffuse,
+ pen background=nullpen, real x, real y, real z) {
+ operator init(diffuse,specular,background,(x,y,z));
+ }
+
+ void operator init(explicit light light) {
+ diffuse=copy(light.diffuse);
+ specular=copy(light.specular);
+ background=light.background;
+ specularfactor=light.specularfactor;
+ position=copy(light.position);
+ }
+
+ real[] background() {return rgba(background == nullpen ? white : background);}
+}
+
+light currentlight;
+
diff --git a/graphics/asymptote/base/plain_scaling.asy b/graphics/asymptote/base/plain_scaling.asy
new file mode 100644
index 0000000000..5bed3338e9
--- /dev/null
+++ b/graphics/asymptote/base/plain_scaling.asy
@@ -0,0 +1,258 @@
+real expansionfactor=sqrt(2);
+
+// A coordinate in "flex space." A linear combination of user and true-size
+// coordinates.
+struct coord {
+ real user,truesize;
+
+ // Build a coord.
+ static coord build(real user, real truesize) {
+ coord c=new coord;
+ c.user=user;
+ c.truesize=truesize;
+ return c;
+ }
+
+ // Deep copy of coordinate. Users may add coords to the picture, but then
+ // modify the struct. To prevent this from yielding unexpected results, deep
+ // copying is used.
+ coord copy() {
+ return build(user, truesize);
+ }
+
+ void clip(real min, real max) {
+ user=min(max(user,min),max);
+ truesize=0;
+ }
+}
+
+bool operator <= (coord a, coord b)
+{
+ return a.user <= b.user && a.truesize <= b.truesize;
+}
+
+bool operator >= (coord a, coord b)
+{
+ return a.user >= b.user && a.truesize >= b.truesize;
+}
+
+// Find the maximal elements of the input array, using the partial ordering
+// given.
+coord[] maxcoords(coord[] in, bool operator <= (coord,coord))
+{
+ // As operator <= is defined in the parameter list, it has a special
+ // meaning in the body of the function.
+
+ coord best;
+ coord[] c;
+
+ int n=in.length;
+
+ if(n == 0)
+ return c;
+
+ int first=0;
+ // Add the first coord without checking restrictions (as there are none).
+ best=in[first];
+ c.push(best);
+
+ static int NONE=-1;
+
+ int dominator(coord x)
+ {
+ // This assumes it has already been checked against the best.
+ for(int i=1; i < c.length; ++i)
+ if(x <= c[i])
+ return i;
+ return NONE;
+ }
+
+ void promote(int i)
+ {
+ // Swap with the top
+ coord x=c[i];
+ c[i]=best;
+ best=c[0]=x;
+ }
+
+ void addmaximal(coord x)
+ {
+ coord[] newc;
+
+ // Check if it beats any others.
+ for(int i=0; i < c.length; ++i) {
+ coord y=c[i];
+ if(!(y <= x))
+ newc.push(y);
+ }
+ newc.push(x);
+ c=newc;
+ best=c[0];
+ }
+
+ void add(coord x)
+ {
+ if(x <= best)
+ return;
+ else {
+ int i=dominator(x);
+ if(i == NONE)
+ addmaximal(x);
+ else
+ promote(i);
+ }
+ }
+
+ for(int i=1; i < n; ++i)
+ add(in[i]);
+
+ return c;
+}
+
+struct coords2 {
+ coord[] x,y;
+ void erase() {
+ x.delete();
+ y.delete();
+ }
+ // Only a shallow copy of the individual elements of x and y
+ // is needed since, once entered, they are never modified.
+ coords2 copy() {
+ coords2 c=new coords2;
+ c.x=copy(x);
+ c.y=copy(y);
+ return c;
+ }
+ void append(coords2 c) {
+ x.append(c.x);
+ y.append(c.y);
+ }
+ void push(pair user, pair truesize) {
+ x.push(coord.build(user.x,truesize.x));
+ y.push(coord.build(user.y,truesize.y));
+ }
+ void push(coord cx, coord cy) {
+ x.push(cx);
+ y.push(cy);
+ }
+ void push(transform t, coords2 c1, coords2 c2) {
+ for(int i=0; i < c1.x.length; ++i) {
+ coord cx=c1.x[i], cy=c2.y[i];
+ pair tinf=shiftless(t)*(0,0);
+ pair z=t*(cx.user,cy.user);
+ pair w=(cx.truesize,cy.truesize);
+ w=length(w)*unit(shiftless(t)*w);
+ coord Cx,Cy;
+ Cx.user=z.x;
+ Cy.user=z.y;
+ Cx.truesize=w.x;
+ Cy.truesize=w.y;
+ push(Cx,Cy);
+ }
+ }
+ void xclip(real min, real max) {
+ for(int i=0; i < x.length; ++i)
+ x[i].clip(min,max);
+ }
+ void yclip(real min, real max) {
+ for(int i=0; i < y.length; ++i)
+ y[i].clip(min,max);
+ }
+}
+
+// The scaling in one dimension: x --> a*x + b
+struct scaling {
+ real a,b;
+ static scaling build(real a, real b) {
+ scaling s=new scaling;
+ s.a=a; s.b=b;
+ return s;
+ }
+ real scale(real x) {
+ return a*x+b;
+ }
+ real scale(coord c) {
+ return scale(c.user) + c.truesize;
+ }
+}
+
+// Calculate the minimum point in scaling the coords.
+real min(real m, scaling s, coord[] c) {
+ for(int i=0; i < c.length; ++i)
+ if(s.scale(c[i]) < m)
+ m=s.scale(c[i]);
+ return m;
+}
+
+// Calculate the maximum point in scaling the coords.
+real max(real M, scaling s, coord[] c) {
+ for(int i=0; i < c.length; ++i)
+ if(s.scale(c[i]) > M)
+ M=s.scale(c[i]);
+ return M;
+}
+
+import simplex;
+
+/*
+ Calculate the sizing constants for the given array and maximum size.
+ Solve the two-variable linear programming problem using the simplex method.
+ This problem is specialized in that the second variable, "b", does not have
+ a non-negativity condition, and the first variable, "a", is the quantity
+ being maximized.
+*/
+real calculateScaling(string dir, coord[] m, coord[] M, real size,
+ bool warn=true) {
+ real[][] A;
+ real[] b;
+ real[] c=new real[] {-1,0,0};
+
+ void addMinCoord(coord c) {
+ // (a*user + b) + truesize >= 0:
+ A.push(new real[] {c.user,1,-1});
+ b.push(-c.truesize);
+ }
+ void addMaxCoord(coord c) {
+ // (a*user + b) + truesize <= size:
+ A.push(new real[] {-c.user,-1,1});
+ b.push(c.truesize-size);
+ }
+
+ for (int i=0; i < m.length; ++i)
+ addMinCoord(m[i]);
+ for (int i=0; i < M.length; ++i)
+ addMaxCoord(M[i]);
+
+ int[] s=array(A.length,1);
+ simplex S=simplex(c,A,s,b);
+
+ if(S.case == S.OPTIMAL) {
+ return S.x[0];
+ } else if(S.case == S.UNBOUNDED) {
+ if(warn) warning("unbounded",dir+" scaling in picture unbounded");
+ return 0;
+ } else {
+ if(!warn) return 1;
+
+ bool userzero(coord[] coords) {
+ for(var coord : coords)
+ if(coord.user != 0) return false;
+ return true;
+ }
+
+ if((userzero(m) && userzero(M)) || size >= infinity) return 1;
+
+ warning("cannotfit","cannot fit picture to "+dir+"size "+(string) size
+ +"...enlarging...");
+
+ return calculateScaling(dir,m,M,expansionfactor*size,warn);
+ }
+}
+
+real calculateScaling(string dir, coord[] coords, real size, bool warn=true)
+{
+ coord[] m=maxcoords(coords,operator >=);
+ coord[] M=maxcoords(coords,operator <=);
+
+ return calculateScaling(dir, m, M, size, warn);
+}
diff --git a/graphics/asymptote/base/plain_shipout.asy b/graphics/asymptote/base/plain_shipout.asy
new file mode 100644
index 0000000000..4562205966
--- /dev/null
+++ b/graphics/asymptote/base/plain_shipout.asy
@@ -0,0 +1,145 @@
+// Default file prefix used for inline LaTeX mode
+string defaultfilename;
+
+file _outpipe;
+if(settings.xasy)
+ _outpipe=output(mode="pipe");
+
+string[] file3;
+
+string outprefix(string prefix=defaultfilename) {
+ return stripextension(prefix != "" ? prefix : outname());
+}
+
+string outformat(string format="")
+{
+ if(format == "") format=settings.outformat;
+ if(format == "") format=nativeformat();
+ return format;
+}
+
+frame currentpatterns;
+
+frame Portrait(frame f) {return f;};
+frame Landscape(frame f) {return rotate(90)*f;};
+frame UpsideDown(frame f) {return rotate(180)*f;};
+frame Seascape(frame f) {return rotate(-90)*f;};
+typedef frame orientation(frame);
+orientation orientation=Portrait;
+
+// Forward references to functions defined in module three.
+object embed3(string, frame, string, string, string, light, projection);
+string Embed(string name, string text="", string options="", real width=0,
+ real height=0);
+
+bool prconly(string format="")
+{
+ return outformat(format) == "prc";
+}
+
+bool prc0(string format="")
+{
+ return settings.prc && (outformat(format) == "pdf" || prconly() || settings.inlineimage );
+}
+
+bool prc(string format="") {
+ return prc0(format) && Embed != null;
+}
+
+bool is3D(string format="")
+{
+ return prc(format) || settings.render != 0;
+}
+
+frame enclose(string prefix=defaultfilename, object F, string format="")
+{
+ if(prc(format)) {
+ frame f;
+ label(f,F.L);
+ return f;
+ } return F.f;
+}
+
+void deconstruct(picture pic=currentpicture)
+{
+ frame f;
+ transform t=pic.calculateTransform();
+ if(currentpicture.fitter == null)
+ f=pic.fit(t);
+ else
+ f=pic.fit();
+ deconstruct(f,currentpatterns,t);
+}
+
+bool implicitshipout=false;
+
+void shipout(string prefix=defaultfilename, frame f,
+ string format="", bool wait=false, bool view=true,
+ string options="", string script="",
+ light light=currentlight, projection P=currentprojection,
+ transform t=identity)
+{
+ if(is3D(f)) {
+ f=enclose(prefix,embed3(prefix,f,format,options,script,light,P));
+ if(settings.render != 0 && !prc(format)) {
+ return;
+ }
+ }
+
+ if(settings.xasy || (!implicitshipout && prefix == defaultfilename)) {
+ if(prefix == defaultfilename) {
+ currentpicture.clear();
+ add(f,group=false);
+ }
+ return;
+ }
+
+ // Applications like LaTeX cannot handle large PostScript coordinates.
+ pair m=min(f);
+ int limit=2000;
+ if(abs(m.x) > limit || abs(m.y) > limit) f=shift(-m)*f;
+
+ _shipout(prefix,f,currentpatterns,format,wait,view,t);
+}
+
+void shipout(string prefix=defaultfilename, picture pic=currentpicture,
+ orientation orientation=orientation,
+ string format="", bool wait=false, bool view=true,
+ string options="", string script="",
+ light light=currentlight, projection P=currentprojection)
+{
+ pic.uptodate=true;
+ if(!uptodate()) {
+ bool inlinetex=settings.inlinetex;
+ bool prc=prc(format);
+ bool empty3=pic.empty3();
+ if(prc && !empty3) {
+ if(settings.render == 0) {
+ string image=outprefix(prefix)+"+"+(string) file3.length;
+ if(settings.inlineimage) image += "_0";
+ settings.inlinetex=false;
+ settings.prc=false;
+ shipout(image,pic,orientation,nativeformat(),view=false,light,P);
+ settings.prc=true;
+ }
+ settings.inlinetex=settings.inlineimage;
+ }
+ frame f;
+ transform t=pic.calculateTransform();
+ if(currentpicture.fitter == null)
+ f=pic.fit(t);
+ else
+ f=pic.fit(prefix,format,view=view,options,script,light,P);
+
+ if(!prconly() && (!pic.empty2() || settings.render == 0 || prc || empty3))
+ shipout(prefix,orientation(f),format,wait,view,t);
+ settings.inlinetex=inlinetex;
+ }
+}
+
+void newpage(picture pic=currentpicture)
+{
+ pic.add(new void(frame f, transform) {
+ newpage(f);
+ },true);
+}
diff --git a/graphics/asymptote/base/plain_strings.asy b/graphics/asymptote/base/plain_strings.asy
new file mode 100644
index 0000000000..c109fc491f
--- /dev/null
+++ b/graphics/asymptote/base/plain_strings.asy
@@ -0,0 +1,264 @@
+string defaultformat(int n, string trailingzero="", bool fixed=false,
+ bool signed=true)
+{
+ return "$%"+trailingzero+"."+string(n)+(fixed ? "f" : "g")+"$";
+}
+
+string defaultformat=defaultformat(4);
+string defaultseparator="\!\times\!";
+
+string ask(string prompt)
+{
+ write(stdout,prompt);
+ return stdin;
+}
+
+string getstring(string name="", string default="", string prompt="",
+ bool store=true)
+{
+ string[] history=history(name,1);
+ if(history.length > 0) default=history[0];
+ if(prompt == "") prompt=name+"? [%s] ";
+ prompt=replace(prompt,new string[][] {{"%s",default}});
+ string s=readline(prompt,name);
+ if(s == "") s=default;
+ else saveline(name,s,store);
+ return s;
+}
+
+int getint(string name="", int default=0, string prompt="", bool store=true)
+{
+ return (int) getstring(name,(string) default,prompt,store);
+}
+
+real getreal(string name="", real default=0, string prompt="", bool store=true)
+{
+ return (real) getstring(name,(string) default,prompt,store);
+}
+
+pair getpair(string name="", pair default=0, string prompt="", bool store=true)
+{
+ return (pair) getstring(name,(string) default,prompt,store);
+}
+
+triple gettriple(string name="", triple default=(0,0,0), string prompt="",
+ bool store=true)
+{
+ return (triple) getstring(name,(string) default,prompt,store);
+}
+
+// returns a string with all occurrences of string 'before' in string 's'
+// changed to string 'after'.
+string replace(string s, string before, string after)
+{
+ return replace(s,new string[][] {{before,after}});
+}
+
+// Like texify but don't convert embedded TeX commands: \${}
+string TeXify(string s)
+{
+ static string[][] t={{"&","\&"},{"%","\%"},{"_","\_"},{"#","\#"},{"<","$<$"},
+ {">","$>$"},{"|","$|$"},{"^","$\hat{\ }$"},
+ {"~","$\tilde{\ }$"},{" ","\phantom{ }"}};
+ return replace(s,t);
+}
+
+private string[][] trans1={{'\\',"\backslash "},
+ {"$","\$"},{"{","\{"},{"}","\}"}};
+private string[][] trans2={{"\backslash ","$\backslash$"}};
+
+// Convert string to TeX
+string texify(string s)
+{
+ return TeXify(replace(replace(s,trans1),trans2));
+}
+
+// Convert string to TeX, preserving newlines
+string verbatim(string s)
+{
+ bool space=substr(s,0,1) == '\n';
+ static string[][] t={{'\n',"\\"}};
+ t.append(trans1);
+ s=TeXify(replace(replace(s,t),trans2));
+ return space ? "\ "+s : s;
+}
+
+// Split a string into an array of substrings delimited by delimiter
+// If delimiter is an empty string, use space delimiter but discard empty
+// substrings. TODO: Move to C++ code.
+string[] split(string s, string delimiter="")
+{
+ bool prune=false;
+ if(delimiter == "") {
+ prune=true;
+ delimiter=" ";
+ }
+
+ string[] S;
+ int last=0;
+ int i;
+ int N=length(delimiter);
+ int n=length(s);
+ while((i=find(s,delimiter,last)) >= 0) {
+ if(i > last || (i == last && !prune))
+ S.push(substr(s,last,i-last));
+ last=i+N;
+ }
+ if(n > last || (n == last && !prune))
+ S.push(substr(s,last,n-last));
+ return S;
+}
+
+// Returns an array of strings obtained by splitting s into individual
+// characters. TODO: Move to C++ code.
+string[] array(string s)
+{
+ int len=length(s);
+ string[] S=new string[len];
+ for(int i=0; i < len; ++i)
+ S[i]=substr(s,i,1);
+ return S;
+}
+
+// Concatenate an array of strings into a single string.
+// TODO: Move to C++ code.
+string operator +(...string[] a)
+{
+ string S;
+ for(string s : a)
+ S += s;
+ return S;
+}
+
+int system(string s)
+{
+ return system(split(s));
+}
+
+int[] operator ecast(string[] a)
+{
+ return sequence(new int(int i) {return (int) a[i];},a.length);
+}
+
+real[] operator ecast(string[] a)
+{
+ return sequence(new real(int i) {return (real) a[i];},a.length);
+}
+
+// Read contents of file as a string.
+string file(string s)
+{
+ file f=input(s);
+ string s;
+ while(!eof(f)) {
+ s += f+'\n';
+ }
+ return s;
+}
+
+string italic(string s)
+{
+ return s != "" ? "{\it "+s+"}" : s;
+}
+
+string baseline(string s, string template="\strut")
+{
+ return s != "" && settings.tex != "none" ? "\vphantom{"+template+"}"+s : s;
+}
+
+string math(string s)
+{
+ return s != "" ? "$"+s+"$" : s;
+}
+
+private void notimplemented(string text)
+{
+ abort(text+" is not implemented for the '"+settings.tex+"' TeX engine");
+}
+
+string jobname(string name)
+{
+ int pos=rfind(name,"-");
+ return pos >= 0 ? "\ASYprefix\jobname"+substr(name,pos) : name;
+}
+
+string graphic(string name, string options="")
+{
+ if(latex()) {
+ if(options != "") options="["+options+"]";
+ bool pdf=pdf();
+ string includegraphics="\includegraphics"+options;
+ if(settings.inlinetex)
+ return includegraphics+"{"+jobname(name)+"}";
+ else
+ return includegraphics+
+ (find(name," ") < 0 ? "{"+name+"}" :
+ (pdf ? "{\""+stripextension(name)+"\".pdf}" : "{\""+name+"\"}"));
+ }
+ if(settings.tex != "context")
+ notimplemented("graphic");
+ return "\externalfigure["+name+"]["+options+"]";
+}
+
+string graphicscale(real x)
+{
+ return string(settings.tex == "context" ? 1000*x : x);
+}
+
+string minipage(string s, real width=100bp)
+{
+ if(latex())
+ return "\begin{minipage}{"+(string) (width/pt)+"pt}"+s+"\end{minipage}";
+ if(settings.tex != "context")
+ notimplemented("minipage");
+ return "\startframedtext[none][frame=off,width="+(string) (width/pt)+
+ "pt]"+s+"\stopframedtext";
+}
+
+void usepackage(string s, string options="")
+{
+ if(!latex()) notimplemented("usepackage");
+ string usepackage="\usepackage";
+ if(options != "") usepackage += "["+options+"]";
+ texpreamble(usepackage+"{"+s+"}");
+}
+
+void pause(string w="Hit enter to continue")
+{
+ write(w);
+ w=stdin;
+}
+
+string format(string format=defaultformat, bool forcemath=false, real x,
+ string locale="")
+{
+ return format(format,forcemath,defaultseparator,x,locale);
+}
+
+string phantom(string s)
+{
+ return settings.tex != "none" ? "\phantom{"+s+"}" : "";
+}
+
+string[] spinner=new string[] {'|','/','-','\\'};
+spinner.cyclic=true;
+
+void progress(bool3 init=default)
+{
+ static int count=-1;
+ static int lastseconds=-1;
+ if(init == true) {
+ lastseconds=0;
+ write(stdout,' ',flush);
+ } else
+ if(init == default) {
+ int seconds=seconds();
+ if(seconds > lastseconds) {
+ lastseconds=seconds;
+ write(stdout,'\b'+spinner[++count],flush);
+ }
+ } else
+ write(stdout,'\b',flush);
+}
+
+restricted int ocgindex=0;
diff --git a/graphics/asymptote/base/pstoedit.asy b/graphics/asymptote/base/pstoedit.asy
new file mode 100644
index 0000000000..7baccd3ed6
--- /dev/null
+++ b/graphics/asymptote/base/pstoedit.asy
@@ -0,0 +1,18 @@
+pen textpen=basealign;
+pair align=Align;
+
+// Compatibility routines for the pstoedit (version 3.43 or later) backend.
+void gsave(picture pic=currentpicture)
+{
+ pic.add(new void (frame f, transform) {
+ gsave(f);
+ },true);
+}
+
+void grestore(picture pic=currentpicture)
+{
+ pic.add(new void (frame f, transform) {
+ grestore(f);
+ },true);
+}
+
diff --git a/graphics/asymptote/base/rational.asy b/graphics/asymptote/base/rational.asy
new file mode 100644
index 0000000000..d24da0d664
--- /dev/null
+++ b/graphics/asymptote/base/rational.asy
@@ -0,0 +1,269 @@
+// Asymptote module implementing rational arithmetic.
+
+int gcd(int m, int n)
+{
+ if(m < n) {
+ int temp=m;
+ m=n;
+ n=temp;
+ }
+ while(n != 0) {
+ int r=m % n;
+ m=n;
+ n=r;
+ }
+ return m;
+}
+
+int lcm(int m, int n)
+{
+ return m#gcd(m,n)*n;
+}
+
+struct rational {
+ int p=0,q=1;
+ void reduce() {
+ int d=gcd(p,q);
+ if(abs(d) > 1) {
+ p #= d;
+ q #= d;
+ }
+ if(q <= 0) {
+ if(q == 0) abort("Division by zero");
+ p=-p;
+ q=-q;
+ }
+ }
+ void operator init(int p=0, int q=1, bool reduce=true) {
+ this.p=p;
+ this.q=q;
+ if(reduce) reduce();
+ }
+}
+
+rational operator cast(int p) {
+ return rational(p,false);
+}
+
+rational[] operator cast(int[] a) {
+ return sequence(new rational(int i) {return a[i];},a.length);
+}
+
+rational[][] operator cast(int[][] a) {
+ return sequence(new rational[](int i) {return a[i];},a.length);
+}
+
+real operator ecast(rational r) {
+ return r.p/r.q;
+}
+
+rational operator -(rational r)
+{
+ return rational(-r.p,r.q,false);
+}
+
+rational operator +(rational r, rational s)
+{
+ return rational(r.p*s.q+s.p*r.q,r.q*s.q);
+}
+
+rational operator -(rational r, rational s)
+{
+ return rational(r.p*s.q-s.p*r.q,r.q*s.q);
+}
+
+rational operator *(rational r, rational s)
+{
+ return rational(r.p*s.p,r.q*s.q);
+}
+
+rational operator /(rational r, rational s)
+{
+ return rational(r.p*s.q,r.q*s.p);
+}
+
+bool operator ==(rational r, rational s)
+{
+ return r.p == s.p && r.q == s.q;
+}
+
+bool operator !=(rational r, rational s)
+{
+ return r.p != s.p || r.q != s.q;
+}
+
+bool operator <(rational r, rational s)
+{
+ return r.p*s.q-s.p*r.q < 0;
+}
+
+bool operator >(rational r, rational s)
+{
+ return r.p*s.q-s.p*r.q > 0;
+}
+
+bool operator <=(rational r, rational s)
+{
+ return r.p*s.q-s.p*r.q <= 0;
+}
+
+bool operator >=(rational r, rational s)
+{
+ return r.p*s.q-s.p*r.q >= 0;
+}
+
+bool[] operator ==(rational[] r, rational s)
+{
+ return sequence(new bool(int i) {return r[i] == s;},r.length);
+}
+
+bool operator ==(rational[] r, rational[] s)
+{
+ if(r.length != s.length)
+ abort(" operation attempted on arrays of different lengths: "+
+ string(r.length)+" != "+string(s.length));
+ return all(sequence(new bool(int i) {return r[i] == s[i];},r.length));
+}
+
+bool operator ==(rational[][] r, rational[][] s)
+{
+ if(r.length != s.length)
+ abort(" operation attempted on arrays of different lengths: "+
+ string(r.length)+" != "+string(s.length));
+ return all(sequence(new bool(int i) {return r[i] == s[i];},r.length));
+}
+
+bool[] operator <(rational[] r, rational s)
+{
+ return sequence(new bool(int i) {return r[i] < s;},r.length);
+}
+
+bool[] operator >(rational[] r, rational s)
+{
+ return sequence(new bool(int i) {return r[i] > s;},r.length);
+}
+
+bool[] operator <=(rational[] r, rational s)
+{
+ return sequence(new bool(int i) {return r[i] <= s;},r.length);
+}
+
+bool[] operator >=(rational[] r, rational s)
+{
+ return sequence(new bool(int i) {return r[i] >= s;},r.length);
+}
+
+rational min(rational a, rational b)
+{
+ return a <= b ? a : b;
+}
+
+rational max(rational a, rational b)
+{
+ return a >= b ? a : b;
+}
+
+string string(rational r)
+{
+ return r.q == 1 ? string(r.p) : string(r.p)+"/"+string(r.q);
+}
+
+string texstring(rational r)
+{
+ if(r.q == 1) return string(r.p);
+ string s;
+ if(r.p < 0) s="-";
+ return s+"\frac{"+string(abs(r.p))+"}{"+string(r.q)+"}";
+}
+
+void write(file fout=stdout, string s="", rational r, suffix suffix=none)
+{
+ write(fout,s+string(r),suffix);
+}
+
+void write(file fout=stdout, string s="", rational[] a, suffix suffix=none)
+{
+ if(s != "")
+ write(fout,s);
+ for(int i=0; i < a.length; ++i) {
+ write(fout,i,none);
+ write(fout,':\t',a[i],endl);
+ }
+ write(fout,suffix);
+}
+
+void write(file fout=stdout, string s="", rational[][] a, suffix suffix=none)
+{
+ if(s != "")
+ write(fout,s);
+ for(int i=0; i < a.length; ++i) {
+ rational[] ai=a[i];
+ for(int j=0; j < ai.length; ++j) {
+ write(fout,ai[j],tab);
+ }
+ write(fout,endl);
+ }
+ write(fout,suffix);
+}
+
+bool rectangular(rational[][] m)
+{
+ int n=m.length;
+ if(n > 0) {
+ int m0=m[0].length;
+ for(int i=1; i < n; ++i)
+ if(m[i].length != m0) return false;
+ }
+ return true;
+}
+
+rational sum(rational[] a)
+{
+ rational sum;
+ for(rational r:a)
+ sum += r;
+ return sum;
+}
+
+rational max(rational[] a)
+{
+ rational M=a[0];
+ for(rational r:a)
+ M=max(M,r);
+ return M;
+}
+
+rational abs(rational r)
+{
+ return rational(abs(r.p),r.q,false);
+}
+
+rational[] operator -(rational[] r)
+{
+ return sequence(new rational(int i) {return -r[i];},r.length);
+}
+
+rational[][] rationalidentity(int n)
+{
+ return sequence(new rational[](int i) {return sequence(new rational(int j) {return j == i ? 1 : 0;},n);},n);
+}
+
+/*
+rational r=rational(1,3)+rational(1,4);
+write(r == rational(1,12));
+write(r);
+real x=r;
+write(x);
+
+rational r=3;
+write(r);
+
+write(gcd(-8,12));
+write(rational(-36,-14));
+
+int[][] a=new int[][] {{1,2},{3,4}};
+rational[][] r=a;
+write(r);
+
+*/
+
diff --git a/graphics/asymptote/base/rationalSimplex.asy b/graphics/asymptote/base/rationalSimplex.asy
new file mode 100644
index 0000000000..5175b87ba7
--- /dev/null
+++ b/graphics/asymptote/base/rationalSimplex.asy
@@ -0,0 +1,362 @@
+// Rational simplex solver written by John C. Bowman and Pouria Ramazi, 2018.
+import rational;
+
+void simplexTableau(rational[][] E, int[] Bindices, int I=-1, int J=-1) {}
+void simplexPhase2() {}
+
+void simplexWrite(rational[][] E, int[] Bindicies, int, int)
+{
+ int m=E.length-1;
+ int n=E[0].length-1;
+
+ write(E[m][n],tab);
+ for(int j=0; j < n; ++j)
+ write(E[m][j],tab);
+ write();
+
+ for(int i=0; i < m; ++i) {
+ write(E[i][n],tab);
+ for(int j=0; j < n; ++j) {
+ write(E[i][j],tab);
+ }
+ write();
+ }
+ write();
+};
+
+struct simplex {
+ static int OPTIMAL=0;
+ static int UNBOUNDED=1;
+ static int INFEASIBLE=2;
+
+ int case;
+ rational[] x;
+ rational cost;
+
+ int m,n;
+ int J;
+
+ // Row reduce based on pivot E[I][J]
+ void rowreduce(rational[][] E, int N, int I, int J) {
+ rational[] EI=E[I];
+ rational v=EI[J];
+ for(int j=0; j < J; ++j) EI[j] /= v;
+ EI[J]=1;
+ for(int j=J+1; j <= N; ++j) EI[j] /= v;
+
+ for(int i=0; i < I; ++i) {
+ rational[] Ei=E[i];
+ rational EiJ=Ei[J];
+ for(int j=0; j < J; ++j)
+ Ei[j] -= EI[j]*EiJ;
+ Ei[J]=0;
+ for(int j=J+1; j <= N; ++j)
+ Ei[j] -= EI[j]*EiJ;
+ }
+ for(int i=I+1; i <= m; ++i) {
+ rational[] Ei=E[i];
+ rational EiJ=Ei[J];
+ for(int j=0; j < J; ++j)
+ Ei[j] -= EI[j]*EiJ;
+ Ei[J]=0;
+ for(int j=J+1; j <= N; ++j)
+ Ei[j] -= EI[j]*EiJ;
+ }
+ }
+
+ int iterate(rational[][] E, int N, int[] Bindices) {
+ while(true) {
+ // Find first negative entry in bottom (reduced cost) row
+ rational[] Em=E[m];
+ for(J=0; J < N; ++J)
+ if(Em[J] < 0) break;
+
+ if(J == N)
+ break;
+
+ int I=-1;
+ rational M;
+ for(int i=0; i < m; ++i) {
+ rational e=E[i][J];
+ if(e > 0) {
+ M=E[i][N]/e;
+ I=i;
+ break;
+ }
+ }
+ for(int i=I+1; i < m; ++i) {
+ rational e=E[i][J];
+ if(e > 0) {
+ rational v=E[i][N]/e;
+ if(v < M) {M=v; I=i;} // Bland's rule: choose smallest argmin
+ }
+ }
+ if(I == -1)
+ return UNBOUNDED; // Can only happen in Phase 2.
+
+ simplexTableau(E,Bindices,I,J);
+
+ // Generate new tableau
+ Bindices[I]=J;
+ rowreduce(E,N,I,J);
+ }
+ return OPTIMAL;
+ }
+
+ int iterateDual(rational[][] E, int N, int[] Bindices) {
+ while(true) {
+ // Find first negative entry in right (basic variable) column
+ rational[] Em=E[m];
+ int I;
+ for(I=0; I < m; ++I) {
+ if(E[I][N] < 0) break;
+ }
+
+ if(I == m)
+ break;
+
+ int J=-1;
+ rational M;
+ for(int j=0; j < N; ++j) {
+ rational e=E[I][j];
+ if(e < 0) {
+ M=-E[m][j]/e;
+ J=j;
+ break;
+ }
+ }
+ for(int j=J+1; j < N; ++j) {
+ rational e=E[I][j];
+ if(e < 0) {
+ rational v=-E[m][j]/e;
+ if(v < M) {M=v; J=j;} // Bland's rule: choose smallest argmin
+ }
+ }
+ if(J == -1)
+ return INFEASIBLE; // Can only happen in Phase 2.
+
+ simplexTableau(E,Bindices,I,J);
+
+ // Generate new tableau
+ Bindices[I]=J;
+ rowreduce(E,N,I,J);
+ }
+ return OPTIMAL;
+ }
+
+ // Try to find a solution x to Ax=b that minimizes the cost c^T x,
+ // where A is an m x n matrix, x is a vector of n non-negative numbers,
+ // b is a vector of length m, and c is a vector of length n.
+ // Can set phase1=false if the last m columns of A form the identity matrix.
+ void operator init(rational[] c, rational[][] A, rational[] b,
+ bool phase1=true, bool dual=false) {
+ if(dual) phase1=false;
+ // Phase 1
+ m=A.length;
+ if(m == 0) {case=INFEASIBLE; return;}
+ n=A[0].length;
+ if(n == 0) {case=INFEASIBLE; return;}
+
+ int N=phase1 ? n+m : n;
+ rational[][] E=new rational[m+1][N+1];
+ rational[] Em=E[m];
+
+ for(int j=0; j < n; ++j)
+ Em[j]=0;
+
+ for(int i=0; i < m; ++i) {
+ rational[] Ai=A[i];
+ rational[] Ei=E[i];
+ if(b[i] >= 0 || dual) {
+ for(int j=0; j < n; ++j) {
+ rational Aij=Ai[j];
+ Ei[j]=Aij;
+ Em[j] -= Aij;
+ }
+ } else {
+ for(int j=0; j < n; ++j) {
+ rational Aij=-Ai[j];
+ Ei[j]=Aij;
+ Em[j] -= Aij;
+ }
+ }
+ }
+
+ if(phase1) {
+ for(int i=0; i < m; ++i) {
+ rational[] Ei=E[i];
+ for(int j=0; j < i; ++j)
+ Ei[n+j]=0;
+ Ei[n+i]=1;
+ for(int j=i+1; j < m; ++j)
+ Ei[n+j]=0;
+ }
+ }
+
+ rational sum=0;
+ for(int i=0; i < m; ++i) {
+ rational B=dual ? b[i] : abs(b[i]);
+ E[i][N]=B;
+ sum -= B;
+ }
+ Em[N]=sum;
+
+ if(phase1)
+ for(int j=0; j < m; ++j)
+ Em[n+j]=0;
+
+ int[] Bindices;
+
+ if(phase1) {
+ Bindices=sequence(new int(int x){return x;},m)+n;
+ iterate(E,N,Bindices);
+
+ if(Em[J] != 0) {
+ simplexTableau(E,Bindices);
+ case=INFEASIBLE;
+ return;
+ }
+ } else Bindices=sequence(new int(int x){return x;},m)+n-m;
+
+ rational[] cB=phase1 ? new rational[m] : c[n-m:n];
+ rational[][] D=phase1 ? new rational[m+1][n+1] : E;
+ if(phase1) {
+ bool output=true;
+ // Drive artificial variables out of basis.
+ for(int i=0; i < m; ++i) {
+ int k=Bindices[i];
+ if(k >= n) {
+ rational[] Ei=E[i];
+ int j;
+ for(j=0; j < n; ++j)
+ if(Ei[j] != 0) break;
+ if(j == n) continue;
+ output=false;
+ simplexTableau(E,Bindices,i,j);
+ Bindices[i]=j;
+ rowreduce(E,n,i,j);
+ }
+ }
+ if(output) simplexTableau(E,Bindices);
+ int ip=0; // reduced i
+ for(int i=0; i < m; ++i) {
+ int k=Bindices[i];
+ if(k >= n) continue;
+ Bindices[ip]=k;
+ cB[ip]=c[k];
+ rational[] Dip=D[ip];
+ rational[] Ei=E[i];
+ for(int j=0; j < n; ++j)
+ Dip[j]=Ei[j];
+ Dip[n]=Ei[N];
+ ++ip;
+ }
+
+ rational[] Dip=D[ip];
+ rational[] Em=E[m];
+ for(int j=0; j < n; ++j)
+ Dip[j]=Em[j];
+ Dip[n]=Em[N];
+
+ if(m > ip) {
+ Bindices.delete(ip,m-1);
+ D.delete(ip,m-1);
+ m=ip;
+ }
+ if(!output) simplexTableau(D,Bindices);
+ }
+
+ rational[] Dm=D[m];
+ for(int j=0; j < n; ++j) {
+ rational sum=0;
+ for(int k=0; k < m; ++k)
+ sum += cB[k]*D[k][j];
+ Dm[j]=c[j]-sum;
+ }
+
+ rational sum=0;
+ for(int k=0; k < m; ++k)
+ sum += cB[k]*D[k][n];
+ Dm[n]=-sum;
+
+ simplexPhase2();
+
+ case=(dual ? iterateDual : iterate)(D,n,Bindices);
+ simplexTableau(D,Bindices);
+ if(case != OPTIMAL)
+ return;
+
+ for(int j=0; j < n; ++j)
+ x[j]=0;
+
+ for(int k=0; k < m; ++k)
+ x[Bindices[k]]=D[k][n];
+
+ cost=-Dm[n];
+ }
+
+ // Try to find a solution x to sgn(Ax-b)=sgn(s) that minimizes the cost
+ // c^T x, where A is an m x n matrix, x is a vector of n non-negative
+ // numbers, b is a vector of length m, and c is a vector of length n.
+ void operator init(rational[] c, rational[][] A, int[] s, rational[] b) {
+ int m=A.length;
+ if(m == 0) {case=INFEASIBLE; return;}
+ int n=A[0].length;
+ if(n == 0) {case=INFEASIBLE; return;}
+
+ int count=0;
+ for(int i=0; i < m; ++i)
+ if(s[i] != 0) ++count;
+
+ rational[][] a=new rational[m][n+count];
+
+ for(int i=0; i < m; ++i) {
+ rational[] ai=a[i];
+ rational[] Ai=A[i];
+ for(int j=0; j < n; ++j) {
+ ai[j]=Ai[j];
+ }
+ }
+
+ int k=0;
+
+ bool phase1=false;
+ bool dual=count == m && all(c >= 0);
+
+ for(int i=0; i < m; ++i) {
+ rational[] ai=a[i];
+ for(int j=0; j < k; ++j)
+ ai[n+j]=0;
+ if(k < count)
+ ai[n+k]=-s[i];
+ for(int j=k+1; j < count; ++j)
+ ai[n+j]=0;
+ int si=s[i];
+ if(si == 0) phase1=true;
+ else {
+ ++k;
+ rational bi=b[i];
+ if(bi == 0) {
+ if(si == 1) {
+ s[i]=-1;
+ for(int j=0; j < n+count; ++j)
+ ai[j]=-ai[j];
+ }
+ } else if(si*bi > 0) {
+ if(dual && si == 1) {
+ b[i]=-bi;
+ s[i]=-1;
+ for(int j=0; j < n+count; ++j)
+ ai[j]=-ai[j];
+ } else
+ phase1=true;
+ }
+ }
+ }
+
+ operator init(concat(c,array(count,rational(0))),a,b,phase1,dual);
+
+ if(case == OPTIMAL && count > 0)
+ x.delete(n,n+count-1);
+ }
+}
diff --git a/graphics/asymptote/base/reload.js b/graphics/asymptote/base/reload.js
new file mode 100644
index 0000000000..a652680305
--- /dev/null
+++ b/graphics/asymptote/base/reload.js
@@ -0,0 +1,23 @@
+// Load/reload the document associated with a given path.
+
+// UNIX: Copy to ~/.adobe/Acrobat/x.x/JavaScripts/
+// To avoid random window placement we recommend specifying an acroread
+// geometry option, for example: -geometry +0+0
+
+// MSWindows: Copy to %APPDATA%/Adobe/Acrobat/x.x/JavaScripts/
+
+// Note: x.x represents the appropriate Acrobat Reader version number.
+
+reload = app.trustedFunction(function(path) {
+ app.beginPriv();
+ n=app.activeDocs.length;
+ for(i=app.activeDocs.length-1; i >= 0; --i) {
+ Doc=app.activeDocs[i];
+ if(Doc.path == path && Doc != this) {
+ Doc.closeDoc();
+ break;
+ }
+ }
+ app.openDoc(path);
+ app.endPriv();
+ });
diff --git a/graphics/asymptote/base/res/notes.txt b/graphics/asymptote/base/res/notes.txt
new file mode 100644
index 0000000000..6b84573510
--- /dev/null
+++ b/graphics/asymptote/base/res/notes.txt
@@ -0,0 +1,7 @@
+For now, I decide not to commit in the *.hdr reflectance image files
+because of the size and that they are binary format. Meanwhile, the
+images I use (temporarily) can be found at:
+- <https://www.deviantart.com/zbyg/art/HDRi-Pack-1-97402522>
+- <https://hdrihaven.com/>
+
+-- Supakorn "Jamie" \ No newline at end of file
diff --git a/graphics/asymptote/base/roundedpath.asy b/graphics/asymptote/base/roundedpath.asy
new file mode 100644
index 0000000000..1a426f91dc
--- /dev/null
+++ b/graphics/asymptote/base/roundedpath.asy
@@ -0,0 +1,84 @@
+// a function to round sharp edges of open and cyclic paths
+// written by stefan knorr
+
+path roundedpath(path A, real R, real S = 1)
+// create rounded path from path A with radius R and scale S = 1
+{
+ path RoundPath; // returned path
+ path LocalPath; // local straight subpath
+ path LocalCirc; // local edge circle for intersection
+ real LocalTime; // local intersectiontime between . and ..
+ pair LocalPair; // local point to be added to 'RoundPath'
+
+ int len=length(A); // length of given path 'A'
+ bool PathClosed=cyclic(A); // true, if given path 'A' is cyclic
+
+ // initialisation: define first Point of 'RoundPath' as
+ if (PathClosed) // ? is 'A' cyclic
+ RoundPath=scale(S)*point(point(A,0)--point(A,1), 0.5); // centerpoint of first straight subpath of 'A'
+ else
+ RoundPath=scale(S)*point(A,0); // first point of 'A'
+
+ // doing everything between start and end
+ // create round paths subpath by subpath for every i-th edge
+ for(int i=1; i < len; ++i)
+ {
+ // straight subpath towards i-th edge
+ LocalPath=point(A,i-1)---point(A,i);
+ // circle with radius 'R' around i-th edge
+ LocalCirc=circle(point(A,i),R);
+ // calculate intersection time between straight subpath and circle
+ real[] t=intersect(LocalPath, LocalCirc);
+ if(t.length > 0) {
+ LocalTime=t[0];
+ // define intersectionpoint between both paths
+ LocalPair=point(subpath(LocalPath, 0, LocalTime), 1);
+ // add straight subpath towards i-th curvature to 'RoundPath'
+ RoundPath=RoundPath--scale(S)*LocalPair;
+ }
+
+ // straight subpath from i-th edge to (i+1)-th edge
+ LocalPath=point(A,i)---point(A,i+1);
+ // calculate intersection-time between straight subpath and circle
+ real[] t=intersect(LocalPath, LocalCirc);
+ if(t.length > 0) {
+ LocalTime=t[0];
+ // define intersectionpoint between both paths
+ LocalPair=point(subpath(LocalPath, 0, LocalTime), 1);
+ // add curvature near i-th edge to 'RoundPath'
+ RoundPath=RoundPath..scale(S)*LocalPair;
+ }
+ }
+
+ // final steps to have a correct termination
+ if(PathClosed) { // Is 'A' cyclic?
+ // straight subpath towards 0-th edge
+ LocalPath=point(A,len-1)---point(A,0);
+ // circle with radius 'R' around 0-th edge
+ LocalCirc=circle(point(A,0),R);
+ // calculate intersection-time between straight subpath and circle
+ real[] t=intersect(LocalPath, LocalCirc);
+ if(t.length > 0) {
+ LocalTime=t[0];
+ // define intersectionpoint between both paths
+ LocalPair=point(subpath(LocalPath, 0, LocalTime), 1);
+ // add straight subpath towards 0-th curvature to 'RoundPath'
+ RoundPath=RoundPath--scale(S)*LocalPair;
+ }
+
+
+ // straight subpath from 0-th edge to 1st edge
+ LocalPath=point(A,0)---point(A,1);
+ // calculate intersection-time between straight subpath and circle
+ real[] t=intersect(LocalPath, LocalCirc);
+ if(t.length > 0) {
+ LocalTime=t[0];
+ // define intersectionpoint between both paths
+ LocalPair=point(subpath(LocalPath, 0, LocalTime), 1);
+ // add curvature near 0-th edge to 'RoundPath' and close path
+ RoundPath=RoundPath..scale(S)*LocalPair--cycle;
+ }
+ } else
+ RoundPath=RoundPath--scale(S)*point(A,len);
+ return RoundPath;
+}
diff --git a/graphics/asymptote/base/shaders/fragment.glsl b/graphics/asymptote/base/shaders/fragment.glsl
new file mode 100644
index 0000000000..38a6a53fbb
--- /dev/null
+++ b/graphics/asymptote/base/shaders/fragment.glsl
@@ -0,0 +1,237 @@
+struct Material
+{
+ vec4 diffuse,emissive,specular;
+ vec4 parameters;
+};
+
+struct Light
+{
+ vec4 direction;
+ vec4 diffuse,specular;
+};
+
+uniform int nlights;
+uniform Light lights[Nlights];
+
+uniform MaterialBuffer {
+ Material Materials[Nmaterials];
+};
+
+
+#ifdef NORMAL
+in vec3 Normal;
+vec3 normal;
+#endif
+
+#ifdef EXPLICIT_COLOR
+in vec4 Color;
+#endif
+
+flat in int materialIndex;
+out vec4 outColor;
+
+// PBR material parameters
+vec3 PBRBaseColor; // Diffuse for nonmetals, reflectance for metals.
+vec3 PBRSpecular; // Specular tint for nonmetals
+float PBRMetallic; // Metallic/Nonmetals switch flag
+float PBRF0; // Fresnel at zero for nonmetals
+float PBRRoughness; // Roughness.
+float PBRRoughnessSq; // used value of roughness, for a little bit more "smoothing"
+
+uniform sampler2D environmentMap;
+const float PI = acos(-1.0);
+#ifdef ENABLE_TEXTURE
+const float twopi=2*PI;
+const float halfpi=PI/2;
+
+const int numSamples=7;
+
+// (x,y,z) -> (r, theta, phi);
+// theta -> [0,\pi], "height" angle
+// phi -> [0, 2\pi], rotation agnle
+vec3 cart2spher(vec3 cart) {
+ float x = cart.z;
+ float y = cart.x;
+ float z = cart.y;
+
+ float r = length(cart);
+ float phi = atan(y,x);
+ float theta = acos(z/r);
+
+ return vec3(r,phi,theta);
+}
+
+vec2 normalizedAngle(vec3 cartVec) {
+ vec3 sphericalVec = cart2spher(cartVec);
+ sphericalVec.y = sphericalVec.y / (2 * PI) - 0.25;
+ sphericalVec.z = sphericalVec.z / PI;
+ // sphericalVec.z = - sphericalVec.z;
+ return sphericalVec.yz;
+}
+#endif
+
+#ifdef NORMAL
+// h is the halfway vector between normal and light direction
+// GGX Trowbridge-Reitz Approximation
+float NDF_TRG(vec3 h, float roughness) {
+ float ndoth = max(dot(normal, h), 0);
+ float alpha2 = PBRRoughnessSq * PBRRoughnessSq;
+
+ float denom = pow(ndoth * ndoth * (alpha2-1) + 1, 2);
+ return alpha2/denom;
+}
+
+float GGX_Geom(vec3 v) {
+ float ndotv = max(dot(v,normal), 0);
+ float ap = pow((1+PBRRoughness),2);
+ float k = ap/8;
+
+ return ndotv/((ndotv * (1-k)) + k);
+}
+
+float Geom(vec3 v, vec3 l) {
+ return GGX_Geom(v) * GGX_Geom(l);
+}
+
+// Schlick's approximation
+float Fresnel(vec3 h, vec3 v, float F0) {
+ float hdotv = max(dot(h,v), 0.0);
+
+ return F0 + (1-F0)*pow((1-hdotv),5);
+}
+
+vec3 BRDF(vec3 viewDirection, vec3 lightDirection) {
+ // Lambertian diffuse
+ vec3 lambertian = PBRBaseColor;
+ // Cook-Torrance model
+ vec3 h = normalize(lightDirection + viewDirection);
+
+ float omegain = max(dot(viewDirection, normal),0);
+ float omegaln = max(dot(lightDirection, normal),0);
+
+ float D = NDF_TRG(h, PBRRoughness);
+ float G = Geom(viewDirection, lightDirection);
+ float F = Fresnel(h, viewDirection, PBRF0);
+
+ float denom=4*omegain*omegaln;
+ float rawReflectance=denom > 0 ? (D*G)/denom : 0;
+
+ vec3 dielectric = mix(lambertian, rawReflectance * PBRSpecular, F);
+ vec3 metal = rawReflectance * PBRBaseColor;
+
+ return mix(dielectric, metal, PBRMetallic);
+}
+#endif
+
+void main()
+{
+vec4 Diffuse;
+vec4 Emissive;
+vec4 Specular;
+vec4 parameters;
+
+#ifdef EXPLICIT_COLOR
+ if(materialIndex < 0) {
+ int index=-materialIndex-1;
+ Material m=Materials[index];
+ Diffuse=Color;
+ Emissive=vec4(0);
+ Specular=m.specular;
+ parameters=m.parameters;
+ } else {
+ Material m=Materials[materialIndex];
+ Diffuse=m.diffuse;
+ Emissive=m.emissive;
+ Specular=m.specular;
+ parameters=m.parameters;
+ }
+#else
+ Material m=Materials[materialIndex];
+ Diffuse=m.diffuse;
+ Emissive=m.emissive;
+ Specular=m.specular;
+ parameters=m.parameters;
+#endif
+
+ PBRRoughness=1-parameters[0];
+ PBRMetallic=parameters[1];
+ PBRF0=parameters[2];
+
+ PBRBaseColor = Diffuse.rgb;
+ PBRRoughnessSq = PBRRoughness * PBRRoughness;
+ PBRSpecular = Specular.rgb;
+
+ // Formally, the formula given a point x and direction \omega,
+ // L_i = \int_{\Omega} f(x, \omega_i, \omega) L(x,\omega_i) (\hat{n}\cdot \omega_i) d \omega_i
+ // where \Omega is the hemisphere covering a point, f is the BRDF function
+ // L is the radiance from a given angle and position.
+
+ vec3 color=Emissive.rgb;
+#ifdef NORMAL
+ vec3 Z=vec3(0,0,1);
+ vec3 pointLightRadiance=vec3(0,0,0);
+
+ normal=normalize(Normal);
+ normal=gl_FrontFacing ? normal : -normal;
+ // as a finite point light, we have some simplification to the rendering equation.
+ if(nlights > 0) {
+ for(int i=0; i < nlights; ++i) {
+ vec3 L = normalize(lights[i].direction.xyz);
+ // what if we use the acutal view from (0,0,0) instead?
+ // vec3 viewDirection = Z;
+ vec3 viewDirection = -normalize(Z);
+ float cosTheta = max(dot(normal, L), 0); // $\omega_i \cdot n$ term
+ float attn = 1; // if we have a good light position.
+ vec3 radiance = cosTheta * attn * lights[i].diffuse.rgb;
+ pointLightRadiance += BRDF(Z, L) * radiance;
+ }
+ color += pointLightRadiance.rgb;
+
+#ifdef ENABLE_TEXTURE
+#ifndef EXPLICIT_COLOR
+ // Experimental environment radiance using Riemann sums;
+ // can also do importance sampling.
+ vec3 envRadiance=vec3(0,0,0);
+
+ vec3 normalPerp = vec3(-normal.y, normal.x, 0);
+ if (length(normalPerp) == 0) { // x, y = 0.
+
+ normalPerp = vec3(1, 0, 0);
+ }
+ // we now have a normal basis;
+ normalPerp = normalize(normalPerp);
+ vec3 normalPerp2 = normalize(cross(normal, normalPerp));
+
+ const float step=1.0/numSamples;
+ const float phistep=twopi*step;
+ const float thetastep=halfpi*step;
+ for (int iphi=0; iphi < numSamples; ++iphi) {
+ float phi=iphi*phistep;
+ for (int itheta=0; itheta < numSamples; ++itheta) {
+ float theta=itheta*thetastep;
+
+ vec3 azimuth=cos(phi)*normalPerp+sin(phi)*normalPerp2;
+ vec3 L=sin(theta)*azimuth+cos(theta)*normal;
+
+ vec3 rawRadiance=texture(environmentMap,normalizedAngle(L)).rgb;
+ vec3 surfRefl=BRDF(Z,L);
+ envRadiance += surfRefl*rawRadiance*sin(2.0*theta);
+ }
+ }
+ envRadiance *= halfpi*step*step;
+
+ // vec3 lightVector = normalize(reflect(-Z, normal));
+ // vec2 anglemap = normalizedAngle(lightVector);
+ // vec3 color = texture(environmentMap, anglemap).rgb;
+ color += envRadiance.rgb;
+#endif
+#endif
+ outColor=vec4(color,Diffuse.a);
+ } else {
+ outColor=Diffuse;
+ }
+#else
+ outColor=Emissive;
+#endif
+}
+
diff --git a/graphics/asymptote/base/shaders/vertex.glsl b/graphics/asymptote/base/shaders/vertex.glsl
new file mode 100644
index 0000000000..815145461e
--- /dev/null
+++ b/graphics/asymptote/base/shaders/vertex.glsl
@@ -0,0 +1,49 @@
+in vec3 position;
+
+#ifdef NORMAL
+in vec3 normal;
+#endif
+
+#ifdef EXPLICIT_COLOR
+in uint color;
+#endif
+
+#ifdef WIDTH
+in float width;
+#endif
+
+in int material;
+
+uniform mat4 projViewMat;
+uniform mat4 viewMat;
+uniform mat4 normMat;
+
+out vec3 ViewPosition;
+#ifdef NORMAL
+out vec3 Normal;
+#endif
+
+#ifdef EXPLICIT_COLOR
+out vec4 Color;
+#endif
+
+flat out int materialIndex;
+
+void main()
+{
+ gl_Position=projViewMat*vec4(position,1.0);
+ ViewPosition=(viewMat*vec4(position,1.0)).xyz;
+#ifdef NORMAL
+ Normal=(normMat*vec4(normal,0)).xyz;
+#endif
+
+#ifdef EXPLICIT_COLOR
+ Color=unpackUnorm4x8(color);
+#endif
+
+#ifdef WIDTH
+ gl_PointSize=width;
+#endif
+
+ materialIndex=material;
+}
diff --git a/graphics/asymptote/base/simplex.asy b/graphics/asymptote/base/simplex.asy
new file mode 100644
index 0000000000..040621dd90
--- /dev/null
+++ b/graphics/asymptote/base/simplex.asy
@@ -0,0 +1,329 @@
+// Real simplex solver written by John C. Bowman and Pouria Ramazi, 2018.
+
+struct simplex {
+ static int OPTIMAL=0;
+ static int UNBOUNDED=1;
+ static int INFEASIBLE=2;
+
+ int case;
+ real[] x;
+ real cost;
+
+ int m,n;
+ int J;
+ real epsilonA;
+
+ // Row reduce based on pivot E[I][J]
+ void rowreduce(real[][] E, int N, int I, int J) {
+ real[] EI=E[I];
+ real v=EI[J];
+ for(int j=0; j < J; ++j) EI[j] /= v;
+ EI[J]=1.0;
+ for(int j=J+1; j <= N; ++j) EI[j] /= v;
+
+ for(int i=0; i < I; ++i) {
+ real[] Ei=E[i];
+ real EiJ=Ei[J];
+ for(int j=0; j < J; ++j)
+ Ei[j] -= EI[j]*EiJ;
+ Ei[J]=0.0;
+ for(int j=J+1; j <= N; ++j)
+ Ei[j] -= EI[j]*EiJ;
+ }
+ for(int i=I+1; i <= m; ++i) {
+ real[] Ei=E[i];
+ real EiJ=Ei[J];
+ for(int j=0; j < J; ++j)
+ Ei[j] -= EI[j]*EiJ;
+ Ei[J]=0.0;
+ for(int j=J+1; j <= N; ++j)
+ Ei[j] -= EI[j]*EiJ;
+ }
+ }
+
+ int iterate(real[][] E, int N, int[] Bindices) {
+ while(true) {
+ // Find first negative entry in bottom (reduced cost) row
+ real[] Em=E[m];
+ for(J=0; J < N; ++J)
+ if(Em[J] < 0) break;
+
+ if(J == N)
+ break;
+
+ int I=-1;
+ real M;
+ for(int i=0; i < m; ++i) {
+ real e=E[i][J];
+ if(e > epsilonA) {
+ M=E[i][N]/e;
+ I=i;
+ break;
+ }
+ }
+ for(int i=I+1; i < m; ++i) {
+ real e=E[i][J];
+ if(e > epsilonA) {
+ real v=E[i][N]/e;
+ if(v < M) {M=v; I=i;} // Bland's rule: choose smallest argmin
+ }
+ }
+ if(I == -1)
+ return UNBOUNDED; // Can only happen in Phase 2.
+
+ // Generate new tableau
+ Bindices[I]=J;
+ rowreduce(E,N,I,J);
+ }
+ return OPTIMAL;
+ }
+
+ int iterateDual(real[][] E, int N, int[] Bindices) {
+ while(true) {
+ // Find first negative entry in right (basic variable) column
+ real[] Em=E[m];
+ int I;
+ for(I=0; I < m; ++I) {
+ if(E[I][N] < 0) break;
+ }
+
+ if(I == m)
+ break;
+
+ int J=-1;
+ real M;
+ for(int j=0; j < N; ++j) {
+ real e=E[I][j];
+ if(e < epsilonA) {
+ M=-E[m][j]/e;
+ J=j;
+ break;
+ }
+ }
+ for(int j=J+1; j < N; ++j) {
+ real e=E[I][j];
+ if(e < epsilonA) {
+ real v=-E[m][j]/e;
+ if(v < M) {M=v; J=j;} // Bland's rule: choose smallest argmin
+ }
+ }
+ if(J == -1)
+ return INFEASIBLE; // Can only happen in Phase 2.
+
+ // Generate new tableau
+ Bindices[I]=J;
+ rowreduce(E,N,I,J);
+ }
+ return OPTIMAL;
+ }
+
+ // Try to find a solution x to Ax=b that minimizes the cost c^T x,
+ // where A is an m x n matrix, x is a vector of n non-negative numbers,
+ // b is a vector of length m, and c is a vector of length n.
+ // Can set phase1=false if the last m columns of A form the identity matrix.
+ void operator init(real[] c, real[][] A, real[] b, bool phase1=true,
+ bool dual=false) {
+ if(dual) phase1=false;
+ static real epsilon=sqrt(realEpsilon);
+ epsilonA=epsilon*norm(A);
+
+ // Phase 1
+ m=A.length;
+ if(m == 0) {case=INFEASIBLE; return;}
+ n=A[0].length;
+ if(n == 0) {case=INFEASIBLE; return;}
+
+ int N=phase1 ? n+m : n;
+ real[][] E=new real[m+1][N+1];
+ real[] Em=E[m];
+
+ for(int j=0; j < n; ++j)
+ Em[j]=0;
+
+ for(int i=0; i < m; ++i) {
+ real[] Ai=A[i];
+ real[] Ei=E[i];
+ if(b[i] >= 0 || dual) {
+ for(int j=0; j < n; ++j) {
+ real Aij=Ai[j];
+ Ei[j]=Aij;
+ Em[j] -= Aij;
+ }
+ } else {
+ for(int j=0; j < n; ++j) {
+ real Aij=-Ai[j];
+ Ei[j]=Aij;
+ Em[j] -= Aij;
+ }
+ }
+ }
+
+ if(phase1) {
+ for(int i=0; i < m; ++i) {
+ real[] Ei=E[i];
+ for(int j=0; j < i; ++j)
+ Ei[n+j]=0.0;
+ Ei[n+i]=1.0;
+ for(int j=i+1; j < m; ++j)
+ Ei[n+j]=0.0;
+ }
+ }
+
+ real sum=0;
+ for(int i=0; i < m; ++i) {
+ real B=dual ? b[i] : abs(b[i]);
+ E[i][N]=B;
+ sum -= B;
+ }
+ Em[N]=sum;
+
+ if(phase1)
+ for(int j=0; j < m; ++j)
+ Em[n+j]=0.0;
+
+ int[] Bindices;
+
+ if(phase1) {
+ Bindices=sequence(new int(int x){return x;},m)+n;
+ iterate(E,N,Bindices);
+
+ if(abs(Em[J]) > epsilonA) {
+ case=INFEASIBLE;
+ return;
+ }
+ } else Bindices=sequence(new int(int x){return x;},m)+n-m;
+
+ real[] cB=phase1 ? new real[m] : c[n-m:n];
+ real[][] D=phase1 ? new real[m+1][n+1] : E;
+ if(phase1) {
+ // Drive artificial variables out of basis.
+ for(int i=0; i < m; ++i) {
+ int k=Bindices[i];
+ if(k >= n) {
+ real[] Ei=E[i];
+ int j;
+ for(j=0; j < n; ++j)
+ if(Ei[j] != 0) break;
+ if(j == n) continue;
+ Bindices[i]=j;
+ rowreduce(E,n,i,j);
+ }
+ }
+ int ip=0; // reduced i
+ for(int i=0; i < m; ++i) {
+ int k=Bindices[i];
+ if(k >= n) continue;
+ Bindices[ip]=k;
+ cB[ip]=c[k];
+ real[] Dip=D[ip];
+ real[] Ei=E[i];
+ for(int j=0; j < n; ++j)
+ Dip[j]=Ei[j];
+ Dip[n]=Ei[N];
+ ++ip;
+ }
+
+ real[] Dip=D[ip];
+ real[] Em=E[m];
+ for(int j=0; j < n; ++j)
+ Dip[j]=Em[j];
+ Dip[n]=Em[N];
+
+ if(m > ip) {
+ Bindices.delete(ip,m-1);
+ D.delete(ip,m-1);
+ m=ip;
+ }
+ }
+
+ real[] Dm=D[m];
+ for(int j=0; j < n; ++j) {
+ real sum=0;
+ for(int k=0; k < m; ++k)
+ sum += cB[k]*D[k][j];
+ Dm[j]=c[j]-sum;
+ }
+
+ real sum=0;
+ for(int k=0; k < m; ++k)
+ sum += cB[k]*D[k][n];
+ Dm[n]=-sum;
+
+ case=(dual ? iterateDual : iterate)(D,n,Bindices);
+ if(case != OPTIMAL)
+ return;
+
+ for(int j=0; j < n; ++j)
+ x[j]=0;
+
+ for(int k=0; k < m; ++k)
+ x[Bindices[k]]=D[k][n];
+
+ cost=-Dm[n];
+ }
+
+ // Try to find a solution x to sgn(Ax-b)=sgn(s) that minimizes the cost
+ // c^T x, where A is an m x n matrix, x is a vector of n non-negative
+ // numbers, b is a vector of length m, and c is a vector of length n.
+ void operator init(real[] c, real[][] A, int[] s, real[] b) {
+ int m=A.length;
+ if(m == 0) {case=INFEASIBLE; return;}
+ int n=A[0].length;
+ if(n == 0) {case=INFEASIBLE; return;}
+
+ int count=0;
+ for(int i=0; i < m; ++i)
+ if(s[i] != 0) ++count;
+
+ real[][] a=new real[m][n+count];
+
+ for(int i=0; i < m; ++i) {
+ real[] ai=a[i];
+ real[] Ai=A[i];
+ for(int j=0; j < n; ++j) {
+ ai[j]=Ai[j];
+ }
+ }
+
+ int k=0;
+
+ bool phase1=false;
+ bool dual=count == m && all(c >= 0);
+
+ for(int i=0; i < m; ++i) {
+ real[] ai=a[i];
+ for(int j=0; j < k; ++j)
+ ai[n+j]=0;
+ if(k < count)
+ ai[n+k]=-s[i];
+ for(int j=k+1; j < count; ++j)
+ ai[n+j]=0;
+ int si=s[i];
+ if(si == 0) phase1=true;
+ else {
+ ++k;
+ real bi=b[i];
+ if(bi == 0) {
+ if(si == 1) {
+ s[i]=-1;
+ for(int j=0; j < n+count; ++j)
+ ai[j]=-ai[j];
+ }
+ } else if(si*bi > 0) {
+ if(dual && si == 1) {
+ b[i]=-bi;
+ s[i]=-1;
+ for(int j=0; j < n+count; ++j)
+ ai[j]=-ai[j];
+ } else
+ phase1=true;
+ }
+ }
+ }
+
+ operator init(concat(c,array(count,0.0)),a,b,phase1,dual);
+
+ if(case == OPTIMAL && count > 0)
+ x.delete(n,n+count-1);
+ }
+}
diff --git a/graphics/asymptote/base/size10.asy b/graphics/asymptote/base/size10.asy
new file mode 100644
index 0000000000..3bbd31227a
--- /dev/null
+++ b/graphics/asymptote/base/size10.asy
@@ -0,0 +1,12 @@
+texpreamble("\makeatletter%
+\renewcommand\normalsize{\@setfontsize\normalsize\@xpt\@xiipt}%
+\renewcommand\small{\@setfontsize\small\@ixpt{11}}%
+\renewcommand\footnotesize{\@setfontsize\footnotesize\@viiipt{9.5}}%
+\renewcommand\scriptsize{\@setfontsize\scriptsize\@viipt\@viiipt}%
+\renewcommand\tiny{\@setfontsize\tiny\@vpt\@vipt}%
+\renewcommand\large{\@setfontsize\large\@xiipt{14}}%
+\renewcommand\Large{\@setfontsize\Large\@xivpt{18}}%
+\renewcommand\LARGE{\@setfontsize\LARGE\@xviipt{22}}%
+\renewcommand\huge{\@setfontsize\huge\@xxpt{25}}%
+\renewcommand\Huge{\@setfontsize\Huge\@xxvpt{30}}%
+\makeatother");
diff --git a/graphics/asymptote/base/size11.asy b/graphics/asymptote/base/size11.asy
new file mode 100644
index 0000000000..9371228815
--- /dev/null
+++ b/graphics/asymptote/base/size11.asy
@@ -0,0 +1,12 @@
+texpreamble("\makeatletter%
+\renewcommand\normalsize{\@setfontsize\normalsize\@xipt{13.6}}%
+\renewcommand\small{\@setfontsize\small\@xpt\@xiipt}%
+\renewcommand\footnotesize{\@setfontsize\footnotesize\@ixpt{11}}%
+\renewcommand\scriptsize{\@setfontsize\scriptsize\@viiipt{9.5}}
+\renewcommand\tiny{\@setfontsize\tiny\@vipt\@viipt}
+\renewcommand\large{\@setfontsize\large\@xiipt{14}}
+\renewcommand\Large{\@setfontsize\Large\@xivpt{18}}
+\renewcommand\LARGE{\@setfontsize\LARGE\@xviipt{22}}
+\renewcommand\huge{\@setfontsize\huge\@xxpt{25}}
+\renewcommand\Huge{\@setfontsize\Huge\@xxvpt{30}}
+\makeatother");
diff --git a/graphics/asymptote/base/slide.asy b/graphics/asymptote/base/slide.asy
new file mode 100644
index 0000000000..8a787c37a5
--- /dev/null
+++ b/graphics/asymptote/base/slide.asy
@@ -0,0 +1,620 @@
+import fontsize;
+usepackage("asycolors");
+
+bool reverse=false; // Set to true to enable reverse video.
+bool stepping=false; // Set to true to enable stepping.
+bool itemstep=true; // Set to false to disable stepping on each item.
+
+settings.toolbar=false; // Disable 3D toolbar by default.
+if(settings.render < 0) settings.render=4;
+
+bool allowstepping=false; // Allow stepping for current slide.
+
+real pagemargin=0.5cm;
+real pagewidth=-2pagemargin;
+real pageheight=-2pagemargin;
+
+bool landscape=orientation == Landscape || orientation == Seascape;
+
+if(landscape) {
+ orientation=Portrait;
+ pagewidth += settings.paperheight;
+ pageheight += settings.paperwidth;
+} else {
+ pagewidth += settings.paperwidth;
+ pageheight += settings.paperheight;
+}
+
+size(pagewidth,pageheight,IgnoreAspect);
+picture background;
+
+real minipagemargin=1inch;
+real minipagewidth=pagewidth-2minipagemargin;
+
+transform tinv=inverse(fixedscaling((-1,-1),(1,1),currentpen));
+
+pen itempen=fontsize(24pt);
+pen codepen=fontsize(20pt);
+pen titlepagepen=fontsize(36pt);
+pen authorpen=fontsize(24pt);
+pen institutionpen=authorpen;
+pen datepen=fontsize(18pt);
+pen urlpen=datepen;
+
+real itemskip=0.5;
+real codeskip=0.25;
+real aboveequationskip=-1.25;
+
+pair dateskip=(0,0.1);
+pair urlskip=(0,0.2);
+
+pair titlealign=3S;
+pen titlepen=fontsize(32pt);
+real titleskip=0.5;
+
+string oldbulletcolor;
+string newbulletcolor="red";
+string bullet="{\bulletcolor\textbullet}";
+
+pair pagenumberposition=S+E;
+pair pagenumberalign=4NW;
+pen pagenumberpen=fontsize(12);
+pen steppagenumberpen=colorless(pagenumberpen);
+
+real figureborder=0.25cm;
+pen figuremattpen;
+
+pen backgroundcolor;
+pen foregroundcolor;
+
+pair titlepageposition=(-0.8,0.4);
+pair startposition=(-0.8,0.9);
+pair currentposition=startposition;
+
+string bulletcolor(string color)
+{
+ return "\def\bulletcolor{"+'\\'+"color{"+color+"}}%";
+}
+
+int[] firstnode=new int[] {currentpicture.nodes.length};
+int[] lastnode;
+bool firststep=true;
+
+int page=0;
+bool havepagenumber=true;
+
+int preamblenodes=2;
+
+bool empty()
+{
+ return currentpicture.nodes.length <= preamblenodes;
+}
+
+void background()
+{
+ if(!background.empty()) {
+ add(background);
+ layer();
+ preamblenodes += 2;
+ }
+}
+
+void color(string name, string color)
+{
+ texpreamble("\def"+'\\'+name+"#1{{\color{"+color+"}#1}}%");
+}
+
+string texcolor(pen p)
+{
+ real[] colors=colors(p);
+ string s;
+ if(colors.length > 0) {
+ s="{"+colorspace(p)+"}{";
+ for(int i=0; i < colors.length-1; ++i)
+ s += format("%.6f",colors[i],"C")+",";
+ s += format("%.6f",colors[colors.length-1],"C")+"}";
+ }
+ return s;
+}
+
+void setpens(pen red=red, pen blue=blue, pen steppen=red)
+{
+ itempen=colorless(itempen);
+ codepen=colorless(codepen);
+ pagenumberpen=colorless(pagenumberpen);
+ steppagenumberpen=colorless(steppagenumberpen)+steppen;
+ titlepagepen=colorless(titlepagepen)+red;
+ authorpen=colorless(authorpen)+blue;
+ institutionpen=colorless(institutionpen)+blue;
+ datepen=colorless(datepen);
+ urlpen=colorless(urlpen);
+}
+
+void reversevideo()
+{
+ backgroundcolor=black;
+ foregroundcolor=white;
+ fill(background,box((-1,-1),(1,1)),backgroundcolor);
+ setpens(mediumred,paleblue,mediumblue);
+ // Work around pdflatex bug, in which white is mapped to black!
+ figuremattpen=pdf() ? cmyk(0,0,0,1/255) : white;
+ color("Red","mediumred");
+ color("Green","green");
+ color("Blue","paleblue");
+ color("Foreground","white");
+ color("Background","black");
+ oldbulletcolor="white";
+ defaultpen(itempen+foregroundcolor);
+}
+
+void normalvideo() {
+ backgroundcolor=invisible;
+ foregroundcolor=black;
+ background=new picture;
+ size(background,currentpicture);
+ setpens();
+ figuremattpen=invisible;
+ color("Red","red");
+ color("Green","heavygreen");
+ color("Blue","blue");
+ color("Foreground","black");
+ color("Background","white");
+ oldbulletcolor="black";
+ defaultpen(itempen+foregroundcolor);
+}
+
+normalvideo();
+
+texpreamble(bulletcolor(newbulletcolor));
+texpreamble("\hyphenpenalty=10000\tolerance=1000");
+texpreamble("\usepackage{amsmath}");
+
+// Evaluate user command line option.
+void usersetting()
+{
+ plain.usersetting();
+ if(reverse) { // Black background
+ reversevideo();
+ } else { // White background
+ normalvideo();
+ }
+}
+
+void numberpage(pen p=pagenumberpen)
+{
+ if(havepagenumber) {
+ label((string) page,pagenumberposition,pagenumberalign,p);
+ }
+}
+
+void nextpage(pen p=pagenumberpen)
+{
+ if(!empty()) {
+ numberpage(p);
+ newpage();
+ }
+ background();
+ firststep=true;
+}
+
+void newslide(bool stepping=true)
+{
+ allowstepping=stepping;
+ nextpage();
+ ++page;
+ havepagenumber=true;
+ currentposition=startposition;
+ firstnode=new int[] {currentpicture.nodes.length};
+ lastnode.delete();
+}
+
+bool checkposition()
+{
+ if(abs(currentposition.x) > 1 || abs(currentposition.y) > 1) {
+ newslide();
+ return false;
+ }
+ return true;
+}
+
+void erasestep(int erasenode) {
+ if(!stepping || !allowstepping) return;
+ if(!checkposition()) return;
+ lastnode.push(erasenode);
+ nextpage(steppagenumberpen);
+ for(int i=0; i < firstnode.length; ++i) {
+ for(int j=firstnode[i]; j <= lastnode[i]; ++j) {
+ tex(bulletcolor(oldbulletcolor));
+ currentpicture.add(currentpicture.nodes[j].d);
+ }
+ }
+ firstnode.push(currentpicture.nodes.length-1);
+ tex(bulletcolor(newbulletcolor));
+}
+
+void step()
+{
+ // Step without erasing anything.
+ erasestep(currentpicture.nodes.length-1);
+}
+
+void incrementposition(pair z)
+{
+ currentposition += z;
+}
+
+void title(string s, pair position=N, pair align=titlealign,
+ pen p=titlepen, bool newslide=true)
+{
+ if(newslide) newslide();
+ checkposition();
+ frame f;
+ if(s != "") label(f,minipage("\center "+s,minipagewidth),(0,0),align,p);
+ add(f,position,labelmargin(p)*align);
+ currentposition=(currentposition.x,position.y+
+ (tinv*(min(f)-titleskip*I*lineskip(p)*pt)).y);
+}
+
+void outline(string s="Outline", pair position=N, pair align=titlealign,
+ pen p=titlepen)
+{
+ newslide(stepping=false);
+ title(s,position,align,p,newslide=false);
+}
+
+void remark(bool center=false, string s, pair align=0, pen p=itempen,
+ real indent=0, bool minipage=true, real skip=itemskip,
+ filltype filltype=NoFill, bool step=false)
+{
+ checkposition();
+ if(minipage) s=minipage(s,minipagewidth);
+
+ pair offset;
+ if(center) {
+ if(align == 0) align=S;
+ offset=(0,currentposition.y);
+ } else {
+ if(align == 0) align=SE;
+ offset=currentposition;
+ }
+
+ frame f;
+ label(f,s,(indent,0),align,p,filltype);
+ pair m=tinv*min(f);
+ pair M=tinv*min(f);
+
+ if(abs(offset.x+M.x) > 1)
+ warning("slidetoowide","slide too wide on page "+(string) page+':\n'+
+ (string) s);
+
+ if(abs(offset.y+M.y) > 1) {
+ void toohigh() {
+ warning("slidetoohigh","slide too high on page "+(string) page+':\n'+
+ (string) s);
+ }
+ if(M.y-m.y < 2) {
+ newslide(); offset=(offset.x,currentposition.y);
+ if(offset.y+M.y > 1 || offset.y+m.y < -1) toohigh();
+ } else toohigh();
+ }
+
+ if(step) {
+ if(!firststep) step();
+ firststep=false;
+ }
+
+ add(f,offset);
+ incrementposition((0,(tinv*(min(f)-skip*I*lineskip(p)*pt)).y));
+}
+
+void center(string s, pen p=itempen)
+{
+ remark("\center "+s,p);
+}
+
+void vbox(string s, pen p=itempen)
+{
+ remark(center=true,"\vbox{"+s+"}",p,minipage=false,skip=0);
+}
+
+void skip(real n=1)
+{
+ incrementposition((0,(tinv*(-n*itemskip*I*lineskip(itempen)*pt)).y));
+}
+
+void equation(string s, pen p=itempen)
+{
+ skip(aboveequationskip);
+ vbox("\begin{gather*}"+s+"\end{gather*}",p);
+}
+
+void equations(string s, pen p=itempen)
+{
+ skip(aboveequationskip);
+ if(find(s,"&") >= 0)
+ vbox("\begin{align*}"+s+"\end{align*}",p);
+ else
+ vbox("\begin{gather*}"+s+"\end{gather*}",p);
+}
+
+void display(frame[] f, real margin=0, pair align=S, pen p=itempen,
+ pen figuremattpen=figuremattpen, bool final=true)
+{
+ if(f.length == 0) return;
+ real[] width=new real[f.length];
+ real sum;
+ for(int i=0; i < f.length; ++i) {
+ width[i]=size(f[i]).x;
+ sum += width[i];
+ }
+ if(sum > pagewidth)
+ warning("toowide","slide too wide on page "+(string) page);
+ else margin=(pagewidth-sum)/(f.length+1);
+ real pos;
+ frame F;
+ for(int i=0; i < f.length; ++i) {
+ real w=0.5*(margin+width[i]);
+ pos += w;
+ add(F,f[i],(pos,0),Fill(figureborder,figuremattpen));
+ pos += w;
+ }
+ add(F,(0,currentposition.y),align);
+ if (final) {
+ real a=0.5(unit(align).y-1);
+ incrementposition(
+ (0, (tinv*(a*(max(F)-min(F))-itemskip*I*lineskip(p)*pt)).y));
+ }
+}
+
+void display(frame f, real margin=0, pair align=S, pen p=itempen,
+ pen figuremattpen=figuremattpen, bool final=true)
+{
+ display(new frame[] {f},margin,align,p,figuremattpen, final);
+}
+
+void display(string[] s, real margin=0, string[] captions=new string[],
+ string caption="", pair align=S, pen p=itempen,
+ pen figuremattpen=figuremattpen, bool final=true)
+{
+ frame[] f=new frame[s.length];
+ frame F;
+ for(int i=0; i < s.length; ++i) {
+ f[i]=newframe;
+ label(f[i],s[i]);
+ add(F,f[i],(0,0));
+ }
+ real y=point(F,S).y;
+ int stop=min(s.length,captions.length);
+ for(int i=0; i < stop; ++i) {
+ if(captions[i] != "")
+ label(f[i],captions[i],point(f[i],S).x+I*y,S);
+ }
+ display(f,margin,align,p,figuremattpen, final);
+ if(caption != "") center(caption,p);
+}
+
+void display(string s, string caption="", pair align=S, pen p=itempen,
+ pen figuremattpen=figuremattpen, bool final=true)
+{
+ display(new string[] {s},caption,align,p,figuremattpen, final);
+}
+
+void figure(string[] s, string options="", real margin=0,
+ string[] captions=new string[], string caption="",
+ pair align=S, pen p=itempen, pen figuremattpen=figuremattpen,
+ bool final=true)
+{
+ string[] S;
+ for(int i=0; i < s.length; ++i) {
+ S[i]=graphic(s[i],options);
+ }
+
+ display(S,margin,captions,caption,align,itempen,figuremattpen,final);
+}
+
+void figure(string s, string options="", string caption="", pair align=S,
+ pen p=itempen, pen figuremattpen=figuremattpen, bool final=true)
+{
+ figure(new string[] {s},options,caption,align,p,figuremattpen,final);
+}
+
+void multifigure(string[] slist, string options="", string caption="",
+ pair align=S, pen p=itempen, pen figuremattpen=figuremattpen,
+ bool step=itemstep)
+{
+ if(step) {
+ int lastnode=currentpicture.nodes.length-1;
+ for (int i=0; i<slist.length-1; ++i) {
+ figure(slist[i],options,caption,align,p,figuremattpen,final=false);
+ erasestep(lastnode);
+ }
+ }
+ figure(slist[slist.length-1],options,caption,align,p,figuremattpen,final=true);
+
+ if(!firststep) step();
+ firststep=false;
+}
+
+void indexedfigure(string prefix, int first, int last,
+ string options="", string caption="",
+ pair align=S, pen p=itempen, pen figuremattpen=figuremattpen,
+ bool step=itemstep)
+{
+ bool Stepping=stepping;
+ stepping=true;
+ string[] s;
+ for(int i=first; i <= last; ++i)
+ s.push(prefix+string(i));
+ multifigure(s,options,caption,align,p,figuremattpen,step=step);
+ stepping=Stepping;
+}
+
+string[] codefile;
+
+void asyinclude(string s, real xsize=0, real ysize=xsize)
+{
+ picture currentpictureSave=currentpicture;
+ currentpicture=new picture;
+ _eval("include \""+s+"\";",true);
+ s=stripdirectory(outprefix()+"_"+s);
+ codefile.push(s);
+ frame f=(xsize > 0 || ysize > 0) ?
+ currentpicture.fit(xsize,ysize) : currentpicture.fit();
+ currentpicture=currentpictureSave;
+ display(f);
+}
+
+string cropcode(string s)
+{
+ while(substr(s,0,1) == '\n') s=substr(s,1,length(s));
+ while(substr(s,length(s)-1,1) == '\n') s=substr(s,0,length(s)-1);
+ return s;
+}
+
+void code(bool center=false, string s, pen p=codepen,
+ real indent=0, real skip=codeskip,
+ filltype filltype=NoFill)
+{
+ remark(center,"{\tt "+verbatim(cropcode(s))+"}",p,indent,skip,filltype);
+}
+
+void filecode(bool center=false, string s, pen p=codepen, real indent=0,
+ real skip=codeskip, filltype filltype=NoFill)
+{
+ code(center,file(s),p,indent,skip,filltype);
+}
+
+void asyfigure(string s, string options="", string caption="", pair align=S,
+ pen p=codepen, pen figuremattpen=figuremattpen,
+ filltype filltype=NoFill, bool newslide=false)
+{
+ string a=s+".asy";
+ asy(nativeformat(),s);
+ s += "."+nativeformat();
+ if(newslide && !empty()) {
+ newslide();
+ currentposition=(currentposition.x,0);
+ align=0;
+ }
+ figure(s,options,caption,align,p,figuremattpen);
+}
+
+string asywrite(string s, string preamble="")
+{
+ static int count=0;
+ string name=outprefix()+"_slide"+(string) count;
+ ++count;
+ file temp=output(name+".asy");
+ write(temp,preamble);
+ write(temp,s);
+ close(temp);
+ codefile.push(name);
+ return name;
+}
+
+void asycode(bool center=false, string s, string options="",
+ string caption="", string preamble="",
+ pair align=S, pen p=codepen, pen figuremattpen=figuremattpen,
+ real indent=0, real skip=codeskip,
+ filltype filltype=NoFill, bool newslide=false)
+{
+ code(center,s,p,indent,skip,filltype);
+ asyfigure(asywrite(s,preamble),options,caption,align,p,figuremattpen,filltype,
+ newslide);
+}
+
+void asyfilecode(bool center=false, string s, string options="",
+ string caption="",
+ pair align=S, pen p=codepen, pen figuremattpen=figuremattpen,
+ real indent=0, real skip=codeskip,
+ filltype filltype=NoFill, bool newslide=false)
+{
+ filecode(center,s+".asy",p,indent,skip,filltype);
+ asyfigure(s,options,caption,align,p,figuremattpen,filltype,newslide);
+}
+
+void item(string s, pen p=itempen, bool step=itemstep)
+{
+ frame b;
+ label(b,bullet,(0,0),p);
+ real bulletwidth=max(b).x-min(b).x;
+ remark(bullet+"\hangindent"+(string) (bulletwidth/pt)+"pt$\,$"+s,p,
+ -bulletwidth,step=step);
+}
+
+void subitem(string s, pen p=itempen)
+{
+ remark("\quad -- "+s,p);
+}
+
+void titlepage(string title, string author, string institution="",
+ string date="", string url="", bool newslide=false)
+{
+ newslide();
+ currentposition=titlepageposition;
+ center(title,titlepagepen);
+ center(author,authorpen);
+ if(institution != "") center(institution,institutionpen);
+ currentposition -= dateskip;
+ if(date != "") center(date,datepen);
+ currentposition -= urlskip;
+ if(url != "") center("{\tt "+url+"}",urlpen);
+}
+
+// Resolve optional bibtex citations:
+void bibliographystyle(string name)
+{
+ settings.twice=true;
+ settings.keepaux=true;
+ texpreamble("\bibliographystyle{"+name+"}");
+}
+
+void bibliography(string name)
+{
+ numberpage();
+ havepagenumber=false;
+ string s=texcolor(backgroundcolor);
+ if(s != "") tex("\definecolor{Background}"+s+"\pagecolor{Background}%");
+ label("",itempen);
+ tex("\eject\def\refname{\fontsize{"+string(fontsize(titlepen))+"}{"+
+ string(lineskip(titlepen))+"}\selectfont References}%");
+ real hmargin,vmargin;
+ if(pdf()) {
+ hmargin=1;
+ vmargin=0;
+ } else {
+ hmargin=1.5;
+ vmargin=1;
+ }
+ string s;
+ if(landscape) {
+ s="{\centering\textheight="+string(pageheight-1inch)+"bp\textwidth="+
+ string(pagewidth-1.5inches)+"bp"+
+ "\vsize=\textheight\hsize=\textwidth\linewidth=\hsize"+
+ "\topmargin="+string(vmargin)+"in\oddsidemargin="+string(hmargin)+"in";
+ } else
+ s="{\centering\textheight="+string(pageheight-0.5inches)+"bp\textwidth="+
+ string(pagewidth-0.5inches)+
+ "bp\hsize=\textwidth\linewidth=\textwidth\vsize=\textheight"+
+ "\topmargin=0.5in\oddsidemargin=1in";
+ s += "\evensidemargin=\oddsidemargin\bibliography{"+name+"}\eject}";
+ tex(s);
+}
+
+exitfcn currentexitfunction=atexit();
+
+void exitfunction()
+{
+ numberpage();
+ if(currentexitfunction != null) currentexitfunction();
+ if(!settings.keep)
+ for(int i=0; i < codefile.length; ++i) {
+ string name=codefile[i];
+ delete(name+"."+nativeformat());
+ delete(name+"_.aux");
+ delete(name+".asy");
+ }
+ codefile=new string[];
+}
+
+atexit(exitfunction);
diff --git a/graphics/asymptote/base/slopefield.asy b/graphics/asymptote/base/slopefield.asy
new file mode 100644
index 0000000000..bdbb025267
--- /dev/null
+++ b/graphics/asymptote/base/slopefield.asy
@@ -0,0 +1,87 @@
+import graph_settings;
+real stepfraction=0.05;
+
+picture slopefield(real f(real,real), pair a, pair b,
+ int nx=nmesh, int ny=nx,
+ real tickfactor=0.5, pen p=currentpen, arrowbar arrow=None)
+{
+ picture pic;
+ real dx=(b.x-a.x)/nx;
+ real dy=(b.y-a.y)/ny;
+ real step=0.5*tickfactor*min(dx,dy);
+
+ for(int i=0; i <= nx; ++i) {
+ real x=a.x+i*dx;
+ for(int j=0; j <= ny; ++j) {
+ pair cp=(x,a.y+j*dy);
+ real slope=f(cp.x,cp.y);
+ real mp=step/sqrt(1+slope^2);
+ draw(pic,(cp.x-mp,cp.y-mp*slope)--(cp.x+mp,cp.y+mp*slope),p,arrow);
+ }
+ }
+ clip(pic,box(a,b));
+ return pic;
+}
+
+picture slopefield(real f(real), pair a, pair b,
+ int nx=nmesh, int ny=nx, pen p=currentpen,
+ arrowbar arrow=None)
+{
+ return slopefield(new real(real x, real y) {return f(x);},a,b,nx,ny,p,arrow);
+}
+
+path curve(pair c, real f(real,real), pair a, pair b)
+{
+ real step=stepfraction*(b.x-a.x);
+ real halfstep=0.5*step;
+ real sixthstep=step/6;
+
+ path follow(real sign) {
+ pair cp=c;
+ guide g=cp;
+ real dx,dy;
+ real factor=1;
+ do {
+ real slope;
+ pair S(pair z) {
+ slope=f(z.x,z.y);
+ return factor*sign/sqrt(1+slope^2)*(1,slope);
+ }
+ pair S3;
+ pair advance() {
+ pair S0=S(cp);
+ pair S1=S(cp+halfstep*S0);
+ pair S2=S(cp+halfstep*S1);
+ S3=S(cp+step*S2);
+ pair cp0=cp+sixthstep*(S0+2S1+2S2+S3);
+ dx=min(cp0.x-a.x,b.x-cp0.x);
+ dy=min(cp0.y-a.y,b.y-cp0.y);
+ return cp0;
+ }
+ pair cp0=advance();
+ if(dx < 0) {
+ factor=(step+dx)/step;
+ cp0=advance();
+ g=g..{S3}cp0{S3};
+ break;
+ }
+ if(dy < 0) {
+ factor=(step+dy)/step;
+ cp0=advance();
+ g=g..{S3}cp0{S3};
+ break;
+ }
+ cp=cp0;
+ g=g..{S3}cp{S3};
+ } while (dx > 0 && dy > 0);
+ return g;
+ }
+
+ return reverse(follow(-1))&follow(1);
+}
+
+path curve(pair c, real f(real), pair a, pair b)
+{
+ return curve(c,new real(real x, real y){return f(x);},a,b);
+}
+
diff --git a/graphics/asymptote/base/smoothcontour3.asy b/graphics/asymptote/base/smoothcontour3.asy
new file mode 100644
index 0000000000..be6f900a4d
--- /dev/null
+++ b/graphics/asymptote/base/smoothcontour3.asy
@@ -0,0 +1,1582 @@
+// Copyright 2015 Charles Staats III
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// smoothcontour3
+// An Asymptote module for drawing smooth implicitly defined surfaces
+// author: Charles Staats III
+// charles dot staats dot iii at gmail dot com
+
+import graph_settings; // for nmesh
+import three;
+import math;
+
+/***********************************************/
+/******** CREATING BEZIER PATCHES **************/
+/******** WITH SPECIFIED NORMALS **************/
+/***********************************************/
+
+// The weight given to minimizing the sum of squares of
+// the mixed partials at the corners of the bezier patch.
+// If this weight is zero, the result is undefined in
+// places and can be rather wild even where it is
+// defined.
+// The struct is used to as a namespace.
+struct pathwithnormals_settings {
+ static real wildnessweight = 1e-3;
+}
+private from pathwithnormals_settings unravel wildnessweight;
+
+// The Bernstein basis polynomials of degree 3:
+real B03(real t) { return (1-t)^3; }
+real B13(real t) { return 3*t*(1-t)^2; }
+real B23(real t) { return 3*t^2*(1-t); }
+real B33(real t) { return t^3; }
+
+private typedef real function(real);
+function[] bernstein = new function[] {B03, B13, B23, B33};
+
+// This function attempts to produce a Bezier patch
+// with the specified boundary path and normal directions.
+// For instance, the patch should be normal to
+// u0normals[0] at (0, 0.25),
+// normal to u0normals[1] at (0, 0.5), and
+// normal to u0normals[2] at (0, 0.75).
+// The actual normal (as computed by the patch.normal() function)
+// may be parallel to the specified normal, antiparallel, or
+// even zero.
+//
+// A small amount of deviation is allowed in order to stabilize
+// the algorithm (by keeping the mixed partials at the corners from
+// growing too large).
+//
+// Note that the specified normals are projected to be orthogonal to
+// the specified boundary path. However, the entries in the array
+// remain intact.
+patch patchwithnormals(path3 external, triple[] u0normals, triple[] u1normals,
+ triple[] v0normals, triple[] v1normals)
+{
+ assert(cyclic(external));
+ assert(length(external) == 4);
+ assert(u0normals.length == 3);
+ assert(u1normals.length == 3);
+ assert(v0normals.length == 3);
+ assert(v1normals.length == 3);
+
+ triple[][] controlpoints = new triple[4][4];
+ controlpoints[0][0] = point(external,0);
+ controlpoints[1][0] = postcontrol(external,0);
+ controlpoints[2][0] = precontrol(external,1);
+ controlpoints[3][0] = point(external,1);
+ controlpoints[3][1] = postcontrol(external,1);
+ controlpoints[3][2] = precontrol(external,2);
+ controlpoints[3][3] = point(external,2);
+ controlpoints[2][3] = postcontrol(external,2);
+ controlpoints[1][3] = precontrol(external,3);
+ controlpoints[0][3] = point(external,3);
+ controlpoints[0][2] = postcontrol(external,3);
+ controlpoints[0][1] = precontrol(external, 4);
+
+ real[][] matrix = new real[24][12];
+ for (int i = 0; i < matrix.length; ++i)
+ for (int j = 0; j < matrix[i].length; ++j)
+ matrix[i][j] = 0;
+ real[] rightvector = new real[24];
+ for (int i = 0; i < rightvector.length; ++i)
+ rightvector[i] = 0;
+
+ void addtocoeff(int i, int j, int count, triple coeffs) {
+ if (1 <= i && i <= 2 && 1 <= j && j <= 2) {
+ int position = 3 * (2 * (i-1) + (j-1));
+ matrix[count][position] += coeffs.x;
+ matrix[count][position+1] += coeffs.y;
+ matrix[count][position+2] += coeffs.z;
+ } else {
+ rightvector[count] -= dot(controlpoints[i][j], coeffs);
+ }
+ }
+
+ void addtocoeff(int i, int j, int count, real coeff) {
+ if (1 <= i && i <= 2 && 1 <= j && j <= 2) {
+ int position = 3 * (2 * (i-1) + (j-1));
+ matrix[count][position] += coeff;
+ matrix[count+1][position+1] += coeff;
+ matrix[count+2][position+2] += coeff;
+ } else {
+ rightvector[count] -= controlpoints[i][j].x * coeff;
+ rightvector[count+1] -= controlpoints[i][j].y * coeff;
+ rightvector[count+2] -= controlpoints[i][j].z * coeff;
+ }
+ }
+
+ int count = 0;
+
+ void apply_u0(int j, real a, triple n) {
+ real factor = 3 * bernstein[j](a);
+ addtocoeff(0,j,count,-factor*n);
+ addtocoeff(1,j,count,factor*n);
+ }
+ void apply_u0(real a, triple n) {
+ triple tangent = dir(external, 4-a);
+ n -= dot(n,tangent)*tangent;
+ n = unit(n);
+ for (int j = 0; j < 4; ++j) {
+ apply_u0(j,a,n);
+ }
+ ++count;
+ }
+ apply_u0(0.25, u0normals[0]);
+ apply_u0(0.5, u0normals[1]);
+ apply_u0(0.75, u0normals[2]);
+
+ void apply_u1(int j, real a, triple n) {
+ real factor = 3 * bernstein[j](a);
+ addtocoeff(3,j,count,factor*n);
+ addtocoeff(2,j,count,-factor*n);
+ }
+ void apply_u1(real a, triple n) {
+ triple tangent = dir(external, 1+a);
+ n -= dot(n,tangent)*tangent;
+ n = unit(n);
+ for (int j = 0; j < 4; ++j)
+ apply_u1(j,a,n);
+ ++count;
+ }
+ apply_u1(0.25, u1normals[0]);
+ apply_u1(0.5, u1normals[1]);
+ apply_u1(0.75, u1normals[2]);
+
+ void apply_v0(int i, real a, triple n) {
+ real factor = 3 * bernstein[i](a);
+ addtocoeff(i,0,count,-factor*n);
+ addtocoeff(i,1,count,factor*n);
+ }
+ void apply_v0(real a, triple n) {
+ triple tangent = dir(external, a);
+ n -= dot(n,tangent) * tangent;
+ n = unit(n);
+ for (int i = 0; i < 4; ++i)
+ apply_v0(i,a,n);
+ ++count;
+ }
+ apply_v0(0.25, v0normals[0]);
+ apply_v0(0.5, v0normals[1]);
+ apply_v0(0.75, v0normals[2]);
+
+ void apply_v1(int i, real a, triple n) {
+ real factor = 3 * bernstein[i](a);
+ addtocoeff(i,3,count,factor*n);
+ addtocoeff(i,2,count,-factor*n);
+ }
+ void apply_v1(real a, triple n) {
+ triple tangent = dir(external, 3-a);
+ n -= dot(n,tangent)*tangent;
+ n = unit(n);
+ for (int i = 0; i < 4; ++i)
+ apply_v1(i,a,n);
+ ++count;
+ }
+ apply_v1(0.25, v1normals[0]);
+ apply_v1(0.5, v1normals[1]);
+ apply_v1(0.75, v1normals[2]);
+
+ addtocoeff(0,0,count,9*wildnessweight);
+ addtocoeff(1,1,count,9*wildnessweight);
+ addtocoeff(0,1,count,-9*wildnessweight);
+ addtocoeff(1,0,count,-9*wildnessweight);
+ count+=3;
+ addtocoeff(3,3,count,9*wildnessweight);
+ addtocoeff(2,2,count,9*wildnessweight);
+ addtocoeff(3,2,count,-9*wildnessweight);
+ addtocoeff(2,3,count,-9*wildnessweight);
+ count+=3;
+ addtocoeff(0,3,count,9*wildnessweight);
+ addtocoeff(1,2,count,9*wildnessweight);
+ addtocoeff(1,3,count,-9*wildnessweight);
+ addtocoeff(0,2,count,-9*wildnessweight);
+ count += 3;
+ addtocoeff(3,0,count,9*wildnessweight);
+ addtocoeff(2,1,count,9*wildnessweight);
+ addtocoeff(3,1,count,-9*wildnessweight);
+ addtocoeff(2,0,count,-9*wildnessweight);
+ count += 3;
+
+ real[] solution = leastsquares(matrix, rightvector, warn=false);
+ if (solution.length == 0) { // if the matrix was singular
+ write("Warning: unable to solve matrix for specifying edge normals "
+ + "on bezier patch. Using coons patch.");
+ return patch(external);
+ }
+
+ for (int i = 1; i <= 2; ++i) {
+ for (int j = 1; j <= 2; ++j) {
+ int position = 3 * (2 * (i-1) + (j-1));
+ controlpoints[i][j] = (solution[position],
+ solution[position+1],
+ solution[position+2]);
+ }
+ }
+
+ return patch(controlpoints);
+}
+
+// This function attempts to produce a Bezier triangle
+// with the specified boundary path and normal directions at the
+// edge midpoints. The bezier triangle should be normal to
+// n1 at point(external, 0.5),
+// normal to n2 at point(external, 1.5), and
+// normal to n3 at point(external, 2.5).
+// The actual normal (as computed by the patch.normal() function)
+// may be parallel to the specified normal, antiparallel, or
+// even zero.
+//
+// A small amount of deviation is allowed in order to stabilize
+// the algorithm (by keeping the mixed partials at the corners from
+// growing too large).
+patch trianglewithnormals(path3 external, triple n1,
+ triple n2, triple n3) {
+ assert(cyclic(external));
+ assert(length(external) == 3);
+ // Use the formal symbols a3, a2b, abc, etc. to denote the control points,
+ // following the Wikipedia article on Bezier triangles.
+ triple a3 = point(external, 0), a2b = postcontrol(external, 0),
+ ab2 = precontrol(external, 1), b3 = point(external, 1),
+ b2c = postcontrol(external, 1), bc2 = precontrol(external, 2),
+ c3 = point(external, 2), ac2 = postcontrol(external, 2),
+ a2c = precontrol(external, 0);
+
+ // Use orthogonal projection to ensure that the normal vectors are
+ // actually normal to the boundary path.
+ triple tangent = dir(external, 0.5);
+ n1 -= dot(n1,tangent)*tangent;
+ n1 = unit(n1);
+
+ tangent = dir(external, 1.5);
+ n2 -= dot(n2,tangent)*tangent;
+ n2 = unit(n2);
+
+ tangent = dir(external, 2.5);
+ n3 -= dot(n3,tangent)*tangent;
+ n3 = unit(n3);
+
+ real wild = 2 * wildnessweight;
+ real[][] matrix = { {n1.x, n1.y, n1.z},
+ {n2.x, n2.y, n2.z},
+ {n3.x, n3.y, n3.z},
+ { wild, 0, 0},
+ { 0, wild, 0},
+ { 0, 0, wild} };
+ real[] rightvector =
+ { dot(n1, (a3 + 3a2b + 3ab2 + b3 - 2a2c - 2b2c)) / 4,
+ dot(n2, (b3 + 3b2c + 3bc2 + c3 - 2ab2 - 2ac2)) / 4,
+ dot(n3, (c3 + 3ac2 + 3a2c + a3 - 2bc2 - 2a2b)) / 4 };
+
+ // The inner control point that minimizes the sum of squares of
+ // the mixed partials on the corners.
+ triple tameinnercontrol =
+ ((a2b + a2c - a3) + (ab2 + b2c - b3) + (ac2 + bc2 - c3)) / 3;
+ rightvector.append(wild * new real[]
+ {tameinnercontrol.x, tameinnercontrol.y, tameinnercontrol.z});
+ real[] solution = leastsquares(matrix, rightvector, warn=false);
+ if (solution.length == 0) { // if the matrix was singular
+ write("Warning: unable to solve matrix for specifying edge normals "
+ + "on bezier triangle. Using coons triangle.");
+ return patch(external);
+ }
+ triple innercontrol = (solution[0], solution[1], solution[2]);
+ return patch(external, innercontrol);
+}
+
+// A wrapper for the previous functions when the normal direction
+// is given as a function of direction. The wrapper can also
+// accommodate cyclic boundary paths of between one and four
+// segments, although the results are best by far when there
+// are three or four segments.
+patch patchwithnormals(path3 external, triple normalat(triple)) {
+ assert(cyclic(external));
+ assert(1 <= length(external) && length(external) <= 4);
+ if (length(external) == 3) {
+ triple n1 = normalat(point(external, 0.5));
+ triple n2 = normalat(point(external, 1.5));
+ triple n3 = normalat(point(external, 2.5));
+ return trianglewithnormals(external, n1, n2, n3);
+ }
+ while (length(external) < 4) external = external -- cycle;
+ triple[] u0normals = new triple[3];
+ triple[] u1normals = new triple[3];
+ triple[] v0normals = new triple[3];
+ triple[] v1normals = new triple[3];
+ for (int i = 1; i <= 3; ++i) {
+ v0normals[i-1] = unit(normalat(point(external, i/4)));
+ u1normals[i-1] = unit(normalat(point(external, 1 + i/4)));
+ v1normals[i-1] = unit(normalat(point(external, 3 - i/4)));
+ u0normals[i-1] = unit(normalat(point(external, 4 - i/4)));
+ }
+ return patchwithnormals(external, u0normals, u1normals, v0normals, v1normals);
+}
+
+/***********************************************/
+/********* DUAL CUBE GRAPH UTILITY *************/
+/***********************************************/
+
+// Suppose a plane intersects a (hollow) cube, and
+// does not intersect any vertices. Then its intersection
+// with cube forms a cycle. The goal of the code below
+// is to reconstruct the order of the cycle
+// given only an unordered list of which edges the plane
+// intersects.
+//
+// Basically, the question is this: If we know the points
+// in which a more-or-less planar surface intersects the
+// edges of cube, how do we connect those points?
+//
+// When I wrote the code, I was thinking in terms of the
+// dual graph of a cube, in which "vertices" are really
+// faces of the cube and "edges" connect those "vertices."
+
+// An enum for the different "vertices" (i.e. faces)
+// available. NULL_VERTEX is primarily intended as a
+// return value to indicate the absence of a desired
+// vertex.
+private int NULL_VERTEX = -1;
+private int XHIGH = 0;
+private int XLOW = 1;
+private int YHIGH = 2;
+private int YLOW = 3;
+private int ZHIGH = 4;
+private int ZLOW = 5;
+
+// An unordered set of nonnegative integers.
+// Since the intent is to use
+// only the six values from the enum above, no effort
+// was made to use scalable algorithms.
+struct intset {
+ private bool[] ints = new bool[0];
+ private int size = 0;
+
+ bool contains(int item) {
+ assert(item >= 0);
+ if (item >= ints.length) return false;
+ return ints[item];
+ }
+
+ // Returns true if the item was added (i.e., was
+ // not already present).
+ bool add(int item) {
+ assert(item >= 0);
+ while (item >= ints.length) ints.push(false);
+ if (ints[item]) return false;
+ ints[item] = true;
+ ++size;
+ return true;
+ }
+
+ int[] elements() {
+ int[] toreturn;
+ for (int i = 0; i < ints.length; ++i) {
+ if (ints[i]) toreturn.push(i);
+ }
+ return toreturn;
+ }
+
+ int size() { return size; }
+}
+
+// A map from integers to sets of integers. Again, no
+// attempt is made to use scalable data structures.
+struct int_to_intset {
+ int[] keys = new int[0];
+ intset[] values = new intset[0];
+
+ void add(int key, int value) {
+ for (int i = 0; i < keys.length; ++i) {
+ if (keys[i] == key) {
+ values[i].add(value);
+ return;
+ }
+ }
+ keys.push(key);
+ intset newset;
+ values.push(newset);
+ newset.add(value);
+ }
+
+ private int indexOf(int key) {
+ for (int i = 0; i < keys.length; ++i) {
+ if (keys[i] == key) return i;
+ }
+ return -1;
+ }
+
+ int[] get(int key) {
+ int i = indexOf(key);
+ if (i < 0) return new int[0];
+ else return values[i].elements();
+ }
+
+ int numvalues(int key) {
+ int i = indexOf(key);
+ if (i < 0) return 0;
+ else return values[i].size();
+ }
+
+ int numkeys() {
+ return keys.length;
+ }
+}
+
+// A struct intended to represent an undirected edge between
+// two "vertices."
+struct edge {
+ int start;
+ int end;
+ void operator init(int a, int b) {
+ start = a;
+ end = b;
+ }
+ bool bordersvertex(int v) { return start == v || end == v; }
+}
+
+string operator cast(edge e) {
+ int a, b;
+ if (e.start <= e.end) {a = e.start; b = e.end;}
+ else {a = e.end; b = e.start; }
+ return (string)a + " <-> " + (string)b;
+}
+
+bool operator == (edge a, edge b) {
+ if (a.start == b.start && a.end == b.end) return true;
+ if (a.start == b.end && a.end == b.start) return true;
+ return false;
+}
+
+string operator cast(edge[] edges) {
+ string toreturn = "{ ";
+ for (int i = 0; i < edges.length; ++i) {
+ toreturn += edges[i];
+ if (i < edges.length-1) toreturn += ", ";
+ }
+ return toreturn + " }";
+}
+
+// Finally, the function that strings together a list of edges
+// into a cycle. It makes assumptions that hold true if the
+// list of edges did in fact come from a plane intersection
+// containing no vertices of the cube. For instance, such a
+// plane can contain at most two noncollinear points of any
+// one face; consequently, no face can border more than two of
+// the selected edges.
+//
+// If the underlying assumptions prove to be false, the function
+// returns null.
+int[] makecircle(edge[] edges) {
+ if (edges.length == 0) return new int[0];
+ int_to_intset graph;
+ for (edge e : edges) {
+ graph.add(e.start, e.end);
+ graph.add(e.end, e.start);
+ }
+ int currentvertex = edges[0].start;
+ int startvertex = currentvertex;
+ int lastvertex = NULL_VERTEX;
+ int[] toreturn = new int[0];
+ do {
+ toreturn.push(currentvertex);
+ int[] adjacentvertices = graph.get(currentvertex);
+ if (adjacentvertices.length != 2) return null;
+ for (int v : adjacentvertices) {
+ if (v != lastvertex) {
+ lastvertex = currentvertex;
+ currentvertex = v;
+ break;
+ }
+ }
+ } while (currentvertex != startvertex);
+ if (toreturn.length != graph.numkeys()) return null;
+ toreturn.cyclic = true;
+ return toreturn;
+}
+
+/***********************************************/
+/********** PATHS BETWEEN POINTS ***************/
+/***********************************************/
+// Construct paths between two points with additional
+// constraints; for instance, the path must be orthogonal
+// to a certain vector at each of the endpoints, must
+// lie within a specified plane or a specified face
+// of a rectangular solid,....
+
+// A vector (typically a normal vector) at a specified position.
+struct positionedvector {
+ triple position;
+ triple direction;
+ void operator init(triple position, triple direction) {
+ this.position = position;
+ this.direction = direction;
+ }
+}
+
+string operator cast(positionedvector vv) {
+ return "position: " + (string)(vv.position) + " vector: " + (string)vv.direction;
+}
+
+// The angle, in degrees, between two vectors.
+real angledegrees(triple a, triple b) {
+ real dotprod = dot(a,b);
+ real lengthprod = max(abs(a) * abs(b), abs(dotprod));
+ if (lengthprod == 0) return 0;
+ return aCos(dotprod / lengthprod);
+}
+
+// A path (single curved segment) between two points. At each point
+// is specified a vector orthogonal to the path.
+path3 pathbetween(positionedvector v1, positionedvector v2) {
+ triple n1 = unit(v1.direction);
+ triple n2 = unit(v2.direction);
+
+ triple p1 = v1.position;
+ triple p2 = v2.position;
+ triple delta = p2-p1;
+
+ triple dir1 = delta - dot(delta, n1)*n1;
+ triple dir2 = delta - dot(delta, n2)*n2;
+ return p1 {dir1} .. {dir2} p2;
+}
+
+// Assuming v1 and v2 are linearly independent, returns an array {a, b}
+// such that a v1 + b v2 is the orthogonal projection of toproject onto
+// the span of v1 and v2. If v1 and v2 are dependent, returns an empty array
+// (if warn==false) or throws an error (if warn==true).
+real[] projecttospan_findcoeffs(triple toproject, triple v1, triple v2,
+ bool warn=false) {
+ real[][] matrix = {{v1.x, v2.x},
+ {v1.y, v2.y},
+ {v1.z, v2.z}};
+ real[] desiredanswer = {toproject.x, toproject.y, toproject.z};
+ return leastsquares(matrix, desiredanswer, warn=warn);
+}
+
+// Project the triple toproject into the span of a and b, but restrict
+// to the quarter-plane of linear combinations a v1 + b v2 such that
+// a >= mincoeff and b >= mincoeff. If v1 and v2 are linearly dependent,
+// return a random (positive) linear combination.
+triple projecttospan(triple toproject, triple v1, triple v2,
+ real mincoeff = 0.05) {
+ real[] coeffs = projecttospan_findcoeffs(toproject, v1, v2, warn=false);
+ real a, b;
+ if (coeffs.length == 0) {
+ a = mincoeff + unitrand();
+ b = mincoeff + unitrand();
+ } else {
+ a = max(coeffs[0], mincoeff);
+ b = max(coeffs[1], mincoeff);
+ }
+ return a*v1 + b*v2;
+}
+
+// A path between two specified vertices of a cyclic path. The
+// path tangent at each endpoint is guaranteed to lie within the
+// quarter-plane spanned by positive linear combinations of the
+// tangents of the two outgoing paths at that endpoint.
+path3 pathbetween(path3 edgecycle, int vertex1, int vertex2) {
+ triple point1 = point(edgecycle, vertex1);
+ triple point2 = point(edgecycle, vertex2);
+
+ triple v1 = -dir(edgecycle, vertex1, sign=-1);
+ triple v2 = dir(edgecycle, vertex1, sign= 1);
+ triple direction1 = projecttospan(unit(point2-point1), v1, v2);
+
+ v1 = -dir(edgecycle, vertex2, sign=-1);
+ v2 = dir(edgecycle, vertex2, sign= 1);
+ triple direction2 = projecttospan(unit(point1-point2), v1, v2);
+
+ return point1 {direction1} .. {-direction2} point2;
+}
+
+// This function applies a heuristic to choose two "opposite"
+// vertices (separated by three segments) of edgecycle, which
+// is required to be a cyclic path consisting of 5 or 6 segments.
+// The two chosen vertices are pushed to savevertices.
+//
+// The function returns a path between the two chosen vertices. The
+// path tangent at each endpoint is guaranteed to lie within the
+// quarter-plane spanned by positive linear combinations of the
+// tangents of the two outgoing paths at that endpoint.
+path3 bisector(path3 edgecycle, int[] savevertices) {
+ real mincoeff = 0.05;
+ assert(cyclic(edgecycle));
+ int n = length(edgecycle);
+ assert(n >= 5 && n <= 6);
+ triple[] forwarddirections = sequence(new triple(int i) {
+ return dir(edgecycle, i, sign=1);
+ }, n);
+ forwarddirections.cyclic = true;
+ triple[] backwarddirections = sequence(new triple(int i) {
+ return -dir(edgecycle, i, sign=-1);
+ }, n);
+ backwarddirections.cyclic = true;
+ real[] angles = sequence(new real(int i) {
+ return angledegrees(forwarddirections[i], backwarddirections[i]);
+ }, n);
+ angles.cyclic = true;
+ int lastindex = (n == 5 ? 4 : 2);
+ real maxgoodness = 0;
+ int chosenindex = -1;
+ triple directionout, directionin;
+ for (int i = 0; i <= lastindex; ++i) {
+ int opposite = i + 3;
+ triple vec = unit(point(edgecycle, opposite) - point(edgecycle, i));
+ real[] coeffsbegin = projecttospan_findcoeffs(vec, forwarddirections[i],
+ backwarddirections[i]);
+ if (coeffsbegin.length == 0) continue;
+ coeffsbegin[0] = max(coeffsbegin[0], mincoeff);
+ coeffsbegin[1] = max(coeffsbegin[1], mincoeff);
+
+ real[] coeffsend = projecttospan_findcoeffs(-vec, forwarddirections[opposite],
+ backwarddirections[opposite]);
+ if (coeffsend.length == 0) continue;
+ coeffsend[0] = max(coeffsend[0], mincoeff);
+ coeffsend[1] = max(coeffsend[1], mincoeff);
+
+ real goodness = angles[i] * angles[opposite] * coeffsbegin[0] * coeffsend[0]
+ * coeffsbegin[1] * coeffsend[1];
+ if (goodness > maxgoodness) {
+ maxgoodness = goodness;
+ directionout = coeffsbegin[0] * forwarddirections[i] +
+ coeffsbegin[1] * backwarddirections[i];
+ directionin = -(coeffsend[0] * forwarddirections[opposite] +
+ coeffsend[1] * backwarddirections[opposite]);
+ chosenindex = i;
+ }
+ }
+ if (chosenindex == -1) {
+ savevertices.push(0);
+ savevertices.push(3);
+ return pathbetween(edgecycle, 0, 3);
+ } else {
+ savevertices.push(chosenindex);
+ savevertices.push(chosenindex+3);
+ return point(edgecycle, chosenindex) {directionout} ..
+ {directionin} point(edgecycle, chosenindex + 3);
+ }
+}
+
+// A path between two specified points (with specified normals) that lies
+// within a specified face of a rectangular solid.
+path3 pathinface(positionedvector v1, positionedvector v2,
+ triple facenorm, triple edge1normout, triple edge2normout)
+{
+ triple dir1 = cross(v1.direction, facenorm);
+ real dotprod = dot(dir1, edge1normout);
+ if (dotprod > 0) dir1 = -dir1;
+ // Believe it or not, this "tiebreaker" is actually relevant at times,
+ // for instance, when graphing the cone x^2 + y^2 = z^2 over the region
+ // -1 <= x,y,z <= 1.
+ else if (dotprod == 0 && dot(dir1, v2.position - v1.position) < 0) dir1 = -dir1;
+
+ triple dir2 = cross(v2.direction, facenorm);
+ dotprod = dot(dir2, edge2normout);
+ if (dotprod < 0) dir2 = -dir2;
+ else if (dotprod == 0 && dot(dir2, v2.position - v1.position) < 0) dir2 = -dir2;
+
+ return v1.position {dir1} .. {dir2} v2.position;
+}
+
+triple normalout(int face) {
+ if (face == XHIGH) return X;
+ else if (face == YHIGH) return Y;
+ else if (face == ZHIGH) return Z;
+ else if (face == XLOW) return -X;
+ else if (face == YLOW) return -Y;
+ else if (face == ZLOW) return -Z;
+ else return O;
+}
+
+// A path between two specified points (with specified normals) that lies
+// within a specified face of a rectangular solid.
+path3 pathinface(positionedvector v1, positionedvector v2,
+ int face, int edge1face, int edge2face) {
+ return pathinface(v1, v2, normalout(face), normalout(edge1face),
+ normalout(edge2face));
+}
+
+/***********************************************/
+/******** DRAWING IMPLICIT SURFACES ************/
+/***********************************************/
+
+// DEPRECATED
+// Quadrilateralization:
+// Produce a surface (array of *nondegenerate* Bezier patches) with a
+// specified three-segment boundary. The surface should approximate the
+// zero locus of the specified f with its specified gradient.
+//
+// If it is not possible to produce the desired result without leaving the
+// specified rectangular region, returns a length-zero array.
+//
+// Dividing a triangle into smaller quadrilaterals this way is opposite
+// the usual trend in mathematics. However, *before the introduction of bezier
+// triangles,* the pathwithnormals algorithm
+// did a poor job of choosing a good surface when the boundary path did
+// not consist of four positive-length segments.
+patch[] triangletoquads(path3 external, real f(triple), triple grad(triple),
+ triple a, triple b) {
+ static real epsilon = 1e-3;
+ assert(length(external) == 3);
+ assert(cyclic(external));
+
+ triple c0 = point(external, 0);
+ triple c1 = point(external, 1);
+ triple c2 = point(external, 2);
+
+ triple center = (c0 + c1 + c2) / 3;
+ triple n = unit(cross(c1-c0, c2-c0));
+
+ real g(real t) { return f(center + t*n); }
+
+ real tmin = -realMax, tmax = realMax;
+ void absorb(real t) {
+ if (t < 0) tmin = max(t,tmin);
+ else tmax = min(t,tmax);
+ }
+ if (n.x != 0) {
+ absorb((a.x - center.x) / n.x);
+ absorb((b.x - center.x) / n.x);
+ }
+ if (n.y != 0) {
+ absorb((a.y - center.y) / n.y);
+ absorb((b.y - center.y) / n.y);
+ }
+ if (n.z != 0) {
+ absorb((a.z - center.z) / n.z);
+ absorb((b.z - center.z) / n.z);
+ }
+
+ real fa = g(tmin);
+ real fb = g(tmax);
+ if ((fa > 0 && fb > 0) || (fa < 0 && fb < 0)) {
+ return new patch[0];
+ } else {
+ real t = findroot(g, tmin, tmax, fa=fa, fb=fb);
+ center += t * n;
+ }
+
+ n = unit(grad(center));
+
+ triple m0 = point(external, 0.5);
+ positionedvector m0 = positionedvector(m0, unit(grad(m0)));
+ triple m1 = point(external, 1.5);
+ positionedvector m1 = positionedvector(m1, unit(grad(m1)));
+ triple m2 = point(external, 2.5);
+ positionedvector m2 = positionedvector(m2, unit(grad(m2)));
+ positionedvector c = positionedvector(center, unit(grad(center)));
+
+ path3 pathto_m0 = pathbetween(c, m0);
+ path3 pathto_m1 = pathbetween(c, m1);
+ path3 pathto_m2 = pathbetween(c, m2);
+
+ path3 quad0 = subpath(external, 0, 0.5)
+ & reverse(pathto_m0)
+ & pathto_m2
+ & subpath(external, -0.5, 0)
+ & cycle;
+ path3 quad1 = subpath(external, 1, 1.5)
+ & reverse(pathto_m1)
+ & pathto_m0
+ & subpath(external, 0.5, 1)
+ & cycle;
+ path3 quad2 = subpath(external, 2, 2.5)
+ & reverse(pathto_m2)
+ & pathto_m1
+ & subpath(external, 1.5, 2)
+ & cycle;
+
+ return new patch[] {patchwithnormals(quad0, grad),
+ patchwithnormals(quad1, grad),
+ patchwithnormals(quad2, grad)};
+}
+
+// Attempts to fill the path external (which should by a cyclic path consisting of
+// three segments) with bezier triangle(s). Returns an empty array if it fails.
+//
+// In more detail: A single bezier triangle is computed using trianglewithnormals. The normals of
+// the resulting triangle at the midpoint of each edge are computed. If any of these normals
+// is in the negative f direction, the external triangle is subdivided into four external triangles
+// and the same procedure is applied to each. If one or more of them has an incorrectly oriented
+// edge normal, the function gives up and returns an empty array.
+//
+// Thus, the returned array consists of 0, 1, or 4 bezier triangles; no other array lengths
+// are possible.
+//
+// This function assumes that the path orientation is consistent with f (and its gradient)
+// -- i.e., that
+// at a corner, (tangent in) x (tangent out) is in the positive f direction.
+patch[] maketriangle(path3 external, real f(triple),
+ triple grad(triple), bool allowsubdivide = true) {
+ assert(cyclic(external));
+ assert(length(external) == 3);
+ triple m1 = point(external, 0.5);
+ triple n1 = unit(grad(m1));
+ triple m2 = point(external, 1.5);
+ triple n2 = unit(grad(m2));
+ triple m3 = point(external, 2.5);
+ triple n3 = unit(grad(m3));
+ patch beziertriangle = trianglewithnormals(external, n1, n2, n3);
+ if (dot(n1, beziertriangle.normal(0.5, 0)) >= 0 &&
+ dot(n2, beziertriangle.normal(0.5, 0.5)) >= 0 &&
+ dot(n3, beziertriangle.normal(0, 0.5)) >= 0)
+ return new patch[] {beziertriangle};
+
+ if (!allowsubdivide) return new patch[0];
+
+ positionedvector m1 = positionedvector(m1, n1);
+ positionedvector m2 = positionedvector(m2, n2);
+ positionedvector m3 = positionedvector(m3, n3);
+ path3 p12 = pathbetween(m1, m2);
+ path3 p23 = pathbetween(m2, m3);
+ path3 p31 = pathbetween(m3, m1);
+ patch[] triangles = maketriangle(p12 & p23 & p31 & cycle, f, grad=grad,
+ allowsubdivide=false);
+ if (triangles.length < 1) return new patch[0];
+
+ triangles.append(maketriangle(subpath(external, -0.5, 0.5) & reverse(p31) & cycle,
+ f, grad=grad, allowsubdivide=false));
+ if (triangles.length < 2) return new patch[0];
+
+ triangles.append(maketriangle(subpath(external, 0.5, 1.5) & reverse(p12) & cycle,
+ f, grad=grad, allowsubdivide=false));
+ if (triangles.length < 3) return new patch[0];
+
+ triangles.append(maketriangle(subpath(external, 1.5, 2.5) & reverse(p23) & cycle,
+ f, grad=grad, allowsubdivide=false));
+ if (triangles.length < 4) return new patch[0];
+
+ return triangles;
+}
+
+
+// Returns true if the point is "nonsingular" (in the sense that the magnitude
+// of the gradient is not too small) AND very close to the zero locus of f
+// (assuming f is locally linear).
+bool check_fpt_zero(triple testpoint, real f(triple), triple grad(triple)) {
+ real testval = f(testpoint);
+ real slope = abs(grad(testpoint));
+ static real tolerance = 2*rootfinder_settings.roottolerance;
+ return !(slope > tolerance && abs(testval) / slope > tolerance);
+}
+
+// Returns true if pt lies within the rectangular solid with
+// opposite corners at a and b.
+bool checkptincube(triple pt, triple a, triple b) {
+ real xmin = a.x;
+ real xmax = b.x;
+ real ymin = a.y;
+ real ymax = b.y;
+ real zmin = a.z;
+ real zmax = b.z;
+ if (xmin > xmax) { real t = xmax; xmax=xmin; xmin=t; }
+ if (ymin > ymax) { real t = ymax; ymax=ymin; ymin=t; }
+ if (zmin > zmax) { real t = zmax; zmax=zmin; zmin=t; }
+
+ return ((xmin <= pt.x) && (pt.x <= xmax) &&
+ (ymin <= pt.y) && (pt.y <= ymax) &&
+ (zmin <= pt.z) && (pt.z <= zmax));
+
+}
+
+// A convenience function for combining the previous two tests.
+bool checkpt(triple testpt, real f(triple), triple grad(triple),
+ triple a, triple b) {
+ return checkptincube(testpt, a, b) &&
+ check_fpt_zero(testpt, f, grad);
+}
+
+// Attempts to fill in the boundary cycle with a collection of
+// patches to approximate smoothly the zero locus of f. If unable to
+// do so while satisfying certain checks, returns null.
+// This is distinct from returning an empty
+// array, which merely indicates that the boundary cycle is too small
+// to be worth filling in.
+patch[] quadpatches(path3 edgecycle, positionedvector[] corners,
+ real f(triple), triple grad(triple),
+ triple a, triple b, bool usetriangles) {
+ assert(corners.cyclic);
+
+ // The tolerance for considering two points "essentially identical."
+ static real tolerance = 2.5 * rootfinder_settings.roottolerance;
+
+ // If there are two neighboring vertices that are essentially identical,
+ // unify them into one.
+ for (int i = 0; i < corners.length; ++i) {
+ if (abs(corners[i].position - corners[i+1].position) < tolerance) {
+ if (corners.length == 2) return new patch[0];
+ corners.delete(i);
+ edgecycle = subpath(edgecycle, 0, i)
+ & subpath(edgecycle, i+1, length(edgecycle))
+ & cycle;
+ --i;
+ assert(length(edgecycle) == corners.length);
+ }
+ }
+
+ static real areatolerance = tolerance^2;
+
+ assert(corners.length >= 2);
+ if (corners.length == 2) {
+ // If the area is too small, just ignore it; otherwise, subdivide.
+ real area0 = abs(cross(-dir(edgecycle, 0, sign=-1, normalize=false),
+ dir(edgecycle, 0, sign=1, normalize=false)));
+ real area1 = abs(cross(-dir(edgecycle, 1, sign=-1, normalize=false),
+ dir(edgecycle, 1, sign=1, normalize=false)));
+ if (area0 < areatolerance && area1 < areatolerance) return new patch[0];
+ else return null;
+ }
+ if (length(edgecycle) > 6) abort("too many edges: not possible.");
+
+ for (int i = 0; i < length(edgecycle); ++i) {
+ if (angledegrees(dir(edgecycle,i,sign=1),
+ dir(edgecycle,i+1,sign=-1)) > 80) {
+ return null;
+ }
+ }
+
+ if (length(edgecycle) == 3) {
+ patch[] toreturn = usetriangles ? maketriangle(edgecycle, f, grad)
+ : triangletoquads(edgecycle, f, grad, a, b);
+ if (toreturn.length == 0) return null;
+ else return toreturn;
+ }
+ if (length(edgecycle) == 4) {
+ return new patch[] {patchwithnormals(edgecycle, grad)};
+ }
+
+ int[] bisectorindices;
+ path3 middleguide = bisector(edgecycle, bisectorindices);
+
+ triple testpoint = point(middleguide, 0.5);
+ if (!checkpt(testpoint, f, grad, a, b)) {
+ return null;
+ }
+
+ patch[] toreturn = null;
+ path3 firstpatch = subpath(edgecycle, bisectorindices[0], bisectorindices[1])
+ & reverse(middleguide) & cycle;
+ if (length(edgecycle) == 5) {
+ path3 secondpatch = middleguide
+ & subpath(edgecycle, bisectorindices[1], 5+bisectorindices[0]) & cycle;
+ toreturn = usetriangles ? maketriangle(secondpatch, f, grad)
+ : triangletoquads(secondpatch, f, grad, a, b);
+ if (toreturn.length == 0) return null;
+ toreturn.push(patchwithnormals(firstpatch, grad));
+ } else {
+ // now length(edgecycle) == 6
+ path3 secondpatch = middleguide
+ & subpath(edgecycle, bisectorindices[1], 6+bisectorindices[0])
+ & cycle;
+ toreturn = new patch[] {patchwithnormals(firstpatch, grad),
+ patchwithnormals(secondpatch, grad)};
+ }
+ return toreturn;
+}
+
+// Numerical gradient of a function
+typedef triple vectorfunction(triple);
+vectorfunction nGrad(real f(triple)) {
+ static real epsilon = 1e-3;
+ return new triple(triple v) {
+ return ( (f(v + epsilon*X) - f(v - epsilon*X)) / (2 epsilon),
+ (f(v + epsilon*Y) - f(v - epsilon*Y)) / (2 epsilon),
+ (f(v + epsilon*Z) - f(v - epsilon*Z)) / (2 epsilon) );
+ };
+}
+
+// A point together with a value at that location.
+struct evaluatedpoint {
+ triple pt;
+ real value;
+ void operator init(triple pt, real value) {
+ this.pt = pt;
+ this.value = value;
+ }
+}
+
+triple operator cast(evaluatedpoint p) { return p.pt; }
+
+// Compute the values of a function at every vertex of an nx by ny by nz
+// array of rectangular solids.
+evaluatedpoint[][][] make3dgrid(triple a, triple b, int nx, int ny, int nz,
+ real f(triple), bool allowzero = false)
+{
+ evaluatedpoint[][][] toreturn = new evaluatedpoint[nx+1][ny+1][nz+1];
+ for (int i = 0; i <= nx; ++i) {
+ for (int j = 0; j <= ny; ++j) {
+ for (int k = 0; k <= nz; ++k) {
+ triple pt = (interp(a.x, b.x, i/nx),
+ interp(a.y, b.y, j/ny),
+ interp(a.z, b.z, k/nz));
+ real value = f(pt);
+ if (value == 0 && !allowzero) value = 1e-5;
+ toreturn[i][j][k] = evaluatedpoint(pt, value);
+ }
+ }
+ }
+ return toreturn;
+}
+
+// The following utilities make, for instance, slice(A, i, j, k, l)
+// equivalent to what A[i:j][k:l] ought to mean for two- and three-
+// -dimensional arrays of evaluatedpoints and of positionedvectors.
+typedef evaluatedpoint T;
+T[][] slice(T[][] a, int start1, int end1, int start2, int end2) {
+ T[][] toreturn = new T[end1-start1][];
+ for (int i = start1; i < end1; ++i) {
+ toreturn[i-start1] = a[i][start2:end2];
+ }
+ return toreturn;
+}
+T[][][] slice(T[][][] a, int start1, int end1,
+ int start2, int end2,
+ int start3, int end3) {
+ T[][][] toreturn = new T[end1-start1][][];
+ for (int i = start1; i < end1; ++i) {
+ toreturn[i-start1] = slice(a[i], start2, end2, start3, end3);
+ }
+ return toreturn;
+}
+typedef positionedvector T;
+T[][] slice(T[][] a, int start1, int end1, int start2, int end2) {
+ T[][] toreturn = new T[end1-start1][];
+ for (int i = start1; i < end1; ++i) {
+ toreturn[i-start1] = a[i][start2:end2];
+ }
+ return toreturn;
+}
+T[][][] slice(T[][][] a, int start1, int end1,
+ int start2, int end2,
+ int start3, int end3) {
+ T[][][] toreturn = new T[end1-start1][][];
+ for (int i = start1; i < end1; ++i) {
+ toreturn[i-start1] = slice(a[i], start2, end2, start3, end3);
+ }
+ return toreturn;
+}
+
+// An object of class gridwithzeros stores the values of a function at each vertex
+// of a three-dimensional grid, together with zeros of the function along edges
+// of the grid and the gradient of the function at each such zero.
+struct gridwithzeros {
+ int nx, ny, nz;
+ evaluatedpoint[][][] corners;
+ positionedvector[][][] xdirzeros;
+ positionedvector[][][] ydirzeros;
+ positionedvector[][][] zdirzeros;
+ triple grad(triple);
+ real f(triple);
+ int maxdepth;
+ bool usetriangles;
+
+ // Populate the edges with zeros that have a sign change and are not already
+ // populated.
+ void fillzeros() {
+ for (int j = 0; j < ny+1; ++j) {
+ for (int k = 0; k < nz+1; ++k) {
+ real y = corners[0][j][k].pt.y;
+ real z = corners[0][j][k].pt.z;
+ real f_along_x(real t) { return f((t, y, z)); }
+ for (int i = 0; i < nx; ++i) {
+ if (xdirzeros[i][j][k] != null) continue;
+ evaluatedpoint start = corners[i][j][k];
+ evaluatedpoint end = corners[i+1][j][k];
+ if ((start.value > 0 && end.value > 0) || (start.value < 0 && end.value < 0))
+ xdirzeros[i][j][k] = null;
+ else {
+ triple root = (0,y,z);
+ root += X * findroot(f_along_x, start.pt.x, end.pt.x,
+ fa=start.value, fb=end.value);
+ triple normal = grad(root);
+ xdirzeros[i][j][k] = positionedvector(root, normal);
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < nx+1; ++i) {
+ for (int k = 0; k < nz+1; ++k) {
+ real x = corners[i][0][k].pt.x;
+ real z = corners[i][0][k].pt.z;
+ real f_along_y(real t) { return f((x, t, z)); }
+ for (int j = 0; j < ny; ++j) {
+ if (ydirzeros[i][j][k] != null) continue;
+ evaluatedpoint start = corners[i][j][k];
+ evaluatedpoint end = corners[i][j+1][k];
+ if ((start.value > 0 && end.value > 0) || (start.value < 0 && end.value < 0))
+ ydirzeros[i][j][k] = null;
+ else {
+ triple root = (x,0,z);
+ root += Y * findroot(f_along_y, start.pt.y, end.pt.y,
+ fa=start.value, fb=end.value);
+ triple normal = grad(root);
+ ydirzeros[i][j][k] = positionedvector(root, normal);
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < nx+1; ++i) {
+ for (int j = 0; j < ny+1; ++j) {
+ real x = corners[i][j][0].pt.x;
+ real y = corners[i][j][0].pt.y;
+ real f_along_z(real t) { return f((x, y, t)); }
+ for (int k = 0; k < nz; ++k) {
+ if (zdirzeros[i][j][k] != null) continue;
+ evaluatedpoint start = corners[i][j][k];
+ evaluatedpoint end = corners[i][j][k+1];
+ if ((start.value > 0 && end.value > 0) || (start.value < 0 && end.value < 0))
+ zdirzeros[i][j][k] = null;
+ else {
+ triple root = (x,y,0);
+ root += Z * findroot(f_along_z, start.pt.z, end.pt.z,
+ fa=start.value, fb=end.value);
+ triple normal = grad(root);
+ zdirzeros[i][j][k] = positionedvector(root, normal);
+ }
+ }
+ }
+ }
+ }
+
+ // Fill in the grid vertices and the zeros along edges. Each cube starts at
+ // depth one and the depth increases each time it subdivides; maxdepth is the
+ // maximum subdivision depth. When a cube at maxdepth cannot be resolved to
+ // patches, it is left empty.
+ void operator init(int nx, int ny, int nz,
+ real f(triple), triple a, triple b,
+ int maxdepth = 6, bool usetriangles) {
+ this.nx = nx;
+ this.ny = ny;
+ this.nz = nz;
+ grad = nGrad(f);
+ this.f = f;
+ this.maxdepth = maxdepth;
+ this.usetriangles = usetriangles;
+ corners = make3dgrid(a, b, nx, ny, nz, f);
+ xdirzeros = new positionedvector[nx][ny+1][nz+1];
+ ydirzeros = new positionedvector[nx+1][ny][nz+1];
+ zdirzeros = new positionedvector[nx+1][ny+1][nz];
+
+ for (int i = 0; i <= nx; ++i) {
+ for (int j = 0; j <= ny; ++j) {
+ for (int k = 0; k <= nz; ++k) {
+ if (i < nx) xdirzeros[i][j][k] = null;
+ if (j < ny) ydirzeros[i][j][k] = null;
+ if (k < nz) zdirzeros[i][j][k] = null;
+ }
+ }
+ }
+
+ fillzeros();
+ }
+
+ // Doubles nx, ny, and nz by halving the sizes of the cubes along the x, y, and z
+ // directions (resulting in 8 times as many cubes). Already existing data about
+ // function values and zeros is copied; vertices and edges with no such pre-existing
+ // data are populated.
+ //
+ // Returns true if subdivide succeeded, false if it failed (because maxdepth
+ // was exceeded).
+ bool subdivide() {
+ if (maxdepth <= 1) {
+ return false;
+ }
+ --maxdepth;
+ triple a = corners[0][0][0];
+ triple b = corners[nx][ny][nz];
+ nx *= 2;
+ ny *= 2;
+ nz *= 2;
+ evaluatedpoint[][][] oldcorners = corners;
+ corners = new evaluatedpoint[nx+1][ny+1][nz+1];
+ for (int i = 0; i <= nx; ++i) {
+ for (int j = 0; j <= ny; ++j) {
+ for (int k = 0; k <= nz; ++k) {
+ if (i % 2 == 0 && j % 2 == 0 && k % 2 == 0) {
+ corners[i][j][k] = oldcorners[quotient(i,2)][quotient(j,2)][quotient(k,2)];
+ } else {
+ triple pt = (interp(a.x, b.x, i/nx),
+ interp(a.y, b.y, j/ny),
+ interp(a.z, b.z, k/nz));
+ real value = f(pt);
+ if (value == 0) value = 1e-5;
+ corners[i][j][k] = evaluatedpoint(pt, value);
+ }
+ }
+ }
+ }
+
+ positionedvector[][][] oldxdir = xdirzeros;
+ xdirzeros = new positionedvector[nx][ny+1][nz+1];
+ for (int i = 0; i < nx; ++i) {
+ for (int j = 0; j < ny + 1; ++j) {
+ for (int k = 0; k < nz + 1; ++k) {
+ if (j % 2 != 0 || k % 2 != 0) {
+ xdirzeros[i][j][k] = null;
+ } else {
+ positionedvector zero = oldxdir[quotient(i,2)][quotient(j,2)][quotient(k,2)];
+ if (zero == null) {
+ xdirzeros[i][j][k] = null;
+ continue;
+ }
+ real x = zero.position.x;
+ if (x > interp(a.x, b.x, i/nx) && x < interp(a.x, b.x, (i+1)/nx)) {
+ xdirzeros[i][j][k] = zero;
+ } else {
+ xdirzeros[i][j][k] = null;
+ }
+ }
+ }
+ }
+ }
+
+ positionedvector[][][] oldydir = ydirzeros;
+ ydirzeros = new positionedvector[nx+1][ny][nz+1];
+ for (int i = 0; i < nx+1; ++i) {
+ for (int j = 0; j < ny; ++j) {
+ for (int k = 0; k < nz + 1; ++k) {
+ if (i % 2 != 0 || k % 2 != 0) {
+ ydirzeros[i][j][k] = null;
+ } else {
+ positionedvector zero = oldydir[quotient(i,2)][quotient(j,2)][quotient(k,2)];
+ if (zero == null) {
+ ydirzeros[i][j][k] = null;
+ continue;
+ }
+ real y = zero.position.y;
+ if (y > interp(a.y, b.y, j/ny) && y < interp(a.y, b.y, (j+1)/ny)) {
+ ydirzeros[i][j][k] = zero;
+ } else {
+ ydirzeros[i][j][k] = null;
+ }
+ }
+ }
+ }
+ }
+
+ positionedvector[][][] oldzdir = zdirzeros;
+ zdirzeros = new positionedvector[nx+1][ny+1][nz];
+ for (int i = 0; i < nx + 1; ++i) {
+ for (int j = 0; j < ny + 1; ++j) {
+ for (int k = 0; k < nz; ++k) {
+ if (i % 2 != 0 || j % 2 != 0) {
+ zdirzeros[i][j][k] = null;
+ } else {
+ positionedvector zero = oldzdir[quotient(i,2)][quotient(j,2)][quotient(k,2)];
+ if (zero == null) {
+ zdirzeros[i][j][k] = null;
+ continue;
+ }
+ real z = zero.position.z;
+ if (z > interp(a.z, b.z, k/nz) && z < interp(a.z, b.z, (k+1)/nz)) {
+ zdirzeros[i][j][k] = zero;
+ } else {
+ zdirzeros[i][j][k] = null;
+ }
+ }
+ }
+ }
+ }
+
+ fillzeros();
+ return true;
+ }
+
+ // Forward declaration of the draw method, which will be called by drawcube().
+ patch[] draw(bool[] reportactive = null);
+
+ // Construct the patches, assuming that we are working
+ // with a single cube (nx = ny = nz = 1). This method will subdivide the
+ // cube if necessary. The parameter reportactive should be an array of
+ // length 6. Setting an entry to true indicates that the surface abuts the
+ // corresponding face (according to the earlier enum), and thus that the
+ // algorithm should be sure that something is drawn in the cube sharing
+ // that face--even if all the vertices of that cube have the same sign.
+ patch[] drawcube(bool[] reportactive = null) {
+ // First, determine which edges (if any) actually have zeros on them.
+ edge[] zeroedges = new edge[0];
+ positionedvector[] zeros = new positionedvector[0];
+
+ int currentface, nextface;
+
+ void pushifnonnull(positionedvector v) {
+ if (v != null) {
+ zeroedges.push(edge(currentface, nextface));
+ zeros.push(v);
+ }
+ }
+ positionedvector findzero(int face1, int face2) {
+ edge e = edge(face1, face2);
+ for (int i = 0; i < zeroedges.length; ++i) {
+ if (zeroedges[i] == e) return zeros[i];
+ }
+ return null;
+ }
+
+ currentface = XLOW;
+ nextface = YHIGH;
+ pushifnonnull(zdirzeros[0][1][0]);
+ nextface = YLOW;
+ pushifnonnull(zdirzeros[0][0][0]);
+ nextface = ZHIGH;
+ pushifnonnull(ydirzeros[0][0][1]);
+ nextface = ZLOW;
+ pushifnonnull(ydirzeros[0][0][0]);
+
+ currentface = XHIGH;
+ nextface = YHIGH;
+ pushifnonnull(zdirzeros[1][1][0]);
+ nextface = YLOW;
+ pushifnonnull(zdirzeros[1][0][0]);
+ nextface = ZHIGH;
+ pushifnonnull(ydirzeros[1][0][1]);
+ nextface = ZLOW;
+ pushifnonnull(ydirzeros[1][0][0]);
+
+ currentface = YHIGH;
+ nextface = ZHIGH;
+ pushifnonnull(xdirzeros[0][1][1]);
+ currentface = ZHIGH;
+ nextface = YLOW;
+ pushifnonnull(xdirzeros[0][0][1]);
+ currentface = YLOW;
+ nextface = ZLOW;
+ pushifnonnull(xdirzeros[0][0][0]);
+ currentface = ZLOW;
+ nextface = YHIGH;
+ pushifnonnull(xdirzeros[0][1][0]);
+
+ //Now, string those edges together to make a circle.
+
+ patch[] subdividecube() {
+ if (!subdivide()) {
+ return new patch[0];
+ }
+ return draw(reportactive);
+ }
+ if (zeroedges.length < 3) {
+ return subdividecube();
+ }
+ int[] faceorder = makecircle(zeroedges);
+ if (alias(faceorder,null)) {
+ return subdividecube();
+ }
+ positionedvector[] patchcorners = new positionedvector[0];
+ for (int i = 0; i < faceorder.length; ++i) {
+ patchcorners.push(findzero(faceorder[i], faceorder[i+1]));
+ }
+ patchcorners.cyclic = true;
+
+ //Now, produce the cyclic path around the edges.
+ path3 edgecycle;
+ for (int i = 0; i < faceorder.length; ++i) {
+ path3 currentpath = pathinface(patchcorners[i], patchcorners[i+1],
+ faceorder[i+1], faceorder[i],
+ faceorder[i+2]);
+ triple testpoint = point(currentpath, 0.5);
+ if (!checkpt(testpoint, f, grad, corners[0][0][0], corners[1][1][1])) {
+ return subdividecube();
+ }
+
+ edgecycle = edgecycle & currentpath;
+ }
+ edgecycle = edgecycle & cycle;
+
+
+ { // Ensure the outward normals are pointing in the same direction as the gradient.
+ triple tangentin = patchcorners[0].position - precontrol(edgecycle, 0);
+ triple tangentout = postcontrol(edgecycle, 0) - patchcorners[0].position;
+ triple normal = cross(tangentin, tangentout);
+ if (dot(normal, patchcorners[0].direction) < 0) {
+ edgecycle = reverse(edgecycle);
+ patchcorners = patchcorners[-sequence(patchcorners.length)];
+ patchcorners.cyclic = true;
+ }
+ }
+
+ patch[] toreturn = quadpatches(edgecycle, patchcorners, f, grad,
+ corners[0][0][0], corners[1][1][1], usetriangles);
+ if (alias(toreturn, null)) return subdividecube();
+ return toreturn;
+ }
+
+ // Extracts the specified cube as a gridwithzeros object with
+ // nx = ny = nz = 1.
+ gridwithzeros getcube(int i, int j, int k) {
+ gridwithzeros cube = new gridwithzeros;
+ cube.grad = grad;
+ cube.f = f;
+ cube.nx = 1;
+ cube.ny = 1;
+ cube.nz = 1;
+ cube.maxdepth = maxdepth;
+ cube.usetriangles = usetriangles;
+ cube.corners = slice(corners,i,i+2,j,j+2,k,k+2);
+ cube.xdirzeros = slice(xdirzeros,i,i+1,j,j+2,k,k+2);
+ cube.ydirzeros = slice(ydirzeros,i,i+2,j,j+1,k,k+2);
+ cube.zdirzeros = slice(zdirzeros,i,i+2,j,j+2,k,k+1);
+ return cube;
+ }
+
+ // Returns an array of patches representing the surface.
+ // The parameter reportactive should be an array of
+ // length 6. Setting an entry to true indicates that the surface abuts the
+ // corresponding face of the cube that bounds the entire grid.
+ //
+ // If reportactive == null, it is assumed that this is a top-level call;
+ // a dot is printed to stdout for each cube drawn as a very rough
+ // progress indicator.
+ //
+ // If reportactive != null, then it is assumed that the caller had a strong
+ // reason to believe that this grid contains a part of the surface; the
+ // grid will subdivide all the way to maxdepth if necessary to find points
+ // on the surface.
+ draw = new patch[](bool[] reportactive = null) {
+ if (alias(reportactive, null)) progress(true);
+ // A list of all the patches not already drawn but known
+ // to contain part of the surface. This "queue" is
+ // actually implemented as stack for simplicity, since
+ // it does not make any difference. In a multi-threaded
+ // version of the algorithm, a queue (shared across all threads)
+ // would make more sense than a stack.
+ triple[] queue = new triple[0];
+ bool[][][] enqueued = new bool[nx][ny][nz];
+ for (int i = 0; i < enqueued.length; ++i) {
+ for (int j = 0; j < enqueued[i].length; ++j) {
+ for (int k = 0; k < enqueued[i][j].length; ++k) {
+ enqueued[i][j][k] = false;
+ }
+ }
+ }
+
+ void enqueue(int i, int j, int k) {
+ if (i >= 0 && i < nx
+ && j >= 0 && j < ny
+ && k >= 0 && k < nz
+ && !enqueued[i][j][k]) {
+ queue.push((i,j,k));
+ enqueued[i][j][k] = true;
+ }
+ if (!alias(reportactive, null)) {
+ if (i < 0) reportactive[XLOW] = true;
+ if (i >= nx) reportactive[XHIGH] = true;
+ if (j < 0) reportactive[YLOW] = true;
+ if (j >= ny) reportactive[YHIGH] = true;
+ if (k < 0) reportactive[ZLOW] = true;
+ if (k >= nz) reportactive[ZHIGH] = true;
+ }
+ }
+
+ for (int i = 0; i < nx+1; ++i) {
+ for (int j = 0; j < ny+1; ++j) {
+ for (int k = 0; k < nz+1; ++k) {
+ if (i < nx && xdirzeros[i][j][k] != null) {
+ for (int jj = j-1; jj <= j; ++jj)
+ for (int kk = k-1; kk <= k; ++kk)
+ enqueue(i, jj, kk);
+ }
+ if (j < ny && ydirzeros[i][j][k] != null) {
+ for (int ii = i-1; ii <= i; ++ii)
+ for (int kk = k-1; kk <= k; ++kk)
+ enqueue(ii, j, kk);
+ }
+ if (k < nz && zdirzeros[i][j][k] != null) {
+ for (int ii = i-1; ii <= i; ++ii)
+ for (int jj = j-1; jj <= j; ++jj)
+ enqueue(ii, jj, k);
+ }
+ }
+ }
+ }
+
+ if (!alias(reportactive, null) && queue.length == 0) {
+ if (subdivide()) return draw(reportactive);
+ }
+
+ patch[] surface = new patch[0];
+
+ while (queue.length > 0) {
+ triple coord = queue.pop();
+ int i = floor(coord.x);
+ int j = floor(coord.y);
+ int k = floor(coord.z);
+ bool[] reportface = array(6, false);
+ patch[] toappend = getcube(i,j,k).drawcube(reportface);
+ if (reportface[XLOW]) enqueue(i-1,j,k);
+ if (reportface[XHIGH]) enqueue(i+1,j,k);
+ if (reportface[YLOW]) enqueue(i,j-1,k);
+ if (reportface[YHIGH]) enqueue(i,j+1,k);
+ if (reportface[ZLOW]) enqueue(i,j,k-1);
+ if (reportface[ZHIGH]) enqueue(i,j,k+1);
+ surface.append(toappend);
+ if (alias(reportactive, null)) progress();
+ }
+ if (alias(reportactive, null)) progress(false);
+ return surface;
+ };
+}
+
+// The external interface of this whole module. Accepts exactly one
+// function (throws an error if two or zero functions are specified).
+// The function should be differentiable. (Whatever you do, do not
+// pass in an indicator function!) Ideally, the zero locus of the
+// function should be smooth; singularities will significantly slow
+// down the algorithm and potentially give bad results.
+//
+// Returns a plot of the zero locus of the function within the
+// rectangular solid with opposite corners at a and b.
+//
+// Additional parameters:
+// n - the number of initial segments in each of the x, y, z directions.
+// overlapedges - if true, the patches of the surface are slightly enlarged
+// to compensate for an artifact in which the viewer can see through the
+// boundary between patches. (Some of this may actually be a result of
+// edges not lining up perfectly, but I'm fairly sure a lot of it arises
+// purely as a rendering artifact.)
+// nx - override n in the x direction
+// ny - override n in the y direction
+// nz - override n in the z direction
+// maxdepth - the maximum depth to which the algorithm will subdivide in
+// an effort to find patches that closely approximate the true surface.
+surface implicitsurface(real f(triple) = null, real ff(real,real,real) = null,
+ triple a, triple b,
+ int n = nmesh,
+ bool keyword overlapedges = false,
+ int keyword nx=n, int keyword ny=n,
+ int keyword nz=n,
+ int keyword maxdepth = 8,
+ bool keyword usetriangles=true) {
+ if (f == null && ff == null)
+ abort("implicitsurface called without specifying a function.");
+ if (f != null && ff != null)
+ abort("Only specify one function when calling implicitsurface.");
+ if (f == null) f = new real(triple w) { return ff(w.x, w.y, w.z); };
+ gridwithzeros grid = gridwithzeros(nx, ny, nz, f, a, b, maxdepth=maxdepth,
+ usetriangles=usetriangles);
+ patch[] patches = grid.draw();
+ if (overlapedges) {
+ for (int i = 0; i < patches.length; ++i) {
+ triple center = (patches[i].triangular ?
+ patches[i].point(1/3, 1/3) : patches[i].point(1/2,1/2));
+ transform3 T=shift(center) * scale3(1.03) * shift(-center);
+ patches[i] = T * patches[i];
+ }
+ }
+ return surface(...patches);
+}
diff --git a/graphics/asymptote/base/solids.asy b/graphics/asymptote/base/solids.asy
new file mode 100644
index 0000000000..942a7fe233
--- /dev/null
+++ b/graphics/asymptote/base/solids.asy
@@ -0,0 +1,412 @@
+import graph3;
+
+pen defaultbackpen=linetype(new real[] {4,4},4,scale=false);
+
+// A solid geometry package.
+
+// Try to find a bounding tangent line between two paths.
+real[] tangent(path p, path q, bool side)
+{
+ static real fuzz=1.0e-5;
+
+ if((cyclic(p) && inside(p,point(q,0)) ||
+ cyclic(q) && inside(q,point(p,0))) &&
+ intersect(p,q,fuzz).length == 0) return new real[];
+
+ for(int i=0; i < 100; ++i) {
+ real ta=side ? mintimes(p)[1] : maxtimes(p)[1];
+ real tb=side ? mintimes(q)[1] : maxtimes(q)[1];
+ pair a=point(p,ta);
+ pair b=point(q,tb);
+ real angle=angle(b-a,warn=false);
+ if(abs(angle) <= sqrtEpsilon || abs(abs(0.5*angle)-pi) <= sqrtEpsilon)
+ return new real[] {ta,tb};
+ transform t=rotate(-degrees(angle));
+ p=t*p;
+ q=t*q;
+ }
+ return new real[];
+}
+
+path line(path p, path q, real[] t)
+{
+ return point(p,t[0])--point(q,t[1]);
+}
+
+// Return the projection of a generalized cylinder of height h constructed
+// from area base in the XY plane and aligned with axis.
+path[] cylinder(path3 base, real h, triple axis=Z, projection P)
+{
+ base=rotate(-colatitude(axis),cross(axis,Z))*base;
+ path3 top=shift(h*axis)*base;
+ path Base=project(base,P);
+ path Top=project(top,P);
+ real[] t1=tangent(Base,Top,true);
+ real[] t2=tangent(Base,Top,false);
+ path p=subpath(Base,t1[0]/P.ninterpolate,t2[0]/P.ninterpolate);
+ path q=subpath(Base,t2[0]/P.ninterpolate,t1[0]/P.ninterpolate);
+ return Base^^Top^^line(Base,Top,t1)^^line(Base,Top,t2);
+}
+
+// The three-dimensional "wireframe" used to visualize a volume of revolution
+struct skeleton {
+ struct curve {
+ path3[] front;
+ path3[] back;
+ }
+ // transverse skeleton (perpendicular to axis of revolution)
+ curve transverse;
+ // longitudinal skeleton (parallel to axis of revolution)
+ curve longitudinal;
+}
+
+// A surface of revolution generated by rotating a planar path3 g
+// from angle1 to angle2 about c--c+axis.
+struct revolution {
+ triple c;
+ path3 g;
+ triple axis;
+ real angle1,angle2;
+ triple M;
+ triple m;
+
+ static real epsilon=10*sqrtEpsilon;
+
+ void operator init(triple c=O, path3 g, triple axis=Z, real angle1=0,
+ real angle2=360) {
+ this.c=c;
+ this.g=g;
+ this.axis=unit(axis);
+ this.angle1=angle1;
+ this.angle2=angle2;
+ M=max(g);
+ m=min(g);
+ }
+
+
+ revolution copy() {
+ return revolution(c,g,axis,angle1,angle2);
+ }
+
+ triple vertex(int i, real j) {
+ triple v=point(g,i);
+ triple center=c+dot(v-c,axis)*axis;
+ triple perp=v-center;
+ triple normal=cross(axis,perp);
+ return center+Cos(j)*perp+Sin(j)*normal;
+ }
+
+ // Construct the surface of rotation generated by rotating g
+ // from angle1 to angle2 sampled n times about the line c--c+axis.
+ // An optional surface pen color(int i, real j) may be specified
+ // to override the color at vertex(i,j).
+ surface surface(int n=nslice, pen color(int i, real j)=null) {
+ return surface(c,g,axis,n,angle1,angle2,color);
+ }
+
+ path3 slice(real position, int n=nCircle) {
+ triple v=point(g,position);
+ triple center=c+dot(v-c,axis)*axis;
+ triple perp=v-center;
+ if(abs(perp) <= epsilon*max(abs(m),abs(M))) return center;
+ triple v1=center+rotate(angle1,axis)*perp;
+ triple v2=center+rotate(angle2,axis)*perp;
+ path3 p=Arc(center,v1,v2,axis,n);
+ return (angle2-angle1) % 360 == 0 ? p&cycle : p;
+ }
+
+ triple camera(projection P) {
+ triple camera=P.camera;
+ if(P.infinity) {
+ real s=abs(M-m)+abs(m-P.target);
+ camera=P.target+camerafactor*s*unit(P.vector());
+ }
+ return camera;
+ }
+
+ // add transverse slice to skeleton s;
+ void transverse(skeleton s, real t, int n=nslice, projection P) {
+ skeleton.curve s=s.transverse;
+ path3 S=slice(t,n);
+ triple camera=camera(P);
+ int L=length(g);
+ real midtime=0.5*L;
+ real sign=sgn(dot(axis,camera-P.target))*sgn(dot(axis,dir(g,midtime)));
+ if(dot(M-m,axis) == 0 || (t <= epsilon && sign < 0) ||
+ (t >= L-epsilon && sign > 0))
+ s.front.push(S);
+ else {
+ path3 Sp=slice(t+epsilon,n);
+ path3 Sm=slice(t-epsilon,n);
+ path sp=project(Sp,P);
+ path sm=project(Sm,P);
+ real[] t1=tangent(sp,sm,true);
+ real[] t2=tangent(sp,sm,false);
+ if(t1.length > 1 && t2.length > 1) {
+ real t1=t1[0]/P.ninterpolate;
+ real t2=t2[0]/P.ninterpolate;
+ int len=length(S);
+ if(t2 < t1) {
+ real temp=t1;
+ t1=t2;
+ t2=temp;
+ }
+ path3 p1=subpath(S,t1,t2);
+ path3 p2=subpath(S,t2,len);
+ path3 P2=subpath(S,0,t1);
+ if(abs(midpoint(p1)-camera) <= abs(midpoint(p2)-camera)) {
+ s.front.push(p1);
+ if(cyclic(S))
+ s.back.push(p2 & P2);
+ else {
+ s.back.push(p2);
+ s.back.push(P2);
+ }
+ } else {
+ if(cyclic(S))
+ s.front.push(p2 & P2);
+ else {
+ s.front.push(p2);
+ s.front.push(P2);
+ }
+ s.back.push(p1);
+ }
+ } else {
+ if((t <= midtime && sign < 0) || (t >= midtime && sign > 0))
+ s.front.push(S);
+ else
+ s.back.push(S);
+ }
+ }
+ }
+
+ // add m evenly spaced transverse slices to skeleton s
+ void transverse(skeleton s, int m=0, int n=nslice, projection P) {
+ if(m == 0) {
+ int N=size(g);
+ for(int i=0; i < N; ++i)
+ transverse(s,(real) i,n,P);
+ } else if(m == 1)
+ transverse(s,reltime(g,0.5),n,P);
+ else {
+ real factor=1/(m-1);
+ for(int i=0; i < m; ++i)
+ transverse(s,reltime(g,i*factor),n,P);
+ }
+ }
+
+ // return approximate silhouette based on m evenly spaced transverse slices;
+ // must be recomputed if camera is adjusted
+ path3[] silhouette(int m=64, projection P=currentprojection) {
+ if(is3D())
+ warning("2Dsilhouette",
+ "silhouette routine is intended only for 2d projections");
+ path3 G,H;
+ int N=size(g);
+ int M=(m == 0) ? N : m;
+ real factor=m == 1 ? 0 : 1/(m-1);
+ int n=nslice;
+
+ real tfirst=-1;
+ real tlast;
+ for(int i=0; i < M; ++i) {
+ real t=(m == 0) ? i : reltime(g,i*factor);
+ path3 S=slice(t,n);
+ triple camera=camera(P);
+ path3 Sp=slice(t+epsilon,n);
+ path3 Sm=slice(t-epsilon,n);
+ path sp=project(Sp,P);
+ path sm=project(Sm,P);
+ real[] t1=tangent(sp,sm,true);
+ real[] t2=tangent(sp,sm,false);
+ if(t1.length > 1 && t2.length > 1) {
+ real t1=t1[0]/P.ninterpolate;
+ real t2=t2[0]/P.ninterpolate;
+ if(t1 != t2) {
+ G=G..point(S,t1);
+ H=point(S,t2)..H;
+ if(tfirst < 0) tfirst=t;
+ tlast=t;
+ }
+ }
+ }
+ int L=length(g);
+ real midtime=0.5*L;
+ triple camera=camera(P);
+ real sign=sgn(dot(axis,camera-P.target))*sgn(dot(axis,dir(g,midtime)));
+
+ skeleton sfirst;
+ transverse(sfirst,tfirst,n,P);
+ triple delta=this.M-this.m;
+ path3 cap;
+ if(dot(delta,axis) == 0 || (tfirst <= epsilon && sign < 0)) {
+ cap=sfirst.transverse.front[0];
+ } else {
+ if(sign > 0) {
+ if(sfirst.transverse.front.length > 0)
+ G=reverse(sfirst.transverse.front[0])..G;
+ } else {
+ if(sfirst.transverse.back.length > 0)
+ G=sfirst.transverse.back[0]..G;
+ }
+ }
+
+ skeleton slast;
+ transverse(slast,tlast,n,P);
+ if(dot(delta,axis) == 0 || (tlast >= L-epsilon && sign > 0)) {
+ cap=slast.transverse.front[0];
+ } else {
+ if(sign > 0) {
+ if(slast.transverse.back.length > 0)
+ H=reverse(slast.transverse.back[0])..H;
+ } else {
+ if(slast.transverse.front.length > 0)
+ H=slast.transverse.front[0]..H;
+ }
+ }
+
+ return size(cap) == 0 ? G^^H : G^^H^^cap;
+ }
+
+ // add longitudinal curves to skeleton;
+ void longitudinal(skeleton s, int n=nslice, projection P) {
+ real t, d=0;
+ // Find a point on g of maximal distance from the axis.
+ int N=size(g);
+ for(int i=0; i < N; ++i) {
+ triple v=point(g,i);
+ triple center=c+dot(v-c,axis)*axis;
+ real r=abs(v-center);
+ if(r > d) {
+ t=i;
+ d=r;
+ }
+ }
+ path3 S=slice(t,n);
+ path3 Sm=slice(t+epsilon,n);
+ path3 Sp=slice(t-epsilon,n);
+ path sp=project(Sp,P);
+ path sm=project(Sm,P);
+ real[] t1=tangent(sp,sm,true);
+ real[] t2=tangent(sp,sm,false);
+ transform3 T=transpose(align(axis));
+ real Longitude(triple v) {return longitude(T*(v-c),warn=false);}
+ real ref=Longitude(point(g,t));
+ real angle(real t) {return Longitude(point(S,t/P.ninterpolate))-ref;}
+ triple camera=camera(P);
+ void push(real[] T) {
+ if(T.length > 1) {
+ path3 p=rotate(angle(T[0]),c,c+axis)*g;
+ path3 p1=subpath(p,0,t);
+ path3 p2=subpath(p,t,length(p));
+ if(length(p1) > 0 &&
+ (length(p2) == 0 ||
+ abs(midpoint(p1)-camera) <= abs(midpoint(p2)-camera))) {
+ s.longitudinal.front.push(p1);
+ s.longitudinal.back.push(p2);
+ } else {
+ s.longitudinal.back.push(p1);
+ s.longitudinal.front.push(p2);
+ }
+ }
+ }
+ push(t1);
+ push(t2);
+ }
+
+ skeleton skeleton(int m=0, int n=nslice, projection P) {
+ skeleton s;
+ transverse(s,m,n,P);
+ longitudinal(s,n,P);
+ return s;
+ }
+}
+
+revolution operator * (transform3 t, revolution r)
+{
+ triple trc=t*r.c;
+ return revolution(trc,t*r.g,t*(r.c+r.axis)-trc,r.angle1,r.angle2);
+}
+
+surface surface(revolution r, int n=nslice, pen color(int i, real j)=null)
+{
+ return r.surface(n,color);
+}
+
+// Draw on picture pic the skeleton of the surface of revolution r.
+// Draw the front portion of each of the m transverse slices with pen p and
+// the back portion with pen backpen. Rotational arcs are based on
+// n-point approximations to the unit circle.
+void draw(picture pic=currentpicture, revolution r, int m=0, int n=nslice,
+ pen frontpen=currentpen, pen backpen=frontpen,
+ pen longitudinalpen=frontpen, pen longitudinalbackpen=backpen,
+ light light=currentlight, string name="",
+ render render=defaultrender, projection P=currentprojection)
+{
+ if(is3D()) {
+ pen thin=thin();
+ void drawskeleton(frame f, transform3 t, projection P) {
+ skeleton s=(t*r).skeleton(m,n,P);
+ if(frontpen != nullpen) {
+ draw(f,s.transverse.back,thin+defaultbackpen+backpen,light);
+ draw(f,s.transverse.front,thin+frontpen,light);
+ }
+ if(longitudinalpen != nullpen) {
+ draw(f,s.longitudinal.back,thin+defaultbackpen+longitudinalbackpen,
+ light);
+ draw(f,s.longitudinal.front,thin+longitudinalpen,light);
+ }
+ }
+
+ bool group=name != "" || render.defaultnames;
+ if(group)
+ begingroup3(pic,name == "" ? "skeleton" : name,render);
+ pic.add(new void(frame f, transform3 t, picture pic, projection P) {
+ drawskeleton(f,t,P);
+ if(pic != null)
+ pic.addBox(min(f,P),max(f,P),min(frontpen),max(frontpen));
+ });
+ frame f;
+ drawskeleton(f,identity4,P);
+ pic.addBox(min3(f),max3(f));
+ if(group)
+ endgroup3(pic);
+ } else {
+ skeleton s=r.skeleton(m,n,P);
+ if(frontpen != nullpen) {
+ draw(pic,s.transverse.back,defaultbackpen+backpen,light);
+ draw(pic,s.transverse.front,frontpen,light);
+ }
+ if(longitudinalpen != nullpen) {
+ draw(pic,s.longitudinal.back,defaultbackpen+longitudinalbackpen,
+ light);
+ draw(pic,s.longitudinal.front,longitudinalpen,light);
+ }
+ }
+}
+
+// Return a right circular cylinder of height h in the direction of axis
+// based on a circle centered at c with radius r.
+revolution cylinder(triple c=O, real r, real h, triple axis=Z)
+{
+ triple C=c+r*perp(axis);
+ axis=h*unit(axis);
+ return revolution(c,C--C+axis,axis);
+}
+
+// Return a right circular cone of height h in the direction of axis
+// based on a circle centered at c with radius r. The parameter n
+// controls the accuracy near the degenerate point at the apex.
+revolution cone(triple c=O, real r, real h, triple axis=Z, int n=nslice)
+{
+ axis=unit(axis);
+ return revolution(c,approach(c+r*perp(axis)--c+h*axis,n),axis);
+}
+
+// Return an approximate sphere of radius r centered at c obtained by rotating
+// an (n+1)-point approximation to a half circle about the Z axis.
+// Note: unitsphere provides a smoother and more efficient surface.
+revolution sphere(triple c=O, real r, int n=nslice)
+{
+ return revolution(c,Arc(c,r,180-sqrtEpsilon,0,sqrtEpsilon,0,Y,n),Z);
+}
diff --git a/graphics/asymptote/base/stats.asy b/graphics/asymptote/base/stats.asy
new file mode 100644
index 0000000000..be9efdfa42
--- /dev/null
+++ b/graphics/asymptote/base/stats.asy
@@ -0,0 +1,292 @@
+private import graph;
+
+real legendmarkersize=2mm;
+
+real mean(real A[])
+{
+ return sum(A)/A.length;
+}
+
+// unbiased estimate
+real variance(real A[])
+{
+ return sum((A-mean(A))^2)/(A.length-1);
+}
+
+real variancebiased(real A[])
+{
+ return sum((A-mean(A))^2)/A.length;
+}
+
+// unbiased estimate
+real stdev(real A[])
+{
+ return sqrt(variance(A));
+}
+
+real rms(real A[])
+{
+ return sqrt(sum(A^2)/A.length);
+}
+
+real skewness(real A[])
+{
+ real[] diff=A-mean(A);
+ return sum(diff^3)/sqrt(sum(diff^2)^3/A.length);
+}
+
+real kurtosis(real A[])
+{
+ real[] diff=A-mean(A);
+ return sum(diff^4)/sum(diff^2)^2*A.length;
+}
+
+real kurtosisexcess(real A[])
+{
+ return kurtosis(A)-3;
+}
+
+real Gaussian(real x, real sigma)
+{
+ static real sqrt2pi=sqrt(2pi);
+ return exp(-0.5*(x/sigma)^2)/(sigma*sqrt2pi);
+}
+
+real Gaussian(real x)
+{
+ static real invsqrt2pi=1/sqrt(2pi);
+ return exp(-0.5*x^2)*invsqrt2pi;
+}
+
+// Return frequency count of data in [bins[i],bins[i+1]) for i=0,...,n-1.
+int[] frequency(real[] data, real[] bins)
+{
+ int n=bins.length-1;
+ int[] freq=new int[n];
+ for(int i=0; i < n; ++i)
+ freq[i]=sum(bins[i] <= data & data < bins[i+1]);
+ return freq;
+}
+
+// Return frequency count in n uniform bins from a to b
+// (faster than the above more general algorithm).
+int[] frequency(real[] data, real a, real b, int n)
+{
+ int[] freq=sequence(new int(int x) {return 0;},n);
+ real h=n/(b-a);
+ for(int i=0; i < data.length; ++i) {
+ int I=Floor((data[i]-a)*h);
+ if(I >= 0 && I < n)
+ ++freq[I];
+ }
+ return freq;
+}
+
+// Return frequency count in [xbins[i],xbins[i+1]) and [ybins[j],ybins[j+1]).
+int[][] frequency(real[] x, real[] y, real[] xbins, real[] ybins)
+{
+ int n=xbins.length-1;
+ int m=ybins.length-1;
+ int[][] freq=new int[n][m];
+ bool[][] inybin=new bool[m][y.length];
+ for(int j=0; j < m; ++j)
+ inybin[j]=ybins[j] <= y & y < ybins[j+1];
+ for(int i=0; i < n; ++i) {
+ bool[] inxbini=xbins[i] <= x & x < xbins[i+1];
+ int[] freqi=freq[i];
+ for(int j=0; j < m; ++j)
+ freqi[j]=sum(inxbini & inybin[j]);
+ }
+ return freq;
+}
+
+// Return frequency count in nx by ny uniform bins in box(a,b).
+int[][] frequency(real[] x, real[] y, pair a, pair b, int nx, int ny=nx)
+{
+ int[][] freq=new int[nx][];
+ for(int i=0; i < nx; ++i)
+ freq[i]=sequence(new int(int x) {return 0;},ny);
+ real hx=nx/(b.x-a.x);
+ real hy=ny/(b.y-a.y);
+ real ax=a.x;
+ real ay=a.y;
+ for(int i=0; i < x.length; ++i) {
+ int I=Floor((x[i]-ax)*hx);
+ int J=Floor((y[i]-ay)*hy);
+ if(I >= 0 && I <= nx && J >= 0 && J <= ny)
+ ++freq[I][J];
+ }
+ return freq;
+}
+
+int[][] frequency(pair[] z, pair a, pair b, int nx, int ny=nx)
+{
+ int[][] freq=new int[nx][];
+ for(int i=0; i < nx; ++i)
+ freq[i]=sequence(new int(int x) {return 0;},ny);
+ real hx=nx/(b.x-a.x);
+ real hy=ny/(b.y-a.y);
+ real ax=a.x;
+ real ay=a.y;
+ for(int i=0; i < z.length; ++i) {
+ int I=Floor((z[i].x-ax)*hx);
+ int J=Floor((z[i].y-ay)*hy);
+ if(I >= 0 && I < nx && J >= 0 && J < ny)
+ ++freq[I][J];
+ }
+ return freq;
+}
+
+path halfbox(pair a, pair b)
+{
+ return a--(a.x,b.y)--b;
+}
+
+path topbox(pair a, pair b)
+{
+ return a--(a.x,b.y)--b--(b.x,a.y);
+}
+
+// Draw a histogram for bin boundaries bin[n+1] of frequency data in count[n].
+void histogram(picture pic=currentpicture, real[] bins, real[] count,
+ real low=-infinity,
+ pen fillpen=nullpen, pen drawpen=nullpen, bool bars=false,
+ Label legend="", real markersize=legendmarkersize)
+{
+ if((fillpen == nullpen || bars == true) && drawpen == nullpen)
+ drawpen=currentpen;
+ bool[] valid=count > 0;
+ real m=min(valid ? count : null);
+ real M=max(valid ? count : null);
+ bounds my=autoscale(pic.scale.y.scale.T(m),pic.scale.y.T(M),
+ pic.scale.y.scale);
+ if(low == -infinity) low=pic.scale.y.scale.Tinv(my.min);
+ real last=low;
+ int n=count.length;
+ begingroup(pic);
+ for(int i=0; i < n; ++i) {
+ if(valid[i]) {
+ real c=count[i];
+ pair b=Scale(pic,(bins[i+1],c));
+ pair a=Scale(pic,(bins[i],low));
+ if(fillpen != nullpen) {
+ fill(pic,box(a,b),fillpen);
+ if(!bars) draw(pic,b--(b.x,a.y),fillpen);
+ }
+ if(!bars)
+ draw(pic,halfbox(Scale(pic,(bins[i],last)),b),drawpen);
+ else draw(pic,topbox(a,b),drawpen);
+ last=c;
+ } else {
+ if(!bars && last != low) {
+ draw(pic,Scale(pic,(bins[i],last))--Scale(pic,(bins[i],low)),drawpen);
+ last=low;
+ }
+ }
+ }
+ if(!bars && last != low)
+ draw(pic,Scale(pic,(bins[n],last))--Scale(pic,(bins[n],low)),drawpen);
+ endgroup(pic);
+
+ if(legend.s != "") {
+ marker m=marker(scale(markersize)*shift((-0.5,-0.5))*unitsquare,
+ drawpen,fillpen == nullpen ? Draw :
+ (drawpen == nullpen ? Fill(fillpen) : FillDraw(fillpen)));
+ legend.p(drawpen);
+ pic.legend.push(Legend(legend.s,legend.p,invisible,m.f));
+ }
+}
+
+// Draw a histogram for data in n uniform bins between a and b
+// (optionally normalized).
+void histogram(picture pic=currentpicture, real[] data, real a, real b, int n,
+ bool normalize=false, real low=-infinity,
+ pen fillpen=nullpen, pen drawpen=nullpen, bool bars=false,
+ Label legend="", real markersize=legendmarkersize)
+{
+ real dx=(b-a)/n;
+ real[] freq=frequency(data,a,b,n);
+ if(normalize) freq /= dx*sum(freq);
+ histogram(pic,a+sequence(n+1)*dx,freq,low,fillpen,drawpen,bars,legend,
+ markersize);
+}
+
+// Method of Shimazaki and Shinomoto for selecting the optimal number of bins.
+// Shimazaki H. and Shinomoto S., A method for selecting the bin size of a
+// time histogram, Neural Computation (2007), Vol. 19(6), 1503-1527.
+// cf. http://www.ton.scphys.kyoto-u.ac.jp/~hideaki/res/histogram.html
+int bins(real[] data, int max=100)
+{
+ real m=min(data);
+ real M=max(data)*(1+epsilon);
+ real n=data.length;
+ int bins=1;
+ real minC=2n-n^2; // Cost function for N=1.
+ for(int N=2; N <= max; ++N) {
+ real C=N*(2n-sum(frequency(data,m,M,N)^2));
+ if(C < minC) {
+ minC=C;
+ bins=N;
+ }
+ }
+
+ return bins;
+}
+
+// return a pair of central Gaussian random numbers with unit variance
+pair Gaussrandpair()
+{
+ real r2,v1,v2;
+ do {
+ v1=2.0*unitrand()-1.0;
+ v2=2.0*unitrand()-1.0;
+ r2=v1*v1+v2*v2;
+ } while(r2 >= 1.0 || r2 == 0.0);
+ return (v1,v2)*sqrt(-log(r2)/r2);
+}
+
+// return a central Gaussian random number with unit variance
+real Gaussrand()
+{
+ static real sqrt2=sqrt(2.0);
+ static pair z;
+ static bool cached=true;
+ cached=!cached;
+ if(cached) return sqrt2*z.y;
+ z=Gaussrandpair();
+ return sqrt2*z.x;
+}
+
+struct linefit {
+ real m,b; // slope, intercept
+ real dm,db; // standard error in slope, intercept
+ real r; // correlation coefficient
+ real fit(real x) {
+ return m*x+b;
+ }
+}
+
+// Do a least-squares fit of data in real arrays x and y to the line y=m*x+b
+linefit leastsquares(real[] x, real[] y)
+{
+ linefit L;
+ int n=x.length;
+ if(n == 1) abort("Least squares fit requires at least 2 data points");
+ real sx=sum(x);
+ real sy=sum(y);
+ real sxx=n*sum(x^2)-sx^2;
+ real sxy=n*sum(x*y)-sx*sy;
+ L.m=sxy/sxx;
+ L.b=(sy-L.m*sx)/n;
+ if(n > 2) {
+ real syy=n*sum(y^2)-sy^2;
+ if(sxx == 0 || syy == 0) return L;
+ L.r=sxy/sqrt(sxx*syy);
+ real arg=syy-sxy^2/sxx;
+ if(arg <= 0) return L;
+ real s=sqrt(arg/(n-2));
+ L.dm=s*sqrt(1/sxx);
+ L.db=s*sqrt(1+sx^2/sxx)/n;
+ }
+ return L;
+}
diff --git a/graphics/asymptote/base/syzygy.asy b/graphics/asymptote/base/syzygy.asy
new file mode 100644
index 0000000000..93889b252e
--- /dev/null
+++ b/graphics/asymptote/base/syzygy.asy
@@ -0,0 +1,926 @@
+/***** syzygy.asy {{{1
+ * Andy Hammerlindl 2006/12/02
+ *
+ * Automates the drawing of braids, relations, and syzygies, along with the
+ * corresponding equations.
+ *
+ * See
+ * http://katlas.math.toronto.edu/drorbn/index.php?title=06-1350/Syzygies_in_Asymptote
+ * For more information.
+ *****/
+struct Component { // {{{1
+ // The number of strings coming in or out of the component.
+ int in;
+ int out;
+
+ // Which 'out' string each 'in' string is connected to. For deriving
+ // equations.
+ int[] connections;
+
+ string symbol; // For pullback notation.
+ string lsym; // For linear equations.
+ string codename; // For Mathematica code.
+
+ guide[] draw(picture pic, guide[] ins);
+}
+
+// Utility functions {{{1
+pair[] endpoints(guide[] a) {
+ pair[] z;
+ for (int i=0; i<a.length; ++i)
+ z.push(endpoint(a[i]));
+ return z;
+}
+
+pair min(pair[] z) {
+ pair m=(infinity, infinity);
+ for (int i=0; i<z.length; ++i) {
+ if (z[i].x < m.x)
+ m=(z[i].x,m.y);
+ if (z[i].y < m.y)
+ m=(m.x,z[i].y);
+ }
+ return m;
+}
+
+pair max(pair[] z) {
+ pair M=(-infinity, -infinity);
+ for (int i=0; i<z.length; ++i) {
+ if (z[i].x > M.x)
+ M=(z[i].x,M.y);
+ if (z[i].y > M.y)
+ M=(M.x,z[i].y);
+ }
+ return M;
+}
+
+// Component Definitions {{{1
+real hwratio=1.4;
+real gapfactor=6;
+
+Component bp=new Component;
+bp.in=2; bp.out=2;
+bp.connections=new int[] {1,0};
+bp.symbol="B^+"; bp.lsym="b^+"; bp.codename="bp";
+bp.draw=new guide[] (picture pic, guide[] ins) {
+ pair[] z=endpoints(ins);
+ pair m=min(z), M=max(z);
+ real w=M.x-m.x, h=hwratio*w;
+ pair centre=(0.5(m.x+M.x),M.y+h/2);
+
+ /*
+ return new guide[] {ins[1]..centre{NW}..z[0]+h*N,
+ ins[0]..centre{NE}..z[1]+h*N};
+ */
+
+ real offset=gapfactor*linewidth(currentpen);
+ draw(pic, ins[1]..(centre-offset*NW){NW});
+ return new guide[] {(centre+offset*NW){NW}..z[0]+h*N,
+ ins[0]..centre{NE}..z[1]+h*N};
+};
+
+Component bm=new Component;
+bm.in=2; bm.out=2;
+bm.connections=new int[] {1,0};
+bm.symbol="B^-"; bm.lsym="b^-"; bm.codename="bm";
+bm.draw=new guide[] (picture pic, guide[] ins) {
+ pair[] z=endpoints(ins);
+ pair m=min(z), M=max(z);
+ real w=M.x-m.x, h=hwratio*w;
+ pair centre=(0.5(m.x+M.x),M.y+h/2);
+
+ /*
+ return new guide[] {ins[1]..centre{NW}..z[0]+h*N,
+ ins[0]..centre{NE}..z[1]+h*N};
+ */
+
+ real offset=gapfactor*linewidth(currentpen);
+ draw(pic, ins[0]..(centre-offset*NE){NE});
+ return new guide[] {ins[1]..centre{NW}..z[0]+h*N,
+ (centre+offset*NE){NE}..z[1]+h*N};
+};
+
+Component phi=new Component;
+phi.in=2; phi.out=1;
+phi.connections=new int[] {0,0};
+phi.symbol="\Phi"; phi.lsym="\phi"; phi.codename="phi";
+phi.draw=new guide[] (picture pic, guide[] ins) {
+ pair[] z=endpoints(ins);
+ pair m=min(z), M=max(z);
+ real w=M.x-m.x, h=hwratio*w;
+ pair centre=(0.5(m.x+M.x),M.y+h/2);
+
+
+ //real offset=4*linewidth(currentpen);
+ draw(pic, ins[0]..centre{NE});
+ draw(pic, ins[1]..centre{NW});
+ draw(pic, centre,linewidth(5*linewidth(currentpen)));
+ dot(pic, centre);
+ return new guide[] {centre..centre+0.5h*N};
+};
+
+Component wye=new Component;
+wye.in=1; wye.out=2;
+wye.connections=null; // TODO: Fix this!
+wye.symbol="Y"; wye.lsym="y"; wye.codename="wye";
+wye.draw=new guide[] (picture pic, guide[] ins) {
+ pair z=endpoint(ins[0]);
+ real w=10, h=hwratio*w; // The 10 is a guess here, and may produce badness.
+ pair centre=(z.x,z.y+h/2);
+
+
+ draw(pic, ins[0]..centre);
+ draw(pic, centre,linewidth(5*linewidth(currentpen)));
+ return new guide[] {centre{NW}..centre+(-0.5w,0.5h),
+ centre{NE}..centre+(0.5w,0.5h)};
+};
+
+
+struct Braid { // {{{1
+ // Members {{{2
+ // Number of lines initially.
+ int n;
+
+ struct Placement {
+ Component c;
+ int place;
+
+ Placement copy() {
+ Placement p=new Placement;
+ p.c=this.c; p.place=this.place;
+ return p;
+ }
+ }
+ Placement[] places;
+
+ void add(Component c, int place) {
+ Placement p=new Placement;
+ p.c=c; p.place=place;
+ places.push(p);
+ }
+
+ void add(Braid sub, int place) {
+ for (int i=0; i<sub.places.length; ++i)
+ add(sub.places[i].c,sub.places[i].place+place);
+ }
+
+ // Drawing {{{2
+ guide[] drawStep(picture pic, Placement p, guide[] ins) {
+ int i=0,j=0;
+
+ // Draw the component.
+ Component c=p.c;
+ //write("drawing "+c.symbol+" at place "+(string)p.place);
+ guide[] couts=c.draw(pic, ins[sequence(c.in)+p.place]);
+
+ pair M=max(endpoints(couts));
+
+ // Extend lines not in the component.
+ guide[] outs;
+ pair[] z=endpoints(ins);
+ while (i<p.place) {
+ outs.push(ins[i]..(z[i].x,M.y));
+ ++i;
+ }
+
+ outs.append(couts);
+ i+=c.in;
+
+ while (i<ins.length) {
+ outs.push(ins[i]..(z[i].x,M.y));
+ ++i;
+ }
+
+ return outs;
+ }
+
+ void drawEnd(picture pic, guide[] ins, real minheight=0) {
+ pair[] z=endpoints(ins);
+ for (int i=0; i<ins.length; ++i) {
+ draw(pic, z[i].y >= minheight ? ins[i] : ins[i]..(z[i].x,minheight));
+ }
+ }
+
+ void draw(picture pic, guide[] ins, real minheight=0) {
+ int steps=places.length;
+
+ guide[] nodes=ins;
+ for (int i=0; i<steps; ++i) {
+ Placement p=places[i];
+ nodes=drawStep(pic, places[i], nodes);
+ }
+
+ drawEnd(pic, nodes, minheight);
+ }
+
+ void draw(picture pic=currentpicture, real spacing=15,
+ real minheight=2hwratio*spacing) {
+ pair[] ins;
+ for (int i=0; i<n; ++i)
+ ins.push((spacing*i,0));
+
+ draw(pic, ins, minheight);
+ }
+
+ // Utilities {{{2
+ int in() {
+ return n;
+ }
+ int out() {
+ int steps=places.length;
+ int num=n; // The number of nodes at this step.
+
+ for (int i=0; i<steps; ++i) {
+ Placement p=places[i];
+ int nextNum=num-p.c.in+p.c.out;
+ num=nextNum;
+ }
+ return num;
+ }
+
+ // Deep copy of a braid.
+ Braid copy() {
+ Braid b=new Braid;
+ b.n=this.n;
+ for (int i=0; i<this.places.length; ++i)
+ b.add(this.places[i].c,this.places[i].place);
+ return b;
+ }
+
+ // Matching {{{2
+ // Tests if a component p can be swapped with a component q which is assumed
+ // to be directly above it.
+ static bool swapable(Placement p, Placement q) {
+ return p.place + p.c.out <= q.place || // p is left of q or
+ q.place + q.c.in <= p.place; // q is left of p
+ }
+
+ // Creates a new braid with a transposition of two components.
+ Braid swap(int i, int j) {
+ if (i>j)
+ return swap(j,i);
+ else {
+ assert(j==i+1); assert(swapable(places[i],places[j]));
+
+ Placement p=places[i].copy();
+ Placement q=places[j].copy();
+ /*write("swap:");
+ write("p originally at " + (string)p.place);
+ write("q originally at " + (string)q.place);
+ write("p.c.in: " + (string)p.c.in + " p.c.out: " + (string)p.c.out);
+ write("q.c.in: " + (string)q.c.in + " q.c.out: " + (string)q.c.out);*/
+ if (q.place + q.c.in <= p.place)
+ // q is left of p - adjust for q renumbering strings.
+ p.place+=q.c.out-q.c.in;
+ else if (p.place + p.c.out <= q.place)
+ // q is right of p - adjust for p renumbering strings.
+ q.place+=p.c.in-p.c.out;
+ else
+ // q is directly on top of p
+ assert(false, "swapable");
+
+ /*write("q now at " + (string)q.place);
+ write("p now at " + (string)p.place);*/
+
+ Braid b=this.copy();
+ b.places[i]=q;
+ b.places[j]=p;
+ return b;
+ }
+ }
+
+ // Tests if the component at index 'start' can be moved to index 'end'
+ // without interfering with other components.
+ bool moveable(int start, int end) {
+ assert(start<places.length); assert(end<places.length);
+ if (start==end)
+ return true;
+ else if (end<start)
+ return moveable(end,start);
+ else {
+ assert(start<end);
+ Placement p=places[start].copy();
+ for (int step=start; step<end; ++step) {
+ Placement q=places[step+1];
+ if (q.place + q.c.in <= p.place)
+ // q is left of p - adjust for q renumbering strings.
+ p.place+=q.c.out-q.c.in;
+ else if (p.place + p.c.out <= q.place)
+ // q is right of p - nothing to do.
+ continue;
+ else
+ // q is directly on top of p
+ return false;
+ }
+ return true;
+ }
+ }
+
+ bool matchComponent(Braid sub, int subindex, int place, int step) {
+ int i=subindex;
+ return sub.places[i].c == this.places[step].c &&
+ sub.places[i].place + place == this.places[step].place;
+ }
+
+ // Returns true if a sub-braid occurs within the one at the specified
+ // coordinates with no component occuring anywhere inbetween.
+ bool exactMatch(Braid sub, int place, int step) {
+ for (int i=0; i<sub.places.length; ++i) {
+ if (!matchComponent(sub, i, place, i+step)) {
+ write("match failed at iteration: ", i);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ bool findSubsequence(Braid sub, int place, int size, int[] acc) {
+ // If we've matched all the components, we've won.
+ if (acc.length >= sub.places.length)
+ return true;
+
+ // The next component to match.
+ Placement p=sub.places[acc.length];
+
+ // Start looking immediately after the last match.
+ for (int step=acc[acc.length-1]+1; step<this.places.length; ++step) {
+ Placement q=this.places[step];
+ */
+
+ bool tryMatch(Braid sub, int place, int size, int[] acc) {
+ // If we've matched all the components, we've won.
+ if (acc.length >= sub.places.length)
+ return true;
+
+ // The next component to match.
+ Placement p=sub.places[acc.length];
+
+ // Start looking immediately after the last match.
+ for (int step=acc[acc.length-1]+1; step<this.places.length; ++step) {
+ Placement q=this.places[step];
+ // Check if the next component is in the set of strings used by the
+ // subbraid.
+ if (q.place + q.c.in > place && q.place < place + size) {
+ // It's in the window, so it must match the next component in the
+ // subbraid.
+ if (p.c==q.c && p.place+place==q.place) {
+ // A match - go on to the next component.
+ acc.push(step);
+ return tryMatch(sub, place, size, acc); // TODO: Adjust place/size.
+ }
+ else
+ return false;
+ }
+
+ // TODO: Adjust place and size.
+ }
+
+ // We've run out of components to match.
+ return false;
+ }
+
+
+ // This attempts to find a subbraid within the braid. It allows other
+ // components to be interspersed with the components of the subbraid so long
+ // as they don't occur on the same string as the ones the subbraid lies on.
+ // Returns null on failure.
+ int[] match(Braid sub, int place) {
+ for (int i=0; i<=this.places.length-sub.places.length; ++i) {
+ // Find where the first component of the subbraid matches and try to
+ // match the rest of the braid starting from there.
+ if (matchComponent(sub, 0, place, i)) {
+ int[] result;
+ result.push(i);
+ if (tryMatch(sub,place,sub.n,result))
+ return result;
+ }
+ }
+ return null;
+ }
+
+ // Equations {{{2
+ // Returns the string that 'place' moves to when going through the section
+ // with Placement p.
+ static int advancePast(Placement p, int place) {
+ // If it's to the left of the component, it is unaffected.
+ return place<p.place ? place :
+ // If it's to the right of the component, adjust the numbering due
+ // to the change of the number of strings in the component.
+ p.place+p.c.in <= place ? place - p.c.in + p.c.out :
+ // If it's in the component, ask the component to do the work.
+ p.place + p.c.connections[place-p.place];
+ }
+
+ // Adjust the place (at step 0) to the step given, to find which string it is
+ // on in that part of the diagram.
+ int advanceToStep(int step, int place) {
+ assert(place>=0 && place<n);
+ assert(step>=0 && step<places.length);
+
+ for (int i=0; i<step; ++i)
+ place=advancePast(places[i], place);
+
+ return place;
+ }
+
+ int pullbackWindowPlace(int step, int place,
+ int w_place, int w_size) {
+ place=advanceToStep(step,place);
+ return place < w_place ? 1 : // The shielding.
+ w_place + w_size <= place ? 0 : // The string doesn't touch it.
+ place-w_place+2;
+ }
+
+ int pullbackPlace(int step, int place) {
+ // Move to the right step.
+ //write("advance: ", step, place, advanceToStep(step,place));
+ //place=advanceToStep(step,place);
+ Placement p=places[step];
+ return pullbackWindowPlace(step,place, p.place, p.c.in);
+ /*return place < p.place ? 1 : // The shielding.
+ p.place + p.c.in <= place ? 0 : // The string doesn't touch it.
+ place-p.place+2;*/
+ }
+
+ int[] pullbackWindow(int step, int w_place, int w_size) {
+ int[] a={1};
+ for (int place=0; place<n; ++place)
+ a.push(pullbackWindowPlace(step, place, w_place, w_size));
+ return a;
+ }
+
+ int[] pullback(int step) {
+ Placement p=places[step];
+ return pullbackWindow(step, p.place, p.c.in);
+ /*int[] a={1};
+ for (int place=0; place<n; ++place)
+ a.push(pullbackPlace(step, place));
+ return a;*/
+ }
+
+ string stepToFormula(int step) {
+ // Determine the pullbacks.
+ string s="(1";
+ for (int place=0; place<n; ++place)
+ //write("pullback: ", step, place, pullbackString(step,place));
+ s+=(string)pullbackPlace(step, place);
+ s+=")^\star "+places[step].c.symbol;
+ return s;
+ }
+
+ // Write it as a formula with pullback notation.
+ string toFormula() {
+ if (places.length==0)
+ return "1";
+ else {
+ string s;
+ for (int step=0; step<places.length; ++step) {
+ if (step>0)
+ s+=" ";
+ s+=stepToFormula(step);
+ }
+ return s;
+ }
+ }
+
+ string windowToLinear(int step, int w_place, int w_size) {
+ int[] a=pullbackWindow(step, w_place, w_size);
+ string s="(";
+ for (int arg=1; arg<=w_size+1; ++arg) {
+ if (arg>1)
+ s+=",";
+ bool first=true;
+ for (int var=0; var<a.length; ++var) {
+ if (a[var]==arg) {
+ if (first)
+ first=false;
+ else
+ s+="+";
+ s+="x_"+(string)(var+1);
+ }
+ }
+ }
+ return s+")";
+ }
+
+ string windowToCode(int step, int w_place, int w_size) {
+ int[] a=pullbackWindow(step, w_place, w_size);
+ string s="[";
+ for (int arg=1; arg<=w_size+1; ++arg) {
+ if (arg>1)
+ s+=", ";
+ bool first=true;
+ for (int var=0; var<a.length; ++var) {
+ if (a[var]==arg) {
+ if (first)
+ first=false;
+ else
+ s+=" + ";
+ s+="x"+(string)(var+1);
+ }
+ }
+ }
+ return s+"]";
+ }
+
+ string stepToLinear(int step) {
+ //int[] a=pullback(step);
+ Placement p=places[step];
+ return p.c.lsym+windowToLinear(step, p.place, p.c.in);
+
+ /*string s=p.c.lsym+"(";
+ for (int arg=1; arg<=p.c.in+1; ++arg) {
+ if (arg>1)
+ s+=",";
+ bool first=true;
+ for (int var=0; var<a.length; ++var) {
+ if (a[var]==arg) {
+ if (first)
+ first=false;
+ else
+ s+="+";
+ s+="x_"+(string)(var+1);
+ }
+ }
+ }
+ return s+")";*/
+ }
+
+ string stepToCode(int step) {
+ Placement p=places[step];
+ return p.c.codename+windowToCode(step, p.place, p.c.in);
+ }
+
+ string toLinear(bool subtract=false) {
+ if (places.length==0)
+ return subtract ? "0" : ""; // or "1" ?
+ else {
+ string s = subtract ? " - " : "";
+ for (int step=0; step<places.length; ++step) {
+ if (step>0)
+ s+= subtract ? " - " : " + ";
+ s+=stepToLinear(step);
+ }
+ return s;
+ }
+ }
+
+ string toCode(bool subtract=false) {
+ if (places.length==0)
+ return subtract ? "0" : ""; // or "1" ?
+ else {
+ string s = subtract ? " - " : "";
+ for (int step=0; step<places.length; ++step) {
+ if (step>0)
+ s+= subtract ? " - " : " + ";
+ s+=stepToCode(step);
+ }
+ return s;
+ }
+ }
+}
+
+struct Relation { // {{{1
+ Braid lhs, rhs;
+
+ string lsym, codename;
+ bool inverted=false;
+
+ string toFormula() {
+ return lhs.toFormula() + " = " + rhs.toFormula();
+ }
+
+ string linearName() {
+ assert(lhs.n==rhs.n);
+ assert(lsym!="");
+
+ string s=(inverted ? "-" : "") + lsym+"(";
+ for (int i=1; i<=lhs.n+1; ++i) {
+ if (i>1)
+ s+=",";
+ s+="x_"+(string)i;
+ }
+ return s+")";
+ }
+
+ string fullCodeName() {
+ assert(lhs.n==rhs.n);
+ assert(codename!="");
+
+ string s=(inverted ? "minus" : "") + codename+"[";
+ for (int i=1; i<=lhs.n+1; ++i) {
+ if (i>1)
+ s+=", ";
+ s+="x"+(string)i+"_";
+ }
+ return s+"]";
+ }
+
+ string toLinear() {
+ return linearName() + " = " + lhs.toLinear() + rhs.toLinear(true);
+ }
+
+ string toCode() {
+ return fullCodeName() + " :> " + lhs.toCode() + rhs.toCode(true);
+ }
+
+ void draw(picture pic=currentpicture) {
+ picture left; lhs.draw(left);
+ frame l=left.fit();
+ picture right; rhs.draw(right);
+ frame r=right.fit();
+
+ real xpad=30;
+
+ add(pic, l);
+ label(pic, "=", (max(l).x + 0.5xpad, 0.25(max(l).y+max(r).y)));
+ add(pic, r, (max(l).x+xpad,0));
+ }
+}
+
+Relation operator- (Relation r) {
+ Relation opposite;
+ opposite.lhs=r.rhs;
+ opposite.rhs=r.lhs;
+ opposite.lsym=r.lsym;
+ opposite.codename=r.codename;
+ opposite.inverted=!r.inverted;
+ return opposite;
+}
+
+
+Braid apply(Relation r, Braid b, int step, int place) {
+ bool valid=b.exactMatch(r.lhs,place,step);
+ if (valid) {
+ Braid result=new Braid;
+ result.n=b.n;
+ for (int i=0; i<step; ++i)
+ result.places.push(b.places[i]);
+ result.add(r.rhs,place);
+ for (int i=step+r.lhs.places.length; i<b.places.length; ++i)
+ result.places.push(b.places[i]);
+ return result;
+ }
+ else {
+ write("Invalid match!");
+ return null;
+ }
+}
+
+// Tableau {{{1
+
+// Draw a number of frames in a nice circular arrangement.
+picture tableau(frame[] cards, bool number=false) {
+ int n=cards.length;
+
+ // Calculate the max height and width of the frames (assuming min(f)=(0,0)).
+ pair M=(0,0);
+ for (int i=0; i<n; ++i) {
+ pair z=max(cards[i]);
+ if (z.x > M.x)
+ M=(z.x,M.y);
+ if (z.y > M.y)
+ M=(M.x,z.y);
+ }
+
+ picture pic;
+ real xpad=2.0, ypad=1.3;
+ void place(int index, real row, real column) {
+ pair z=((M.x*xpad)*column,(M.y*ypad)*row);
+ add(pic, cards[index], z);
+ if (number) {
+ label(pic,(string)index, z+(0.5M.x,0), S);
+ }
+ }
+
+ // Handle small collections.
+ if (n<=4) {
+ for (int i=0; i<n; ++i)
+ place(i,0,i);
+ }
+ else {
+ int rows=quotient(n-1,2), columns=3;
+
+ // Add the top middle card.
+ place(0,rows-1,1);
+
+ // place cards down the right side.
+ for (int i=1; i<rows; ++i)
+ place(i, rows-i,2);
+
+ // place cards at the bottom.
+ if (n%2==0) {
+ place(rows,0,2);
+ place(rows+1,0,1);
+ place(rows+2,0,0);
+ }
+ else {
+ place(rows,0,1.5);
+ place(rows+1,0,0.5);
+ }
+
+ // place cards up the left side.
+ for (int i=1; i<rows; ++i)
+ place(i+n-rows,i,0);
+ }
+
+ return pic;
+}
+
+struct Syzygy { // {{{1
+ // Setup {{{2
+ Braid initial=null;
+ bool cyclic=true;
+ bool showall=false;
+ bool number=false; // Number the diagrams when drawn.
+
+ string lsym, codename;
+
+ bool watched=false;
+ bool uptodate=true;
+
+ struct Move {
+ Braid action(Braid);
+ Relation rel;
+ int place, step;
+ }
+
+ Move[] moves;
+
+ void apply(Relation r, int step, int place) {
+ Move m=new Move;
+ m.rel=r;
+ m.place=place; m.step=step;
+ m.action=new Braid (Braid b) {
+ return apply(r, b, step, place);
+ };
+ moves.push(m);
+
+ uptodate = false;
+ }
+
+ void swap(int i, int j) {
+ Move m=new Move;
+ m.rel=null;
+ m.action=new Braid (Braid b) {
+ return b.swap(i, j);
+ };
+ moves.push(m);
+
+ uptodate = false;
+ }
+
+ // Drawing {{{2
+ picture[] drawMoves() {
+ picture[] pics;
+
+ assert(initial!=null, "must set initial braid");
+ Braid b=initial;
+
+ picture pic;
+ b.draw(pic);
+ pics.push(pic);
+
+ for (int i=0; i<moves.length; ++i) {
+ b=moves[i].action(b);
+ if (showall || moves[i].rel != null) {
+ picture pic;
+ b.draw(pic);
+ pics.push(pic);
+ }
+ }
+
+ // Remove the last picture.
+ if (this.cyclic)
+ pics.pop();
+
+ return pics;
+ }
+
+ void draw(picture pic=currentpicture) {
+ pic.add(tableau(fit(drawMoves()), this.number));
+ }
+
+ void updatefunction() {
+ if (!uptodate) {
+ picture pic; this.draw(pic);
+ shipout(pic);
+ uptodate = true;
+ }
+ }
+
+ void oldupdatefunction() = null;
+
+ void watch() {
+ if (!watched) {
+ watched = true;
+ oldupdatefunction = atupdate();
+ atupdate(this.updatefunction);
+ uptodate = false;
+ }
+ }
+
+ void unwatch() {
+ assert(watched == true);
+ atupdate(oldupdatefunction);
+ uptodate = false;
+ }
+
+ // Writing {{{2
+ string linearName() {
+ assert(lsym!="");
+
+ string s=lsym+"(";
+ for (int i=1; i<=initial.n+1; ++i) {
+ if (i>1)
+ s+=",";
+ s+="x_"+(string)i;
+ }
+ return s+")";
+ }
+
+ string fullCodeName() {
+ assert(codename!="");
+
+ string s=codename+"[";
+ for (int i=1; i<=initial.n+1; ++i) {
+ if (i>1)
+ s+=", ";
+ s+="x"+(string)i+"_";
+ }
+ return s+"]";
+ }
+
+ string toLinear() {
+ string s=linearName()+" = ";
+
+ Braid b=initial;
+ bool first=true;
+ for (int i=0; i<moves.length; ++i) {
+ Move m=moves[i];
+ if (m.rel != null) {
+ if (first) {
+ first=false;
+ if (m.rel.inverted)
+ s+=" - ";
+ }
+ else
+ s+=m.rel.inverted ? " - " : " + ";
+ s+=m.rel.lsym+b.windowToLinear(m.step, m.place, m.rel.lhs.n);
+ }
+ b=m.action(b);
+ }
+
+ return s;
+ }
+
+ string toCode() {
+ string s=fullCodeName()+" :> ";
+
+ Braid b=initial;
+ bool first=true;
+ for (int i=0; i<moves.length; ++i) {
+ Move m=moves[i];
+ if (m.rel != null) {
+ if (first) {
+ first=false;
+ if (m.rel.inverted)
+ s+=" - ";
+ }
+ else
+ s+=m.rel.inverted ? " - " : " + ";
+ s+=m.rel.codename+b.windowToCode(m.step, m.place, m.rel.lhs.n);
+ }
+ b=m.action(b);
+ }
+
+ return s;
+ }
+
+}
+
+// Relation definitions {{{1
+// If you define more relations that you think would be useful, please email
+// them to me, and I'll add them to the script. --Andy.
+Relation r3;
+r3.lhs.n=3;
+r3.lsym="\rho_3"; r3.codename="rho3";
+r3.lhs.add(bp,0); r3.lhs.add(bp,1); r3.lhs.add(bp,0);
+r3.rhs.n=3;
+r3.rhs.add(bp,1); r3.rhs.add(bp,0); r3.rhs.add(bp,1);
+
+Relation r4a;
+r4a.lhs.n=3;
+r4a.lsym="\rho_{4a}"; r4a.codename="rho4a";
+r4a.lhs.add(bp,0); r4a.lhs.add(bp,1); r4a.lhs.add(phi,0);
+r4a.rhs.n=3;
+r4a.rhs.add(phi,1); r4a.rhs.add(bp,0);
+
+Relation r4b;
+r4b.lhs.n=3;
+r4b.lsym="\rho_{4b}"; r4b.codename="rho4b";
+r4b.lhs.add(bp,1); r4b.lhs.add(bp,0); r4b.lhs.add(phi,1);
+r4b.rhs.n=3;
+r4b.rhs.add(phi,0); r4b.rhs.add(bp,0);
+
diff --git a/graphics/asymptote/base/texcolors.asy b/graphics/asymptote/base/texcolors.asy
new file mode 100644
index 0000000000..90d9606cb7
--- /dev/null
+++ b/graphics/asymptote/base/texcolors.asy
@@ -0,0 +1,68 @@
+pen GreenYellow=cmyk(0.15,0,0.69,0);
+pen Yellow=cmyk(0,0,1,0);
+pen Goldenrod=cmyk(0,0.10,0.84,0);
+pen Dandelion=cmyk(0,0.29,0.84,0);
+pen Apricot=cmyk(0,0.32,0.52,0);
+pen Peach=cmyk(0,0.50,0.70,0);
+pen Melon=cmyk(0,0.46,0.50,0);
+pen YellowOrange=cmyk(0,0.42,1,0);
+pen Orange=cmyk(0,0.61,0.87,0);
+pen BurntOrange=cmyk(0,0.51,1,0);
+pen Bittersweet=cmyk(0,0.75,1,0.24);
+pen RedOrange=cmyk(0,0.77,0.87,0);
+pen Mahogany=cmyk(0,0.85,0.87,0.35);
+pen Maroon=cmyk(0,0.87,0.68,0.32);
+pen BrickRed=cmyk(0,0.89,0.94,0.28);
+pen Red=cmyk(0,1,1,0);
+pen OrangeRed=cmyk(0,1,0.50,0);
+pen RubineRed=cmyk(0,1,0.13,0);
+pen WildStrawberry=cmyk(0,0.96,0.39,0);
+pen Salmon=cmyk(0,0.53,0.38,0);
+pen CarnationPink=cmyk(0,0.63,0,0);
+pen Magenta=cmyk(0,1,0,0);
+pen VioletRed=cmyk(0,0.81,0,0);
+pen Rhodamine=cmyk(0,0.82,0,0);
+pen Mulberry=cmyk(0.34,0.90,0,0.02);
+pen RedViolet=cmyk(0.07,0.90,0,0.34);
+pen Fuchsia=cmyk(0.47,0.91,0,0.08);
+pen Lavender=cmyk(0,0.48,0,0);
+pen Thistle=cmyk(0.12,0.59,0,0);
+pen Orchid=cmyk(0.32,0.64,0,0);
+pen DarkOrchid=cmyk(0.40,0.80,0.20,0);
+pen Purple=cmyk(0.45,0.86,0,0);
+pen Plum=cmyk(0.50,1,0,0);
+pen Violet=cmyk(0.79,0.88,0,0);
+pen RoyalPurple=cmyk(0.75,0.90,0,0);
+pen BlueViolet=cmyk(0.86,0.91,0,0.04);
+pen Periwinkle=cmyk(0.57,0.55,0,0);
+pen CadetBlue=cmyk(0.62,0.57,0.23,0);
+pen CornflowerBlue=cmyk(0.65,0.13,0,0);
+pen MidnightBlue=cmyk(0.98,0.13,0,0.43);
+pen NavyBlue=cmyk(0.94,0.54,0,0);
+pen RoyalBlue=cmyk(1,0.50,0,0);
+pen Blue=cmyk(1,1,0,0);
+pen Cerulean=cmyk(0.94,0.11,0,0);
+pen Cyan=cmyk(1,0,0,0);
+pen ProcessBlue=cmyk(0.96,0,0,0);
+pen SkyBlue=cmyk(0.62,0,0.12,0);
+pen Turquoise=cmyk(0.85,0,0.20,0);
+pen TealBlue=cmyk(0.86,0,0.34,0.02);
+pen Aquamarine=cmyk(0.82,0,0.30,0);
+pen BlueGreen=cmyk(0.85,0,0.33,0);
+pen Emerald=cmyk(1,0,0.50,0);
+pen JungleGreen=cmyk(0.99,0,0.52,0);
+pen SeaGreen=cmyk(0.69,0,0.50,0);
+pen Green=cmyk(1,0,1,0);
+pen ForestGreen=cmyk(0.91,0,0.88,0.12);
+pen PineGreen=cmyk(0.92,0,0.59,0.25);
+pen LimeGreen=cmyk(0.50,0,1,0);
+pen YellowGreen=cmyk(0.44,0,0.74,0);
+pen SpringGreen=cmyk(0.26,0,0.76,0);
+pen OliveGreen=cmyk(0.64,0,0.95,0.40);
+pen RawSienna=cmyk(0,0.72,1,0.45);
+pen Sepia=cmyk(0,0.83,1,0.70);
+pen Brown=cmyk(0,0.81,1,0.60);
+pen Tan=cmyk(0.14,0.42,0.56,0);
+pen Gray=cmyk(0,0,0,0.50);
+pen Black=cmyk(0,0,0,1);
+pen White=cmyk(0,0,0,0);
diff --git a/graphics/asymptote/base/three.asy b/graphics/asymptote/base/three.asy
new file mode 100644
index 0000000000..866137b1db
--- /dev/null
+++ b/graphics/asymptote/base/three.asy
@@ -0,0 +1,3251 @@
+private import math;
+
+if(settings.xasy)
+ settings.render=0;
+
+if(prc0()) {
+ if(!latex()) settings.prc=false;
+ else {
+ access embed;
+ Embed=embed.embedplayer;
+ }
+}
+
+// Useful lossy compression values.
+restricted real Zero=0;
+restricted real Low=0.0001;
+restricted real Medium=0.001;
+restricted real High=0.01;
+
+restricted int PRCsphere=0; // Renders slowly but produces smaller PRC files.
+restricted int NURBSsphere=1; // Renders fast but produces larger PRC files.
+
+struct render
+{
+ // PRC parameters:
+ real compression; // lossy compression parameter (0=no compression)
+ real granularity; // PRC rendering granularity
+
+ bool closed; // use one-sided rendering?
+ bool tessellate; // use tessellated mesh to store straight patches?
+
+ bool3 merge; // merge nodes before rendering, for faster but
+ // lower quality PRC rendering (the value default means
+ // merge opaque patches only).
+
+ int sphere; // PRC sphere type (PRCsphere or NURBSsphere).
+
+ // General parameters:
+ real margin; // shrink amount for rendered openGL viewport, in bp.
+ real tubegranularity; // granularity for rendering tubes
+ bool labelfill; // fill subdivision cracks in unlighted labels
+
+ bool partnames; // assign part name indices to compound objects
+ bool defaultnames; // assign default names to unnamed objects
+
+ static render defaultrender;
+
+ void operator init(real compression=defaultrender.compression,
+ real granularity=defaultrender.granularity,
+ bool closed=defaultrender.closed,
+ bool tessellate=defaultrender.tessellate,
+ bool3 merge=defaultrender.merge,
+ int sphere=defaultrender.sphere,
+ real margin=defaultrender.margin,
+ real tubegranularity=defaultrender.tubegranularity,
+ bool labelfill=defaultrender.labelfill,
+ bool partnames=defaultrender.partnames,
+ bool defaultnames=defaultrender.defaultnames)
+ {
+ this.compression=compression;
+ this.granularity=granularity;
+ this.closed=closed;
+ this.tessellate=tessellate;
+ this.merge=merge;
+ this.sphere=sphere;
+ this.margin=margin;
+ this.tubegranularity=tubegranularity;
+ this.labelfill=labelfill;
+ this.partnames=partnames;
+ this.defaultnames=defaultnames;
+ }
+}
+
+render operator init() {return render();}
+
+render defaultrender=render.defaultrender=new render;
+defaultrender.compression=High;
+defaultrender.granularity=Medium;
+defaultrender.closed=false;
+defaultrender.tessellate=false;
+defaultrender.merge=false;
+defaultrender.margin=0.02;
+defaultrender.tubegranularity=0.001;
+defaultrender.sphere=NURBSsphere;
+defaultrender.labelfill=true;
+defaultrender.partnames=false;
+defaultrender.defaultnames=true;
+
+real defaultshininess=0.7;
+real defaultmetallic=0.0;
+real defaultfresnel0=0.04;
+
+
+
+real angleprecision=1e-5; // Precision for centering perspective projections.
+int maxangleiterations=25;
+
+string defaultembed3Doptions="3Dmenu";
+string defaultembed3Dscript;
+real defaulteyetoview=63mm/1000mm;
+
+string partname(int i, render render=defaultrender)
+{
+ return render.partnames ? string(i+1) : "";
+}
+
+triple O=(0,0,0);
+triple X=(1,0,0), Y=(0,1,0), Z=(0,0,1);
+
+// A translation in 3D space.
+transform3 shift(triple v)
+{
+ transform3 t=identity(4);
+ t[0][3]=v.x;
+ t[1][3]=v.y;
+ t[2][3]=v.z;
+ return t;
+}
+
+// Avoid two parentheses.
+transform3 shift(real x, real y, real z)
+{
+ return shift((x,y,z));
+}
+
+transform3 shift(transform3 t)
+{
+ transform3 T=identity(4);
+ T[0][3]=t[0][3];
+ T[1][3]=t[1][3];
+ T[2][3]=t[2][3];
+ return T;
+}
+
+// A 3D scaling in the x direction.
+transform3 xscale3(real x)
+{
+ transform3 t=identity(4);
+ t[0][0]=x;
+ return t;
+}
+
+// A 3D scaling in the y direction.
+transform3 yscale3(real y)
+{
+ transform3 t=identity(4);
+ t[1][1]=y;
+ return t;
+}
+
+// A 3D scaling in the z direction.
+transform3 zscale3(real z)
+{
+ transform3 t=identity(4);
+ t[2][2]=z;
+ return t;
+}
+
+// A 3D scaling by s in the v direction.
+transform3 scale(triple v, real s)
+{
+ v=unit(v);
+ s -= 1;
+ return new real[][] {
+ {1+s*v.x^2, s*v.x*v.y, s*v.x*v.z, 0},
+ {s*v.x*v.y, 1+s*v.y^2, s*v.y*v.z, 0},
+ {s*v.x*v.z, s*v.y*v.z, 1+s*v.z^2, 0},
+ {0, 0, 0, 1}};
+}
+
+// A transformation representing rotation by an angle in degrees about
+// an axis v through the origin (in the right-handed direction).
+transform3 rotate(real angle, triple v)
+{
+ if(v == O) abort("cannot rotate about the zero vector");
+ v=unit(v);
+ real x=v.x, y=v.y, z=v.z;
+ real s=Sin(angle), c=Cos(angle), t=1-c;
+
+ return new real[][] {
+ {t*x^2+c, t*x*y-s*z, t*x*z+s*y, 0},
+ {t*x*y+s*z, t*y^2+c, t*y*z-s*x, 0},
+ {t*x*z-s*y, t*y*z+s*x, t*z^2+c, 0},
+ {0, 0, 0, 1}};
+}
+
+// A transformation representing rotation by an angle in degrees about
+// the line u--v (in the right-handed direction).
+transform3 rotate(real angle, triple u, triple v)
+{
+ return shift(u)*rotate(angle,v-u)*shift(-u);
+}
+
+// Reflects about the plane through u, v, and w.
+transform3 reflect(triple u, triple v, triple w)
+{
+ triple n=unit(cross(v-u,w-u));
+ if(n == O)
+ abort("points determining reflection plane cannot be colinear");
+
+ return new real[][] {
+ {1-2*n.x^2, -2*n.x*n.y, -2*n.x*n.z, u.x},
+ {-2*n.x*n.y, 1-2*n.y^2, -2*n.y*n.z, u.y},
+ {-2*n.x*n.z, -2*n.y*n.z, 1-2*n.z^2, u.z},
+ {0, 0, 0, 1}
+ }*shift(-u);
+}
+
+// Project u onto v.
+triple project(triple u, triple v)
+{
+ v=unit(v);
+ return dot(u,v)*v;
+}
+
+// Return a unit vector perpendicular to a given unit vector v.
+triple perp(triple v)
+{
+ triple u=cross(v,Y);
+ if(abs(u) > sqrtEpsilon) return unit(u);
+ u=cross(v,Z);
+ return (abs(u) > sqrtEpsilon) ? unit(u) : X;
+}
+
+// Return the transformation corresponding to moving the camera from the target
+// (looking in the negative z direction) to the point 'eye' (looking at target,
+// orienting the camera so that direction 'up' points upwards.
+// Since, in actuality, we are transforming the points instead of the camera,
+// we calculate the inverse matrix.
+// Based on the gluLookAt implementation in the OpenGL manual.
+transform3 look(triple eye, triple up=Z, triple target=O)
+{
+ triple f=unit(target-eye);
+ if(f == O)
+ f=-Z; // The eye is already at the origin: look down.
+
+ triple s=cross(f,up);
+
+ // If the eye is pointing either directly up or down, there is no
+ // preferred "up" direction. Pick one arbitrarily.
+ s=s != O ? unit(s) : perp(f);
+
+ triple u=cross(s,f);
+
+ transform3 M={{ s.x, s.y, s.z, 0},
+ { u.x, u.y, u.z, 0},
+ {-f.x, -f.y, -f.z, 0},
+ { 0, 0, 0, 1}};
+
+ return M*shift(-eye);
+}
+
+// Return a matrix to do perspective distortion based on a triple v.
+transform3 distort(triple v)
+{
+ transform3 t=identity(4);
+ real d=length(v);
+ if(d == 0) return t;
+ t[3][2]=-1/d;
+ t[3][3]=0;
+ return t;
+}
+
+projection operator * (transform3 t, projection P)
+{
+ projection P=P.copy();
+ if(!P.absolute) {
+ P.camera=t*P.camera;
+ triple target=P.target;
+ P.target=t*P.target;
+ if(P.infinity)
+ P.normal=t*(target+P.normal)-P.target;
+ else
+ P.normal=P.vector();
+ P.calculate();
+ }
+ return P;
+}
+
+// With this, save() and restore() in plain also save and restore the
+// currentprojection.
+addSaveFunction(new restoreThunk() {
+ projection P=currentprojection.copy();
+ return new void() {
+ currentprojection=P;
+ };
+ });
+
+pair project(triple v, projection P=currentprojection)
+{
+ return project(v,P.t);
+}
+
+pair dir(triple v, triple dir, projection P)
+{
+ return unit(project(v+0.5dir,P)-project(v-0.5*dir,P));
+}
+
+// Uses the homogenous coordinate to perform perspective distortion.
+// When combined with a projection to the XY plane, this effectively maps
+// points in three space to a plane through target and
+// perpendicular to the vector camera-target.
+projection perspective(triple camera, triple up=Z, triple target=O,
+ real zoom=1, real angle=0, pair viewportshift=0,
+ bool showtarget=true, bool autoadjust=true,
+ bool center=autoadjust)
+{
+ if(camera == target)
+ abort("camera cannot be at target");
+ return projection(camera,up,target,zoom,angle,viewportshift,
+ showtarget,autoadjust,center,
+ new transformation(triple camera, triple up, triple target)
+ {return transformation(look(camera,up,target),
+ distort(camera-target));});
+}
+
+projection perspective(real x, real y, real z, triple up=Z, triple target=O,
+ real zoom=1, real angle=0, pair viewportshift=0,
+ bool showtarget=true, bool autoadjust=true,
+ bool center=autoadjust)
+{
+ return perspective((x,y,z),up,target,zoom,angle,viewportshift,showtarget,
+ autoadjust,center);
+}
+
+projection orthographic(triple camera, triple up=Z, triple target=O,
+ real zoom=1, pair viewportshift=0,
+ bool showtarget=true, bool center=false)
+{
+ return projection(camera,up,target,zoom,viewportshift,showtarget,
+ center=center,new transformation(triple camera, triple up,
+ triple target) {
+ return transformation(look(camera,up,target));});
+}
+
+projection orthographic(real x, real y, real z, triple up=Z,
+ triple target=O, real zoom=1, pair viewportshift=0,
+ bool showtarget=true, bool center=false)
+{
+ return orthographic((x,y,z),up,target,zoom,viewportshift,showtarget,
+ center=center);
+}
+
+// Compute camera position with x axis below the horizontal at angle alpha,
+// y axis below the horizontal at angle beta, and z axis up.
+triple camera(real alpha, real beta)
+{
+ real denom=Tan(alpha+beta);
+ real Tanalpha=Tan(alpha);
+ real Tanbeta=Tan(beta);
+ return (sqrt(Tanalpha/denom),sqrt(Tanbeta/denom),sqrt(Tanalpha*Tanbeta));
+}
+
+projection oblique(real angle=45)
+{
+ transform3 t=identity(4);
+ real c2=Cos(angle)^2;
+ real s2=1-c2;
+ t[0][2]=-c2;
+ t[1][2]=-s2;
+ t[2][2]=1;
+ t[2][3]=-1;
+ return projection((c2,s2,1),up=Y,normal=(0,0,1),
+ new transformation(triple,triple,triple) {
+ return transformation(t);});
+}
+
+projection obliqueZ(real angle=45) {return oblique(angle);}
+
+projection obliqueX(real angle=45)
+{
+ transform3 t=identity(4);
+ real c2=Cos(angle)^2;
+ real s2=1-c2;
+ t[0][0]=-c2;
+ t[1][0]=-s2;
+ t[1][1]=0;
+ t[0][1]=1;
+ t[1][2]=1;
+ t[2][2]=0;
+ t[2][0]=1;
+ t[2][3]=-1;
+ return projection((1,c2,s2),normal=(1,0,0),
+ new transformation(triple,triple,triple) {
+ return transformation(t);});
+}
+
+projection obliqueY(real angle=45)
+{
+ transform3 t=identity(4);
+ real c2=Cos(angle)^2;
+ real s2=1-c2;
+ t[0][1]=c2;
+ t[1][1]=s2;
+ t[1][2]=1;
+ t[2][1]=-1;
+ t[2][2]=0;
+ t[2][3]=-1;
+ return projection((c2,-1,s2),normal=(0,-1,0),
+ new transformation(triple,triple,triple) {
+ return transformation(t);});
+}
+
+projection oblique=oblique();
+projection obliqueX=obliqueX(), obliqueY=obliqueY(), obliqueZ=obliqueZ();
+
+projection LeftView=orthographic(-X,showtarget=true);
+projection RightView=orthographic(X,showtarget=true);
+projection FrontView=orthographic(-Y,showtarget=true);
+projection BackView=orthographic(Y,showtarget=true);
+projection BottomView=orthographic(-Z,up=-Y,showtarget=true);
+projection TopView=orthographic(Z,up=Y,showtarget=true);
+
+currentprojection=perspective(5,4,2);
+
+projection projection()
+{
+ projection P;
+ real[] a=_projection();
+ if(a.length == 0 || a[10] == 0.0) return currentprojection;
+ int k=0;
+ return a[0] == 1 ?
+ orthographic((a[++k],a[++k],a[++k]),(a[++k],a[++k],a[++k]),
+ (a[++k],a[++k],a[++k]),a[++k],(a[k += 2],a[++k])) :
+ perspective((a[++k],a[++k],a[++k]),(a[++k],a[++k],a[++k]),
+ (a[++k],a[++k],a[++k]),a[++k],a[++k],(a[++k],a[++k]));
+}
+
+// Map pair z to a triple by inverting the projection P onto the
+// plane perpendicular to normal and passing through point.
+triple invert(pair z, triple normal, triple point,
+ projection P=currentprojection)
+{
+ transform3 t=P.t;
+ real[][] A={{t[0][0]-z.x*t[3][0],t[0][1]-z.x*t[3][1],t[0][2]-z.x*t[3][2]},
+ {t[1][0]-z.y*t[3][0],t[1][1]-z.y*t[3][1],t[1][2]-z.y*t[3][2]},
+ {normal.x,normal.y,normal.z}};
+ real[] b={z.x*t[3][3]-t[0][3],z.y*t[3][3]-t[1][3],dot(normal,point)};
+ real[] x=solve(A,b,warn=false);
+ return x.length > 0 ? (x[0],x[1],x[2]) : P.camera;
+}
+
+// Map pair to a triple on the projection plane.
+triple invert(pair z, projection P=currentprojection)
+{
+ return invert(z,P.normal,P.target,P);
+}
+
+// Map pair dir to a triple direction at point v on the projection plane.
+triple invert(pair dir, triple v, projection P=currentprojection)
+{
+ return invert(project(v,P)+dir,P.normal,v,P)-v;
+}
+
+pair xypart(triple v)
+{
+ return (v.x,v.y);
+}
+
+struct control {
+ triple post,pre;
+ bool active=false;
+ bool straight=true;
+ void operator init(triple post, triple pre, bool straight=false) {
+ this.post=post;
+ this.pre=pre;
+ active=true;
+ this.straight=straight;
+ }
+}
+
+control nocontrol;
+
+control operator * (transform3 t, control c)
+{
+ control C;
+ C.post=t*c.post;
+ C.pre=t*c.pre;
+ C.active=c.active;
+ C.straight=c.straight;
+ return C;
+}
+
+void write(file file, control c)
+{
+ write(file,".. controls ");
+ write(file,c.post);
+ write(file," and ");
+ write(file,c.pre);
+}
+
+struct Tension {
+ real out,in;
+ bool atLeast;
+ bool active;
+ void operator init(real out=1, real in=1, bool atLeast=false,
+ bool active=true) {
+ real check(real val) {
+ if(val < 0.75) abort("tension cannot be less than 3/4");
+ return val;
+ }
+ this.out=check(out);
+ this.in=check(in);
+ this.atLeast=atLeast;
+ this.active=active;
+ }
+}
+
+Tension operator init()
+{
+ return Tension();
+}
+
+Tension noTension;
+noTension.active=false;
+
+void write(file file, Tension t)
+{
+ write(file,"..tension ");
+ if(t.atLeast) write(file,"atleast ");
+ write(file,t.out);
+ write(file," and ");
+ write(file,t.in);
+}
+
+struct dir {
+ triple dir;
+ real gamma=1; // endpoint curl
+ bool Curl; // curl specified
+ bool active() {
+ return dir != O || Curl;
+ }
+ void init(triple v) {
+ this.dir=v;
+ }
+ void init(real gamma) {
+ if(gamma < 0) abort("curl cannot be less than 0");
+ this.gamma=gamma;
+ this.Curl=true;
+ }
+ void init(dir d) {
+ dir=d.dir;
+ gamma=d.gamma;
+ Curl=d.Curl;
+ }
+ void default(triple v) {
+ if(!active()) init(v);
+ }
+ void default(dir d) {
+ if(!active()) init(d);
+ }
+ dir copy() {
+ dir d=new dir;
+ d.init(this);
+ return d;
+ }
+}
+
+void write(file file, dir d)
+{
+ if(d.dir != O) {
+ write(file,"{"); write(file,unit(d.dir)); write(file,"}");
+ } else if(d.Curl) {
+ write(file,"{curl "); write(file,d.gamma); write(file,"}");
+ }
+}
+
+dir operator * (transform3 t, dir d)
+{
+ dir D=d.copy();
+ D.init(unit(shiftless(t)*d.dir));
+ return D;
+}
+
+void checkEmpty(int n) {
+ if(n == 0)
+ abort("nullpath3 has no points");
+}
+
+int adjustedIndex(int i, int n, bool cycles)
+{
+ checkEmpty(n);
+ if(cycles)
+ return i % n;
+ else if(i < 0)
+ return 0;
+ else if(i >= n)
+ return n-1;
+ else
+ return i;
+}
+
+struct flatguide3 {
+ triple[] nodes;
+ bool[] cyclic; // true if node is really a cycle
+ control[] control; // control points for segment starting at node
+ Tension[] Tension; // Tension parameters for segment starting at node
+ dir[] in,out; // in and out directions for segment starting at node
+
+ bool cyclic() {int n=cyclic.length; return n > 0 ? cyclic[n-1] : false;}
+ bool precyclic() {int i=find(cyclic); return i >= 0 && i < cyclic.length-1;}
+
+ int size() {
+ return cyclic() ? nodes.length-1 : nodes.length;
+ }
+
+ void node(triple v, bool b=false) {
+ nodes.push(v);
+ control.push(nocontrol);
+ Tension.push(noTension);
+ in.push(new dir);
+ out.push(new dir);
+ cyclic.push(b);
+ }
+
+ void control(triple post, triple pre) {
+ if(control.length > 0) {
+ control c=control(post,pre,false);
+ control[control.length-1]=c;
+ }
+ }
+
+ void Tension(real out, real in, bool atLeast) {
+ if(Tension.length > 0)
+ Tension[Tension.length-1]=Tension(out,in,atLeast,true);
+ }
+
+ void in(triple v) {
+ if(in.length > 0) {
+ in[in.length-1].init(v);
+ }
+ }
+
+ void out(triple v) {
+ if(out.length > 0) {
+ out[out.length-1].init(v);
+ }
+ }
+
+ void in(real gamma) {
+ if(in.length > 0) {
+ in[in.length-1].init(gamma);
+ }
+ }
+
+ void out(real gamma) {
+ if(out.length > 0) {
+ out[out.length-1].init(gamma);
+ }
+ }
+
+ void cycleToken() {
+ if(nodes.length > 0)
+ node(nodes[0],true);
+ }
+
+ // Return true if outgoing direction at node i is known.
+ bool solved(int i) {
+ return out[i].active() || control[i].active;
+ }
+}
+
+void write(file file, string s="", explicit flatguide3 x, suffix suffix=none)
+{
+ write(file,s);
+ if(x.size() == 0) write(file,"<nullpath3>");
+ else for(int i=0; i < x.nodes.length; ++i) {
+ if(i > 0) write(file,endl);
+ if(x.cyclic[i]) write(file,"cycle");
+ else write(file,x.nodes[i]);
+ if(i < x.nodes.length-1) {
+ // Explicit control points trump other specifiers
+ if(x.control[i].active)
+ write(file,x.control[i]);
+ else {
+ write(file,x.out[i]);
+ if(x.Tension[i].active) write(file,x.Tension[i]);
+ }
+ write(file,"..");
+ if(!x.control[i].active) write(file,x.in[i]);
+ }
+ }
+ write(file,suffix);
+}
+
+void write(string s="", flatguide3 x, suffix suffix=endl)
+{
+ write(stdout,s,x,suffix);
+}
+
+// A guide3 is most easily represented as something that modifies a flatguide3.
+typedef void guide3(flatguide3);
+
+restricted void nullpath3(flatguide3) {};
+
+guide3 operator init() {return nullpath3;}
+
+guide3 operator cast(triple v)
+{
+ return new void(flatguide3 f) {
+ f.node(v);
+ };
+}
+
+guide3 operator cast(cycleToken) {
+ return new void(flatguide3 f) {
+ f.cycleToken();
+ };
+}
+
+guide3 operator controls(triple post, triple pre)
+{
+ return new void(flatguide3 f) {
+ f.control(post,pre);
+ };
+};
+
+guide3 operator controls(triple v)
+{
+ return operator controls(v,v);
+}
+
+guide3 operator cast(tensionSpecifier t)
+{
+ return new void(flatguide3 f) {
+ f.Tension(t.out, t.in, t.atLeast);
+ };
+}
+
+guide3 operator cast(curlSpecifier spec)
+{
+ return new void(flatguide3 f) {
+ if(spec.side == JOIN_OUT) f.out(spec.value);
+ else if(spec.side == JOIN_IN) f.in(spec.value);
+ else
+ abort("invalid curl specifier");
+ };
+}
+
+guide3 operator spec(triple v, int side)
+{
+ return new void(flatguide3 f) {
+ if(side == JOIN_OUT) f.out(v);
+ else if(side == JOIN_IN) f.in(v);
+ else
+ abort("invalid direction specifier");
+ };
+}
+
+guide3 operator -- (... guide3[] g)
+{
+ return new void(flatguide3 f) {
+ if(g.length > 0) {
+ for(int i=0; i < g.length-1; ++i) {
+ g[i](f);
+ f.out(1);
+ f.in(1);
+ }
+ g[g.length-1](f);
+ }
+ };
+}
+
+guide3 operator .. (... guide3[] g)
+{
+ return new void(flatguide3 f) {
+ for(int i=0; i < g.length; ++i)
+ g[i](f);
+ };
+}
+
+typedef guide3 interpolate3(... guide3[]);
+
+interpolate3 join3(tensionSpecifier t)
+{
+ return new guide3(... guide3[] a) {
+ if(a.length == 0) return nullpath3;
+ guide3 g=a[0];
+ for(int i=1; i < a.length; ++i)
+ g=g..t..a[i];
+ return g;
+ };
+}
+
+interpolate3 operator ::=join3(operator tension(1,true));
+interpolate3 operator ---=join3(operator tension(infinity,true));
+
+flatguide3 operator cast(guide3 g)
+{
+ flatguide3 f;
+ g(f);
+ return f;
+}
+
+flatguide3[] operator cast(guide3[] g)
+{
+ flatguide3[] p=new flatguide3[g.length];
+ for(int i=0; i < g.length; ++i) {
+ flatguide3 f;
+ g[i](f);
+ p[i]=f;
+ }
+ return p;
+}
+
+// A version of asin that tolerates numerical imprecision
+real asin1(real x)
+{
+ return asin(min(max(x,-1),1));
+}
+
+// A version of acos that tolerates numerical imprecision
+real acos1(real x)
+{
+ return acos(min(max(x,-1),1));
+}
+
+struct Controls {
+ triple c0,c1;
+
+ // 3D extension of John Hobby's control point formula
+ // (cf. The MetaFont Book, page 131),
+ // as described in John C. Bowman and A. Hammerlindl,
+ // TUGBOAT: The Communications of the TeX Users Group 29:2 (2008).
+
+ void operator init(triple v0, triple v1, triple d0, triple d1, real tout,
+ real tin, bool atLeast) {
+ triple v=v1-v0;
+ triple u=unit(v);
+ real L=length(v);
+ d0=unit(d0);
+ d1=unit(d1);
+ real theta=acos1(dot(d0,u));
+ real phi=acos1(dot(d1,u));
+ if(dot(cross(d0,v),cross(v,d1)) < 0) phi=-phi;
+ c0=v0+d0*L*relativedistance(theta,phi,tout,atLeast);
+ c1=v1-d1*L*relativedistance(phi,theta,tin,atLeast);
+ }
+}
+
+private triple cross(triple d0, triple d1, triple reference)
+{
+ triple normal=cross(d0,d1);
+ return normal == O ? reference : normal;
+}
+
+private triple dir(real theta, triple d0, triple d1, triple reference)
+{
+ triple normal=cross(d0,d1,reference);
+ if(normal == O) return d1;
+ return rotate(degrees(theta),dot(normal,reference) >= 0 ? normal : -normal)*
+ d1;
+}
+
+private real angle(triple d0, triple d1, triple reference)
+{
+ real theta=acos1(dot(unit(d0),unit(d1)));
+ return dot(cross(d0,d1,reference),reference) >= 0 ? theta : -theta;
+}
+
+// 3D extension of John Hobby's angle formula (The MetaFont Book, page 131).
+// Notational differences: here psi[i] is the turning angle at z[i+1],
+// beta[i] is the tension for segment i, and in[i] is the incoming
+// direction for segment i (where segment i begins at node i).
+
+real[] theta(triple[] v, real[] alpha, real[] beta,
+ triple dir0, triple dirn, real g0, real gn, triple reference)
+{
+ real[] a,b,c,f,l,psi;
+ int n=alpha.length;
+ bool cyclic=v.cyclic;
+ for(int i=0; i < n; ++i)
+ l[i]=1/length(v[i+1]-v[i]);
+ int i0,in;
+ if(cyclic) {i0=0; in=n;}
+ else {i0=1; in=n-1;}
+ for(int i=0; i < in; ++i)
+ psi[i]=angle(v[i+1]-v[i],v[i+2]-v[i+1],reference);
+ if(cyclic) {
+ l.cyclic=true;
+ psi.cyclic=true;
+ } else {
+ psi[n-1]=0;
+ if(dir0 == O) {
+ real a0=alpha[0];
+ real b0=beta[0];
+ real chi=g0*(b0/a0)^2;
+ a[0]=0;
+ b[0]=3a0-a0/b0+chi;
+ real C=chi*(3a0-1)+a0/b0;
+ c[0]=C;
+ f[0]=-C*psi[0];
+ } else {
+ a[0]=c[0]=0;
+ b[0]=1;
+ f[0]=angle(v[1]-v[0],dir0,reference);
+ }
+ if(dirn == O) {
+ real an=alpha[n-1];
+ real bn=beta[n-1];
+ real chi=gn*(an/bn)^2;
+ a[n]=chi*(3bn-1)+bn/an;
+ b[n]=3bn-bn/an+chi;
+ c[n]=f[n]=0;
+ } else {
+ a[n]=c[n]=0;
+ b[n]=1;
+ f[n]=angle(v[n]-v[n-1],dirn,reference);
+ }
+ }
+
+ for(int i=i0; i < n; ++i) {
+ real in=beta[i-1]^2*l[i-1];
+ real A=in/alpha[i-1];
+ a[i]=A;
+ real B=3*in-A;
+ real out=alpha[i]^2*l[i];
+ real C=out/beta[i];
+ b[i]=B+3*out-C;
+ c[i]=C;
+ f[i]=-B*psi[i-1]-C*psi[i];
+ }
+
+ return tridiagonal(a,b,c,f);
+}
+
+triple reference(triple[] v, int n, triple d0, triple d1)
+{
+ triple[] V=sequence(new triple(int i) {
+ return cross(v[i+1]-v[i],v[i+2]-v[i+1]);
+ },n-1);
+ if(n > 0) {
+ V.push(cross(d0,v[1]-v[0]));
+ V.push(cross(v[n]-v[n-1],d1));
+ }
+
+ triple max=V[0];
+ real M=abs(max);
+ for(int i=1; i < V.length; ++i) {
+ triple vi=V[i];
+ real a=abs(vi);
+ if(a > M) {
+ M=a;
+ max=vi;
+ }
+ }
+
+ triple reference;
+ for(int i=0; i < V.length; ++i) {
+ triple u=unit(V[i]);
+ reference += dot(u,max) < 0 ? -u : u;
+ }
+
+ return reference;
+}
+
+// Fill in missing directions for n cyclic nodes.
+void aim(flatguide3 g, int N)
+{
+ bool cyclic=true;
+ int start=0, end=0;
+
+ // If the cycle contains one or more direction specifiers, break the loop.
+ for(int k=0; k < N; ++k)
+ if(g.solved(k)) {cyclic=false; end=k; break;}
+ for(int k=N-1; k >= 0; --k)
+ if(g.solved(k)) {cyclic=false; start=k; break;}
+ while(start < N && g.control[start].active) ++start;
+
+ int n=N-(start-end);
+ if(n <= 1 || (cyclic && n <= 2)) return;
+
+ triple[] v=new triple[cyclic ? n : n+1];
+ real[] alpha=new real[n];
+ real[] beta=new real[n];
+ for(int k=0; k < n; ++k) {
+ int K=(start+k) % N;
+ v[k]=g.nodes[K];
+ alpha[k]=g.Tension[K].out;
+ beta[k]=g.Tension[K].in;
+ }
+ if(cyclic) {
+ v.cyclic=true;
+ alpha.cyclic=true;
+ beta.cyclic=true;
+ } else v[n]=g.nodes[(start+n) % N];
+ int final=(end-1) % N;
+
+ triple d0=g.out[start].dir;
+ triple d1=g.in[final].dir;
+
+ triple reference=reference(v,n,d0,d1);
+
+ real[] theta=theta(v,alpha,beta,d0,d1,g.out[start].gamma,g.in[final].gamma,
+ reference);
+
+ v.cyclic=true;
+ theta.cyclic=true;
+
+ for(int k=1; k < (cyclic ? n+1 : n); ++k) {
+ triple w=dir(theta[k],v[k]-v[k-1],v[k+1]-v[k],reference);
+ g.in[(start+k-1) % N].init(w);
+ g.out[(start+k) % N].init(w);
+ }
+
+ if(g.out[start].dir == O)
+ g.out[start].init(dir(theta[0],v[0]-g.nodes[(start-1) % N],v[1]-v[0],
+ reference));
+ if(g.in[final].dir == O)
+ g.in[final].init(dir(theta[n],v[n-1]-v[n-2],v[n]-v[n-1],reference));
+}
+
+// Fill in missing directions for the sequence of nodes i...n.
+void aim(flatguide3 g, int i, int n)
+{
+ int j=n-i;
+ if(j > 1 || g.out[i].dir != O || g.in[i].dir != O) {
+ triple[] v=new triple[j+1];
+ real[] alpha=new real[j];
+ real[] beta=new real[j];
+ for(int k=0; k < j; ++k) {
+ v[k]=g.nodes[i+k];
+ alpha[k]=g.Tension[i+k].out;
+ beta[k]=g.Tension[i+k].in;
+ }
+ v[j]=g.nodes[n];
+
+ triple d0=g.out[i].dir;
+ triple d1=g.in[n-1].dir;
+
+ triple reference=reference(v,j,d0,d1);
+
+ real[] theta=theta(v,alpha,beta,d0,d1,g.out[i].gamma,g.in[n-1].gamma,
+ reference);
+
+ for(int k=1; k < j; ++k) {
+ triple w=dir(theta[k],v[k]-v[k-1],v[k+1]-v[k],reference);
+ g.in[i+k-1].init(w);
+ g.out[i+k].init(w);
+ }
+ if(g.out[i].dir == O) {
+ triple w=dir(theta[0],g.in[i].dir,v[1]-v[0],reference);
+ if(i > 0) g.in[i-1].init(w);
+ g.out[i].init(w);
+ }
+ if(g.in[n-1].dir == O) {
+ triple w=dir(theta[j],g.out[n-1].dir,v[j]-v[j-1],reference);
+ g.in[n-1].init(w);
+ g.out[n].init(w);
+ }
+ }
+}
+
+private real Fuzz=10*realEpsilon;
+
+triple XYplane(pair z) {return (z.x,z.y,0);}
+triple YZplane(pair z) {return (0,z.x,z.y);}
+triple ZXplane(pair z) {return (z.y,0,z.x);}
+
+bool cyclic(guide3 g) {flatguide3 f; g(f); return f.cyclic();}
+int size(guide3 g) {flatguide3 f; g(f); return f.size();}
+int length(guide3 g) {flatguide3 f; g(f); return f.nodes.length-1;}
+
+triple dir(path3 p)
+{
+ return dir(p,length(p));
+}
+
+triple dir(path3 p, path3 h)
+{
+ return 0.5*(dir(p)+dir(h));
+}
+
+// return the point on path3 p at arclength L
+triple arcpoint(path3 p, real L)
+{
+ return point(p,arctime(p,L));
+}
+
+// return the direction on path3 p at arclength L
+triple arcdir(path3 p, real L)
+{
+ return dir(p,arctime(p,L));
+}
+
+// return the time on path3 p at the relative fraction l of its arclength
+real reltime(path3 p, real l)
+{
+ return arctime(p,l*arclength(p));
+}
+
+// return the point on path3 p at the relative fraction l of its arclength
+triple relpoint(path3 p, real l)
+{
+ return point(p,reltime(p,l));
+}
+
+// return the direction of path3 p at the relative fraction l of its arclength
+triple reldir(path3 p, real l)
+{
+ return dir(p,reltime(p,l));
+}
+
+// return the initial point of path3 p
+triple beginpoint(path3 p)
+{
+ return point(p,0);
+}
+
+// return the point on path3 p at half of its arclength
+triple midpoint(path3 p)
+{
+ return relpoint(p,0.5);
+}
+
+// return the final point of path3 p
+triple endpoint(path3 p)
+{
+ return point(p,length(p));
+}
+
+path3 path3(triple v)
+{
+ triple[] point={v};
+ return path3(point,point,point,new bool[] {false},false);
+}
+
+path3 path3(path p, triple plane(pair)=XYplane)
+{
+ int n=size(p);
+ return path3(sequence(new triple(int i) {return plane(precontrol(p,i));},n),
+ sequence(new triple(int i) {return plane(point(p,i));},n),
+ sequence(new triple(int i) {return plane(postcontrol(p,i));},n),
+ sequence(new bool(int i) {return straight(p,i);},n),
+ cyclic(p));
+}
+
+path3[] path3(explicit path[] g, triple plane(pair)=XYplane)
+{
+ return sequence(new path3(int i) {return path3(g[i],plane);},g.length);
+}
+
+path3 invert(path p, triple normal, triple point,
+ projection P=currentprojection)
+{
+ return path3(p,new triple(pair z) {return invert(z,normal,point,P);});
+}
+
+path3 invert(path p, triple point, projection P=currentprojection)
+{
+ return path3(p,new triple(pair z) {return invert(z,P.normal,point,P);});
+}
+
+path3 invert(path p, projection P=currentprojection)
+{
+ return path3(p,new triple(pair z) {return invert(z,P.normal,P.target,P);});
+}
+
+// Construct a path from a path3 by applying P to each control point.
+path path(path3 p, pair P(triple)=xypart)
+{
+ int n=length(p);
+ if(n < 0) return nullpath;
+ guide g=P(point(p,0));
+ if(n == 0) return g;
+ for(int i=1; i < n; ++i)
+ g=straight(p,i-1) ? g--P(point(p,i)) :
+ g..controls P(postcontrol(p,i-1)) and P(precontrol(p,i))..P(point(p,i));
+
+ if(straight(p,n-1))
+ return cyclic(p) ? g--cycle : g--P(point(p,n));
+
+ pair post=P(postcontrol(p,n-1));
+ pair pre=P(precontrol(p,n));
+ return cyclic(p) ? g..controls post and pre..cycle :
+ g..controls post and pre..P(point(p,n));
+}
+
+void write(file file, string s="", explicit path3 x, suffix suffix=none)
+{
+ write(file,s);
+ int n=length(x);
+ if(n < 0) write("<nullpath3>");
+ else {
+ for(int i=0; i < n; ++i) {
+ write(file,point(x,i));
+ if(i < length(x)) {
+ if(straight(x,i)) write(file,"--");
+ else {
+ write(file,".. controls ");
+ write(file,postcontrol(x,i));
+ write(file," and ");
+ write(file,precontrol(x,i+1),newl);
+ write(file," ..");
+ }
+ }
+ }
+ if(cyclic(x))
+ write(file,"cycle",suffix);
+ else
+ write(file,point(x,n),suffix);
+ }
+}
+
+void write(string s="", explicit path3 x, suffix suffix=endl)
+{
+ write(stdout,s,x,suffix);
+}
+
+void write(file file, string s="", explicit path3[] x, suffix suffix=none)
+{
+ write(file,s);
+ if(x.length > 0) write(file,x[0]);
+ for(int i=1; i < x.length; ++i) {
+ write(file,endl);
+ write(file," ^^");
+ write(file,x[i]);
+ }
+ write(file,suffix);
+}
+
+void write(string s="", explicit path3[] x, suffix suffix=endl)
+{
+ write(stdout,s,x,suffix);
+}
+
+path3 solve(flatguide3 g)
+{
+ int n=g.nodes.length-1;
+
+ // If duplicate points occur consecutively, add dummy controls (if absent).
+ for(int i=0; i < n; ++i) {
+ if(g.nodes[i] == g.nodes[i+1] && !g.control[i].active)
+ g.control[i]=control(g.nodes[i],g.nodes[i],straight=true);
+ }
+
+ // Fill in empty direction specifiers inherited from explicit control points.
+ for(int i=0; i < n; ++i) {
+ if(g.control[i].active) {
+ g.out[i].init(g.control[i].post-g.nodes[i]);
+ g.in[i].init(g.nodes[i+1]-g.control[i].pre);
+ }
+ }
+
+ // Propagate directions across nodes.
+ for(int i=0; i < n; ++i) {
+ int next=g.cyclic[i+1] ? 0 : i+1;
+ if(g.out[next].active())
+ g.in[i].default(g.out[next]);
+ if(g.in[i].active()) {
+ g.out[next].default(g.in[i]);
+ g.out[i+1].default(g.in[i]);
+ }
+ }
+
+ // Compute missing 3D directions.
+ // First, resolve cycles
+ int i=find(g.cyclic);
+ if(i > 0) {
+ aim(g,i);
+ // All other cycles can now be reduced to sequences.
+ triple v=g.out[0].dir;
+ for(int j=i; j <= n; ++j) {
+ if(g.cyclic[j]) {
+ g.in[j-1].default(v);
+ g.out[j].default(v);
+ if(g.nodes[j-1] == g.nodes[j] && !g.control[j-1].active)
+ g.control[j-1]=control(g.nodes[j-1],g.nodes[j-1]);
+ }
+ }
+ }
+
+ // Next, resolve sequences.
+ int i=0;
+ int start=0;
+ while(i < n) {
+ // Look for a missing outgoing direction.
+ while(i <= n && g.solved(i)) {start=i; ++i;}
+ if(i > n) break;
+ // Look for the end of the sequence.
+ while(i < n && !g.solved(i)) ++i;
+
+ while(start < i && g.control[start].active) ++start;
+
+ if(start < i)
+ aim(g,start,i);
+ }
+
+ // Compute missing 3D control points.
+ for(int i=0; i < n; ++i) {
+ int next=g.cyclic[i+1] ? 0 : i+1;
+ if(!g.control[i].active) {
+ control c;
+ if((g.out[i].Curl && g.in[i].Curl) ||
+ (g.out[i].dir == O && g.in[i].dir == O)) {
+ // fill in straight control points for path3 functions
+ triple delta=(g.nodes[i+1]-g.nodes[i])/3;
+ c=control(g.nodes[i]+delta,g.nodes[i+1]-delta,straight=true);
+ } else {
+ Controls C=Controls(g.nodes[i],g.nodes[next],g.out[i].dir,g.in[i].dir,
+ g.Tension[i].out,g.Tension[i].in,
+ g.Tension[i].atLeast);
+ c=control(C.c0,C.c1);
+ }
+ g.control[i]=c;
+ }
+ }
+
+ // Convert to Knuth's format (control points stored with nodes)
+ int n=g.nodes.length;
+ bool cyclic;
+ if(n > 0) {
+ cyclic=g.cyclic[n-1];
+ if(cyclic) --n;
+ }
+ triple[] pre=new triple[n];
+ triple[] point=new triple[n];
+ triple[] post=new triple[n];
+ bool[] straight=new bool[n];
+ if(n > 0) {
+ for(int i=0; i < n-1; ++i) {
+ point[i]=g.nodes[i];
+ post[i]=g.control[i].post;
+ pre[i+1]=g.control[i].pre;
+ straight[i]=g.control[i].straight;
+ }
+ point[n-1]=g.nodes[n-1];
+ if(cyclic) {
+ pre[0]=g.control[n-1].pre;
+ post[n-1]=g.control[n-1].post;
+ straight[n-1]=g.control[n-1].straight;
+ } else {
+ pre[0]=point[0];
+ post[n-1]=point[n-1];
+ straight[n-1]=false;
+ }
+ }
+
+ return path3(pre,point,post,straight,cyclic);
+}
+
+path nurb(path3 p, projection P, int ninterpolate=P.ninterpolate)
+{
+ triple f=P.camera;
+ triple u=unit(P.normal);
+ transform3 t=P.t;
+
+ path nurb(triple v0, triple v1, triple v2, triple v3) {
+ return nurb(project(v0,t),project(v1,t),project(v2,t),project(v3,t),
+ dot(u,f-v0),dot(u,f-v1),dot(u,f-v2),dot(u,f-v3),ninterpolate);
+ }
+
+ path g;
+
+ if(straight(p,0))
+ g=project(point(p,0),t);
+
+ int last=length(p);
+ for(int i=0; i < last; ++i) {
+ if(straight(p,i))
+ g=g--project(point(p,i+1),t);
+ else
+ g=g&nurb(point(p,i),postcontrol(p,i),precontrol(p,i+1),point(p,i+1));
+ }
+
+ int n=length(g);
+ if(cyclic(p)) g=g&cycle;
+
+ return g;
+}
+
+path project(path3 p, projection P=currentprojection,
+ int ninterpolate=P.ninterpolate)
+{
+ guide g;
+
+ int last=length(p);
+ if(last < 0) return g;
+
+ transform3 t=P.t;
+
+ if(ninterpolate == 1 || piecewisestraight(p)) {
+ g=project(point(p,0),t);
+ // Construct the path.
+ int stop=cyclic(p) ? last-1 : last;
+ for(int i=0; i < stop; ++i) {
+ if(straight(p,i))
+ g=g--project(point(p,i+1),t);
+ else {
+ g=g..controls project(postcontrol(p,i),t) and
+ project(precontrol(p,i+1),t)..project(point(p,i+1),t);
+ }
+ }
+ } else return nurb(p,P);
+
+ if(cyclic(p))
+ g=straight(p,last-1) ? g--cycle :
+ g..controls project(postcontrol(p,last-1),t) and
+ project(precontrol(p,last),t)..cycle;
+ return g;
+}
+
+pair[] project(triple[] v, projection P=currentprojection)
+{
+ return sequence(new pair(int i) {return project(v[i],P.t);},v.length);
+}
+
+path[] project(explicit path3[] g, projection P=currentprojection)
+{
+ return sequence(new path(int i) {return project(g[i],P);},g.length);
+}
+
+guide3 operator cast(path3 p)
+{
+ int last=length(p);
+
+ bool cyclic=cyclic(p);
+ int stop=cyclic ? last-1 : last;
+ return new void(flatguide3 f) {
+ if(last >= 0) {
+ f.node(point(p,0));
+ for(int i=0; i < stop; ++i) {
+ if(straight(p,i)) {
+ f.out(1);
+ f.in(1);
+ } else
+ f.control(postcontrol(p,i),precontrol(p,i+1));
+ f.node(point(p,i+1));
+ }
+ if(cyclic) {
+ if(straight(p,stop)) {
+ f.out(1);
+ f.in(1);
+ } else
+ f.control(postcontrol(p,stop),precontrol(p,last));
+ f.cycleToken();
+ }
+ }
+ };
+}
+
+// Return a unit normal vector to a planar path p (or O if the path is
+// nonplanar).
+triple normal(path3 p)
+{
+ triple normal;
+ real fuzz=sqrtEpsilon*abs(max(p)-min(p));
+ real absnormal;
+ real theta;
+
+ bool Cross(triple a, triple b) {
+ if(abs(a) >= fuzz && abs(b) >= fuzz) {
+ triple n=cross(unit(a),unit(b));
+ real absn=abs(n);
+ if(absn < sqrtEpsilon) return false;
+ n=unit(n);
+ if(absnormal > 0 &&
+ abs(normal-n) > sqrtEpsilon && abs(normal+n) > sqrtEpsilon)
+ return true;
+ else {
+ int sign=dot(n,normal) >= 0 ? 1 : -1;
+ theta += sign*asin1(absn);
+ if(absn > absnormal) {
+ absnormal=absn;
+ normal=n;
+ theta=sign*theta;
+ }
+ }
+ }
+ return false;
+ }
+
+ int L=length(p);
+ if(L <= 0) return O;
+
+ triple zi=point(p,0);
+ triple v0=zi-precontrol(p,0);
+ for(int i=0; i < L; ++i) {
+ triple c0=postcontrol(p,i);
+ triple c1=precontrol(p,i+1);
+ triple zp=point(p,i+1);
+ triple v1=c0-zi;
+ triple v2=c1-c0;
+ triple v3=zp-c1;
+ if(Cross(v0,v1) || Cross(v1,v2) || Cross(v2,v3)) return O;
+ v0=v3;
+ zi=zp;
+ }
+ return theta >= 0 ? normal : -normal;
+}
+
+// Return a unit normal vector to a polygon with vertices in p.
+triple normal(triple[] p)
+{
+ triple normal;
+ real fuzz=sqrtEpsilon*abs(maxbound(p)-minbound(p));
+ real absnormal;
+ real theta;
+
+ bool Cross(triple a, triple b) {
+ if(abs(a) >= fuzz && abs(b) >= fuzz) {
+ triple n=cross(unit(a),unit(b));
+ real absn=abs(n);
+ n=unit(n);
+ if(absnormal > 0 && absn > sqrtEpsilon &&
+ abs(normal-n) > sqrtEpsilon && abs(normal+n) > sqrtEpsilon)
+ return true;
+ else {
+ int sign=dot(n,normal) >= 0 ? 1 : -1;
+ theta += sign*asin1(absn);
+ if(absn > absnormal) {
+ absnormal=absn;
+ normal=n;
+ theta=sign*theta;
+ }
+ }
+ }
+ return false;
+ }
+
+ if(p.length <= 0) return O;
+
+ triple zi=p[0];
+ triple v0=zi-p[p.length-1];
+ for(int i=0; i < p.length-1; ++i) {
+ triple zp=p[i+1];
+ triple v1=zp-zi;
+ if(Cross(v0,v1)) return O;
+ v0=v1;
+ zi=zp;
+ }
+ return theta >= 0 ? normal : -normal;
+}
+
+// Transforms that map XY plane to YX, YZ, ZY, ZX, and XZ planes.
+restricted transform3 XY=identity4;
+restricted transform3 YX=rotate(-90,O,Z);
+restricted transform3 YZ=rotate(90,O,Z)*rotate(90,O,X);
+restricted transform3 ZY=rotate(-90,O,X)*YZ;
+restricted transform3 ZX=rotate(-90,O,Z)*rotate(-90,O,Y);
+restricted transform3 XZ=rotate(-90,O,Y)*ZX;
+
+private transform3 flip(transform3 t, triple X, triple Y, triple Z,
+ projection P)
+{
+ static transform3 flip(triple v) {
+ static real s(real x) {return x > 0 ? -1 : 1;}
+ return scale(s(v.x),s(v.y),s(v.z));
+ }
+
+ triple u=unit(P.normal);
+ triple up=unit(perp(P.up,u));
+ bool upright=dot(Z,u) >= 0;
+ if(dot(Y,up) < 0) {
+ t=flip(Y)*t;
+ upright=!upright;
+ }
+ return upright ? t : flip(X)*t;
+}
+
+restricted transform3 XY(projection P=currentprojection)
+{
+ return flip(XY,X,Y,Z,P);
+}
+
+restricted transform3 YX(projection P=currentprojection)
+{
+ return flip(YX,Y,X,Z,P);
+}
+
+restricted transform3 YZ(projection P=currentprojection)
+{
+ return flip(YZ,Y,Z,X,P);
+}
+
+restricted transform3 ZY(projection P=currentprojection)
+{
+ return flip(ZY,Z,Y,X,P);
+}
+
+restricted transform3 ZX(projection P=currentprojection)
+{
+ return flip(ZX,Z,X,Y,P);
+}
+
+restricted transform3 XZ(projection P=currentprojection)
+{
+ return flip(XZ,X,Z,Y,P);
+}
+
+// Transform3 that projects in direction dir onto plane with normal n
+// through point O.
+transform3 planeproject(triple n, triple O=O, triple dir=n)
+{
+ real a=n.x, b=n.y, c=n.z;
+ real u=dir.x, v=dir.y, w=dir.z;
+ real delta=1.0/(a*u+b*v+c*w);
+ real d=-(a*O.x+b*O.y+c*O.z)*delta;
+ return new real[][] {
+ {(b*v+c*w)*delta,-b*u*delta,-c*u*delta,-d*u},
+ {-a*v*delta,(a*u+c*w)*delta,-c*v*delta,-d*v},
+ {-a*w*delta,-b*w*delta,(a*u+b*v)*delta,-d*w},
+ {0,0,0,1}
+ };
+}
+
+// Transform3 that projects in direction dir onto plane defined by p.
+transform3 planeproject(path3 p, triple dir=O)
+{
+ triple n=normal(p);
+ return planeproject(n,point(p,0),dir == O ? n : dir);
+}
+
+// Transform for projecting onto plane through point O with normal cross(u,v).
+transform transform(triple u, triple v, triple O=O,
+ projection P=currentprojection)
+{
+ transform3 t=P.t;
+ real[] tO=t*new real[] {O.x,O.y,O.z,1};
+ real tO3=tO[3];
+ real factor=1/tO3^2;
+ real[] x=(tO3*t[0]-tO[0]*t[3])*factor;
+ real[] y=(tO3*t[1]-tO[1]*t[3])*factor;
+ triple x=(x[0],x[1],x[2]);
+ triple y=(y[0],y[1],y[2]);
+ u=unit(u);
+ v=unit(v);
+ return (0,0,dot(u,x),dot(v,x),dot(u,y),dot(v,y));
+}
+
+// Project Label onto plane through point O with normal cross(u,v).
+Label project(Label L, triple u, triple v, triple O=O,
+ projection P=currentprojection) {
+ Label L=L.copy();
+ L.position=project(O,P.t);
+ L.transform(transform(u,v,O,P));
+ return L;
+}
+
+path3 operator cast(guide3 g) {return solve(g);}
+path3 operator cast(triple v) {return path3(v);}
+
+guide3[] operator cast(triple[] v)
+{
+ return sequence(new guide3(int i) {return v[i];},v.length);
+}
+
+path3[] operator cast(triple[] v)
+{
+ return sequence(new path3(int i) {return v[i];},v.length);
+}
+
+path3[] operator cast(guide3[] g)
+{
+ return sequence(new path3(int i) {return solve(g[i]);},g.length);
+}
+
+guide3[] operator cast(path3[] g)
+{
+ return sequence(new guide3(int i) {return g[i];},g.length);
+}
+
+void write(file file, string s="", explicit guide3[] x, suffix suffix=none)
+{
+ write(file,s,(path3[]) x,suffix);
+}
+
+void write(string s="", explicit guide3[] x, suffix suffix=endl)
+{
+ write(stdout,s,(path3[]) x,suffix);
+}
+
+triple point(explicit guide3 g, int t) {
+ flatguide3 f;
+ g(f);
+ int n=f.size();
+ return f.nodes[adjustedIndex(t,n,f.cyclic())];
+}
+
+triple[] dirSpecifier(guide3 g, int t)
+{
+ flatguide3 f;
+ g(f);
+ int n=f.size();
+ checkEmpty(n);
+ if(f.cyclic()) t=t % n;
+ else if(t < 0 || t >= n-1) return new triple[];
+ return new triple[] {f.out[t].dir,f.in[t].dir};
+}
+
+triple[] controlSpecifier(guide3 g, int t) {
+ flatguide3 f;
+ g(f);
+ int n=f.size();
+ checkEmpty(n);
+ if(f.cyclic()) t=t % n;
+ else if(t < 0 || t >= n-1) return new triple[];
+ control c=f.control[t];
+ if(c.active) return new triple[] {c.post,c.pre};
+ else return new triple[];
+}
+
+tensionSpecifier tensionSpecifier(guide3 g, int t)
+{
+ flatguide3 f;
+ g(f);
+ int n=f.size();
+ checkEmpty(n);
+ if(f.cyclic()) t=t % n;
+ else if(t < 0 || t >= n-1) return operator tension(1,1,false);
+ Tension T=f.Tension[t];
+ return operator tension(T.out,T.in,T.atLeast);
+}
+
+real[] curlSpecifier(guide3 g, int t)
+{
+ flatguide3 f;
+ g(f);
+ int n=f.size();
+ checkEmpty(n);
+ if(f.cyclic()) t=t % n;
+ else if(t < 0 || t >= n-1) return new real[];
+ return new real[] {f.out[t].gamma,f.in[t].gamma};
+}
+
+guide3 reverse(guide3 g)
+{
+ flatguide3 f;
+ bool cyclic=cyclic(g);
+ g(f);
+
+ if(f.precyclic())
+ return reverse(solve(g));
+
+ int n=f.size();
+ checkEmpty(n);
+ guide3 G;
+ if(n >= 0) {
+ int start=cyclic ? n : n-1;
+ for(int i=start; i > 0; --i) {
+ G=G..f.nodes[i];
+ control c=f.control[i-1];
+ if(c.active)
+ G=G..operator controls(c.pre,c.post);
+ else {
+ dir in=f.in[i-1];
+ triple d=in.dir;
+ if(d != O) G=G..operator spec(-d,JOIN_OUT);
+ else if(in.Curl) G=G..operator curl(in.gamma,JOIN_OUT);
+ dir out=f.out[i-1];
+ triple d=out.dir;
+ if(d != O) G=G..operator spec(-d,JOIN_IN);
+ else if(out.Curl) G=G..operator curl(out.gamma,JOIN_IN);
+ }
+ }
+ if(cyclic) G=G..cycle;
+ else G=G..f.nodes[0];
+ }
+ return G;
+}
+
+triple intersectionpoint(path3 p, path3 q, real fuzz=-1)
+{
+ real[] t=intersect(p,q,fuzz);
+ if(t.length == 0) abort("paths do not intersect");
+ return point(p,t[0]);
+}
+
+// return an array containing all intersection points of p and q
+triple[] intersectionpoints(path3 p, path3 q, real fuzz=-1)
+{
+ real[][] t=intersections(p,q,fuzz);
+ return sequence(new triple(int i) {return point(p,t[i][0]);},t.length);
+}
+
+triple[] intersectionpoints(explicit path3[] p, explicit path3[] q,
+ real fuzz=-1)
+{
+ triple[] v;
+ for(int i=0; i < p.length; ++i)
+ for(int j=0; j < q.length; ++j)
+ v.append(intersectionpoints(p[i],q[j],fuzz));
+ return v;
+}
+
+path3 operator &(path3 p, cycleToken tok)
+{
+ int n=length(p);
+ if(n < 0) return nullpath3;
+ triple a=point(p,0);
+ triple b=point(p,n);
+ return subpath(p,0,n-1)..controls postcontrol(p,n-1) and precontrol(p,n)..
+ cycle;
+}
+
+// return the point on path3 p at arclength L
+triple arcpoint(path3 p, real L)
+{
+ return point(p,arctime(p,L));
+}
+
+// return the point on path3 p at arclength L
+triple arcpoint(path3 p, real L)
+{
+ return point(p,arctime(p,L));
+}
+
+// return the direction on path3 p at arclength L
+triple arcdir(path3 p, real L)
+{
+ return dir(p,arctime(p,L));
+}
+
+// return the time on path3 p at the relative fraction l of its arclength
+real reltime(path3 p, real l)
+{
+ return arctime(p,l*arclength(p));
+}
+
+// return the point on path3 p at the relative fraction l of its arclength
+triple relpoint(path3 p, real l)
+{
+ return point(p,reltime(p,l));
+}
+
+// return the direction of path3 p at the relative fraction l of its arclength
+triple reldir(path3 p, real l)
+{
+ return dir(p,reltime(p,l));
+}
+
+// return the point on path3 p at half of its arclength
+triple midpoint(path3 p)
+{
+ return relpoint(p,0.5);
+}
+
+real relative(Label L, path3 g)
+{
+ return L.position.relative ? reltime(g,L.relative()) : L.relative();
+}
+
+// return the linear transformation that maps X,Y,Z to u,v,w.
+transform3 transform3(triple u, triple v, triple w=cross(u,v))
+{
+ return new real[][] {
+ {u.x,v.x,w.x,0},
+ {u.y,v.y,w.y,0},
+ {u.z,v.z,w.z,0},
+ {0,0,0,1}
+ };
+}
+
+// return the rotation that maps Z to a unit vector u about cross(u,Z),
+transform3 align(triple u)
+{
+ real a=u.x;
+ real b=u.y;
+ real c=u.z;
+ real d=a^2+b^2;
+
+ if(d != 0) {
+ d=sqrt(d);
+ real e=1/d;
+ return new real[][] {
+ {-b*e,-a*c*e,a,0},
+ {a*e,-b*c*e,b,0},
+ {0,d,c,0},
+ {0,0,0,1}};
+ }
+ return c >= 0 ? identity(4) : diagonal(1,-1,-1,1);
+}
+
+// return a rotation that maps X,Y to the projection plane.
+transform3 transform3(projection P=currentprojection)
+{
+ triple w=unit(P.normal);
+ triple v=unit(perp(P.up,w));
+ if(v == O) v=cross(perp(w),w);
+ triple u=cross(v,w);
+ return u != O ? transform3(u,v,w) : identity(4);
+}
+
+triple[] triples(real[] x, real[] y, real[] z)
+{
+ if(x.length != y.length || x.length != z.length)
+ abort("arrays have different lengths");
+ return sequence(new triple(int i) {return (x[i],y[i],z[i]);},x.length);
+}
+
+path3[] operator cast(path3 p)
+{
+ return new path3[] {p};
+}
+
+path3[] operator cast(guide3 g)
+{
+ return new path3[] {(path3) g};
+}
+
+path3[] operator ^^ (path3 p, path3 q)
+{
+ return new path3[] {p,q};
+}
+
+path3[] operator ^^ (path3 p, explicit path3[] q)
+{
+ return concat(new path3[] {p},q);
+}
+
+path3[] operator ^^ (explicit path3[] p, path3 q)
+{
+ return concat(p,new path3[] {q});
+}
+
+path3[] operator ^^ (explicit path3[] p, explicit path3[] q)
+{
+ return concat(p,q);
+}
+
+path3[] operator * (transform3 t, explicit path3[] p)
+{
+ return sequence(new path3(int i) {return t*p[i];},p.length);
+}
+
+triple[] operator * (transform3 t, triple[] v)
+{
+ return sequence(new triple(int i) {return t*v[i];},v.length);
+}
+
+triple[][] operator * (transform3 t, triple[][] v)
+{
+ triple[][] V=new triple[v.length][];
+ for(int i=0; i < v.length; ++i) {
+ triple[] vi=v[i];
+ V[i]=sequence(new triple(int j) {return t*vi[j];},vi.length);
+ }
+ return V;
+}
+
+triple min(explicit path3[] p)
+{
+ checkEmpty(p.length);
+ triple minp=min(p[0]);
+ for(int i=1; i < p.length; ++i)
+ minp=minbound(minp,min(p[i]));
+ return minp;
+}
+
+triple max(explicit path3[] p)
+{
+ checkEmpty(p.length);
+ triple maxp=max(p[0]);
+ for(int i=1; i < p.length; ++i)
+ maxp=maxbound(maxp,max(p[i]));
+ return maxp;
+}
+
+path3 randompath3(int n, bool cumulate=true, interpolate3 join=operator ..)
+{
+ guide3 g;
+ triple w;
+ for(int i=0; i <= n; ++i) {
+ triple z=(unitrand()-0.5,unitrand()-0.5,unitrand()-0.5);
+ if(cumulate) w += z;
+ else w=z;
+ g=join(g,w);
+ }
+ return g;
+}
+
+path3[] box(triple v1, triple v2)
+{
+ return
+ (v1.x,v1.y,v1.z)--
+ (v1.x,v1.y,v2.z)--
+ (v1.x,v2.y,v2.z)--
+ (v1.x,v2.y,v1.z)--
+ (v1.x,v1.y,v1.z)--
+ (v2.x,v1.y,v1.z)--
+ (v2.x,v1.y,v2.z)--
+ (v2.x,v2.y,v2.z)--
+ (v2.x,v2.y,v1.z)--
+ (v2.x,v1.y,v1.z)^^
+ (v2.x,v2.y,v1.z)--
+ (v1.x,v2.y,v1.z)^^
+ (v1.x,v2.y,v2.z)--
+ (v2.x,v2.y,v2.z)^^
+ (v2.x,v1.y,v2.z)--
+ (v1.x,v1.y,v2.z);
+}
+
+restricted path3[] unitbox=box(O,(1,1,1));
+restricted path3 unitcircle3=X..Y..-X..-Y..cycle;
+restricted path3 unitsquare3=O--X--X+Y--Y--cycle;
+
+path3 circle(triple c, real r, triple normal=Z)
+{
+ path3 p=normal == Z ? unitcircle3 : align(unit(normal))*unitcircle3;
+ return shift(c)*scale3(r)*p;
+}
+
+// return an arc centered at c from triple v1 to v2 (assuming |v2-c|=|v1-c|),
+// drawing in the given direction.
+// The normal must be explicitly specified if c and the endpoints are colinear.
+path3 arc(triple c, triple v1, triple v2, triple normal=O, bool direction=CCW)
+{
+ v1 -= c;
+ real r=abs(v1);
+ v1=unit(v1);
+ v2=unit(v2-c);
+
+ if(normal == O) {
+ normal=cross(v1,v2);
+ if(normal == O) abort("explicit normal required for these endpoints");
+ }
+
+ transform3 T;
+ bool align=normal != Z;
+ if(align) {
+ T=align(unit(normal));
+ transform3 Tinv=transpose(T);
+ v1=Tinv*v1;
+ v2=Tinv*v2;
+ }
+
+ string invalidnormal="invalid normal vector";
+ real fuzz=sqrtEpsilon;
+ if(abs(v1.z) > fuzz || abs(v2.z) > fuzz)
+ abort(invalidnormal);
+
+ real[] t1=intersect(unitcircle3,O--2*(v1.x,v1.y,0));
+ real[] t2=intersect(unitcircle3,O--2*(v2.x,v2.y,0));
+
+ if(t1.length == 0 || t2.length == 0)
+ abort(invalidnormal);
+
+ real t1=t1[0];
+ real t2=t2[0];
+ int n=length(unitcircle3);
+ if(direction) {
+ if(t1 >= t2) t1 -= n;
+ } else if(t2 >= t1) t2 -= n;
+
+ path3 p=subpath(unitcircle3,t1,t2);
+ if(align) p=T*p;
+ return shift(c)*scale3(r)*p;
+}
+
+// return an arc centered at c with radius r from c+r*dir(theta1,phi1) to
+// c+r*dir(theta2,phi2) in degrees, drawing in the given direction
+// relative to the normal vector cross(dir(theta1,phi1),dir(theta2,phi2)).
+// The normal must be explicitly specified if c and the endpoints are colinear.
+path3 arc(triple c, real r, real theta1, real phi1, real theta2, real phi2,
+ triple normal=O, bool direction)
+{
+ return arc(c,c+r*dir(theta1,phi1),c+r*dir(theta2,phi2),normal,direction);
+}
+
+// return an arc centered at c with radius r from c+r*dir(theta1,phi1) to
+// c+r*dir(theta2,phi2) in degrees, drawing drawing counterclockwise
+// relative to the normal vector cross(dir(theta1,phi1),dir(theta2,phi2))
+// iff theta2 > theta1 or (theta2 == theta1 and phi2 >= phi1).
+// The normal must be explicitly specified if c and the endpoints are colinear.
+path3 arc(triple c, real r, real theta1, real phi1, real theta2, real phi2,
+ triple normal=O)
+{
+ return arc(c,r,theta1,phi1,theta2,phi2,normal,
+ theta2 > theta1 || (theta2 == theta1 && phi2 >= phi1) ? CCW : CW);
+}
+
+private real epsilon=1000*realEpsilon;
+
+// Return a representation of the plane through point O with normal cross(u,v).
+path3 plane(triple u, triple v, triple O=O)
+{
+ return O--O+u--O+u+v--O+v--cycle;
+}
+
+// PRC/OpenGL support
+
+include three_light;
+
+void draw(frame f, path3 g, material p=currentpen, light light=nolight,
+ string name="", render render=defaultrender,
+ projection P=currentprojection);
+
+void begingroup3(frame f, string name="", render render=defaultrender,
+ triple center=O, int interaction=0)
+{
+ _begingroup3(f,name,render.compression,render.granularity,render.closed,
+ render.tessellate,render.merge == false,
+ render.merge == true,center,interaction);
+}
+
+void begingroup3(picture pic=currentpicture, string name="",
+ render render=defaultrender,
+ triple center=O, int interaction=0)
+{
+ pic.add(new void(frame f, transform3, picture pic, projection) {
+ if(is3D())
+ begingroup3(f,name,render,center,interaction);
+ if(pic != null)
+ begingroup(pic);
+ },true);
+}
+
+void endgroup3(picture pic=currentpicture)
+{
+ pic.add(new void(frame f, transform3, picture pic, projection) {
+ if(is3D())
+ endgroup3(f);
+ if(pic != null)
+ endgroup(pic);
+ },true);
+}
+
+void addPath(picture pic, path3 g, pen p)
+{
+ if(size(g) > 0)
+ pic.addBox(min(g),max(g),min3(p),max3(p));
+}
+
+include three_surface;
+include three_margins;
+
+pair min(path3 p, projection P)
+{
+ path3 q=P.T.modelview*p;
+ if(P.infinity)
+ return xypart(min(q));
+ return maxratio(q)/P.T.projection[3][2];
+}
+
+pair max(path3 p, projection P)
+{
+ path3 q=P.T.modelview*p;
+ if(P.infinity)
+ return xypart(max(q));
+ return minratio(q)/P.T.projection[3][2];
+}
+
+pair min(frame f, projection P)
+{
+ frame g=P.T.modelview*f;
+ if(P.infinity)
+ return xypart(min3(g));
+ return maxratio(g)/P.T.projection[3][2];
+}
+
+pair max(frame f, projection P)
+{
+ frame g=P.T.modelview*f;
+ if(P.infinity)
+ return xypart(max3(g));
+ return minratio(g)/P.T.projection[3][2];
+}
+
+void draw(picture pic=currentpicture, Label L="", path3 g,
+ align align=NoAlign, material p=currentpen, margin3 margin=NoMargin3,
+ light light=nolight, string name="", render render=defaultrender)
+{
+ pen q=(pen) p;
+ pic.add(new void(frame f, transform3 t, picture pic, projection P) {
+ path3 G=margin(t*g,q).g;
+ if(is3D()) {
+ draw(f,G,p,light,name,render,null);
+ if(pic != null && size(G) > 0)
+ pic.addBox(min(G,P),max(G,P),min(q),max(q));
+ }
+ if(pic != null)
+ draw(pic,project(G,P),q);
+ },true);
+ Label L=L.copy();
+ L.align(align);
+ if(L.s != "") {
+ L.p(q);
+ label(pic,L,g);
+ }
+ addPath(pic,g,q);
+}
+
+include three_tube;
+
+draw=new void(frame f, path3 g, material p=currentpen,
+ light light=nolight, string name="",
+ render render=defaultrender,
+ projection P=currentprojection) {
+ pen q=(pen) p;
+ if(is3D()) {
+ p=material(p);
+ real width=linewidth(q);
+ void drawthick(path3 g) {
+ if(settings.thick) {
+ if(width > 0) {
+ bool prc=prc();
+ void cylinder(transform3) {};
+ void sphere(transform3, bool half) {};
+ void disk(transform3) {};
+ void pipe(path3, path3);
+ if(prc) {
+ cylinder=new void(transform3 t) {drawPRCcylinder(f,t,p,light);};
+ sphere=new void(transform3 t, bool half)
+ {drawPRCsphere(f,t,half,p,light,render);};
+ disk=new void(transform3 t) {draw(f,t*unitdisk,p,light,render);};
+ pipe=new void(path3 center, path3 g)
+ {drawPRCtube(f,center,g,p,light);};
+ }
+ real linecap=linecap(q);
+ real r=0.5*width;
+ bool open=!cyclic(g);
+ int L=length(g);
+ triple g0=point(g,0);
+ triple gL=point(g,L);
+ if(open && L > 0) {
+ if(linecap == 2) {
+ g0 -= r*dir(g,0);
+ gL += r*dir(g,L);
+ g=g0..g..gL;
+ L += 2;
+ }
+ }
+ tube T=tube(g,width,render,cylinder,sphere,pipe);
+ path3 c=T.center;
+ if(L >= 0) {
+ if(open) {
+ int Lc=length(c);
+ triple c0=point(c,0);
+ triple cL=point(c,Lc);
+ triple dir0=dir(g,0);
+ triple dirL=dir(g,L);
+ triple dirc0=dir(c,0);
+ triple dircL=dir(c,Lc);
+ transform3 t0=shift(g0)*align(-dir0);
+ transform3 tL=shift(gL)*align(dirL);
+ transform3 tc0=shift(c0)*align(-dirc0);
+ transform3 tcL=shift(cL)*align(dircL);
+ if(linecap == 0 || linecap == 2) {
+ transform3 scale2r=scale(r,r,1);
+ T.s.append(t0*scale2r*unitdisk);
+ disk(tc0*scale2r);
+ if(L > 0) {
+ T.s.append(tL*scale2r*unitdisk);
+ disk(tcL*scale2r);
+ }
+ } else if(linecap == 1) {
+ transform3 scale3r=scale3(r);
+ T.s.append(t0*scale3r*
+ (dir0 != O ? unithemisphere : unitsphere));
+ sphere(tc0*scale3r,half=straight(c,0));
+ if(L > 0) {
+ T.s.append(tL*scale3r*
+ (dirL != O ? unithemisphere : unitsphere));
+ sphere(tcL*scale3r,half=straight(c,Lc-1));
+ }
+ }
+ }
+ if(opacity(q) == 1)
+ _draw(f,c,q);
+ }
+ for(patch s : T.s.s)
+ draw3D(f,s,p,light,prc=false);
+ } else _draw(f,g,q);
+ } else _draw(f,g,q);
+ }
+ bool group=q != nullpen && (name != "" || render.defaultnames);
+ if(group)
+ begingroup3(f,name == "" ? "curve" : name,render);
+ if(linetype(q).length == 0) drawthick(g);
+ else {
+ real[] dash=linetype(adjust(q,arclength(g),cyclic(g)));
+ if(sum(dash) > 0) {
+ dash.cyclic=true;
+ real offset=offset(q);
+ real L=arclength(g);
+ int i=0;
+ real l=offset;
+ while(l <= L) {
+ real t1=arctime(g,l);
+ l += dash[i];
+ real t2=arctime(g,min(l,L));
+ drawthick(subpath(g,t1,t2));
+ ++i;
+ l += dash[i];
+ ++i;
+ }
+ }
+ }
+ if(group)
+ endgroup3(f);
+ } else draw(f,project(g,P),q);
+};
+
+void draw(frame f, explicit path3[] g, material p=currentpen,
+ light light=nolight, string name="",
+ render render=defaultrender, projection P=currentprojection)
+{
+ bool group=g.length > 1 && (name != "" || render.defaultnames);
+ if(group)
+ begingroup3(f,name == "" ? "curves" : name,render);
+ for(int i=0; i < g.length; ++i)
+ draw(f,g[i],p,light,partname(i,render),render,P);
+ if(group)
+ endgroup3(f);
+}
+
+void draw(picture pic=currentpicture, explicit path3[] g,
+ material p=currentpen, margin3 margin=NoMargin3, light light=nolight,
+ string name="", render render=defaultrender)
+{
+ bool group=g.length > 1 && (name != "" || render.defaultnames);
+ if(group)
+ begingroup3(pic,name == "" ? "curves" : name,render);
+ for(int i=0; i < g.length; ++i)
+ draw(pic,g[i],p,margin,light,partname(i,render),render);
+ if(group)
+ endgroup3(pic);
+}
+
+include three_arrows;
+
+void draw(picture pic=currentpicture, Label L="", path3 g,
+ align align=NoAlign, material p=currentpen, arrowbar3 arrow,
+ arrowbar3 bar=None, margin3 margin=NoMargin3, light light=nolight,
+ light arrowheadlight=currentlight, string name="",
+ render render=defaultrender)
+{
+ bool group=arrow != None || bar != None;
+ if(group)
+ begingroup3(pic,name,render);
+ bool drawpath=arrow(pic,g,p,margin,light,arrowheadlight);
+ if(bar(pic,g,p,margin,light,arrowheadlight) && drawpath)
+ draw(pic,L,g,align,p,margin,light,render);
+ if(group)
+ endgroup3(pic);
+ if(L.s != "")
+ label(pic,L,g,align,(pen) p);
+}
+
+void draw(frame f, path3 g, material p=currentpen, arrowbar3 arrow,
+ light light=nolight, light arrowheadlight=currentlight,
+ string name="", render render=defaultrender,
+ projection P=currentprojection)
+{
+ picture pic;
+ bool group=arrow != None;
+ if(group)
+ begingroup3(f,name,render);
+ if(arrow(pic,g,p,NoMargin3,light,arrowheadlight))
+ draw(f,g,p,light,render,P);
+ add(f,pic.fit());
+ if(group)
+ endgroup3(f);
+}
+
+void add(picture pic=currentpicture, void d(picture,transform3),
+ bool exact=false)
+{
+ pic.add(d,exact);
+}
+
+// Fit the picture src using the identity transformation (so user
+// coordinates and truesize coordinates agree) and add it about the point
+// position to picture dest.
+void add(picture dest, picture src, triple position, bool group=true,
+ bool above=true)
+{
+ dest.add(new void(picture f, transform3 t) {
+ f.add(shift(t*position)*src,group,above);
+ });
+}
+
+void add(picture src, triple position, bool group=true, bool above=true)
+{
+ add(currentpicture,src,position,group,above);
+}
+
+// Align an arrow pointing to b from the direction dir. The arrow is
+// 'length' PostScript units long.
+void arrow(picture pic=currentpicture, Label L="", triple b, triple dir,
+ real length=arrowlength, align align=NoAlign,
+ pen p=currentpen, arrowbar3 arrow=Arrow3, margin3 margin=EndMargin3,
+ light light=nolight, light arrowheadlight=currentlight,
+ string name="", render render=defaultrender)
+{
+ Label L=L.copy();
+ if(L.defaultposition) L.position(0);
+ L.align(L.align,dir);
+ L.p(p);
+ picture opic;
+ marginT3 margin=margin(b--b,p); // Extract margin.begin and margin.end
+ triple a=(margin.begin+length+margin.end)*unit(dir);
+ draw(opic,L,a--O,align,p,arrow,margin,light,arrowheadlight,name,render);
+ add(pic,opic,b);
+}
+
+void arrow(picture pic=currentpicture, Label L="", triple b, pair dir,
+ real length=arrowlength, align align=NoAlign,
+ pen p=currentpen, arrowbar3 arrow=Arrow3, margin3 margin=EndMargin3,
+ light light=nolight, light arrowheadlight=currentlight,
+ string name="", render render=defaultrender,
+ projection P=currentprojection)
+{
+ arrow(pic,L,b,invert(dir,b,P),length,align,p,arrow,margin,light,
+ arrowheadlight,name,render);
+}
+
+triple min3(picture pic, projection P=currentprojection)
+{
+ return pic.min3(P);
+}
+
+triple max3(picture pic, projection P=currentprojection)
+{
+ return pic.max3(P);
+}
+
+triple size3(picture pic, bool user=false, projection P=currentprojection)
+{
+ transform3 t=pic.calculateTransform3(P);
+ triple M=pic.max(t);
+ triple m=pic.min(t);
+ if(!user) return M-m;
+ t=inverse(t);
+ return t*M-t*m;
+}
+
+triple point(frame f, triple dir)
+{
+ triple m=min3(f);
+ triple M=max3(f);
+ return m+realmult(rectify(dir),M-m);
+}
+
+triple point(picture pic=currentpicture, triple dir, bool user=true,
+ projection P=currentprojection)
+{
+ triple min = pic.userMin(), max = pic.userMax();
+ triple v=min+realmult(rectify(dir),max-min);
+ return user ? v : pic.calculateTransform3(P)*v;
+}
+
+triple truepoint(picture pic=currentpicture, triple dir, bool user=true,
+ projection P=currentprojection)
+{
+ transform3 t=pic.calculateTransform3(P);
+ triple m=pic.min(t);
+ triple M=pic.max(t);
+ triple v=m+realmult(rectify(dir),M-m);
+ return user ? inverse(t)*v : v;
+}
+
+void add(picture dest=currentpicture, object src, pair position=0, pair align=0,
+ bool group=true, filltype filltype=NoFill, bool above=true)
+{
+ if(prc())
+ label(dest,src,position,align);
+ else if(settings.render == 0)
+ plain.add(dest,src,position,align,group,filltype,above);
+}
+
+private struct viewpoint {
+ triple target,camera,up;
+ real angle;
+ void operator init(string s) {
+ s=replace(s,'\n'," ");
+ string[] S=split(s);
+ int pos(string s, string key) {
+ int pos=find(s,key);
+ if(pos < 0) return -1;
+ pos += length(key);
+ while(substr(s,pos,1) == " ") ++pos;
+ if(substr(s,pos,1) == "=")
+ return pos+1;
+ return -1;
+ }
+ triple C2C=X;
+ real ROO=1;
+ real ROLL=0;
+ angle=30;
+ int pos;
+ for(int k=0; k < S.length; ++k) {
+ if((pos=pos(S[k],"COO")) >= 0)
+ target=((real) substr(S[k],pos),(real) S[++k],(real) S[++k]);
+ else if((pos=pos(S[k],"C2C")) >= 0)
+ C2C=((real) substr(S[k],pos),(real) S[++k],(real) S[++k]);
+ else if((pos=pos(S[k],"ROO")) >= 0)
+ ROO=(real) substr(S[k],pos);
+ else if((pos=pos(S[k],"ROLL")) >= 0)
+ ROLL=(real) substr(S[k],pos);
+ else if((pos=pos(S[k],"AAC")) >= 0)
+ angle=(real) substr(S[k],pos);
+ }
+ camera=target+ROO*C2C;
+ triple u=unit(target-camera);
+ triple w=unit(Z-u.z*u);
+ up=rotate(ROLL,O,u)*w;
+ }
+}
+
+projection perspective(string s)
+{
+ viewpoint v=viewpoint(s);
+ projection P=perspective(v.camera,v.up,v.target);
+ P.angle=v.angle;
+ P.absolute=true;
+ return P;
+}
+
+projection absorthographic(triple camera=Z, triple target=O, real roll=0)
+{
+ triple u=unit(target-camera);
+ triple w=unit(Z-u.z*u);
+ triple up=rotate(roll,O,u)*w;
+ projection P=
+ projection(camera,up,target,1,0,false,false,
+ new transformation(triple camera, triple up, triple target)
+ {return transformation(look(camera,up,target));});
+ P.absolute=true;
+ return P;
+}
+
+projection absperspective(triple camera=Z, triple target=O, real roll=0,
+ real angle=30)
+{
+ triple u=unit(target-camera);
+ triple w=unit(Z-u.z*u);
+ triple up=rotate(roll,O,u)*w;
+ projection P=perspective(camera,up,target);
+ P.angle=angle;
+ P.absolute=true;
+ return P;
+}
+
+private string Format(real x)
+{
+ assert(abs(x) < 1e17,"Number too large: "+string(x));
+ return format("%.9f",x,"C");
+}
+
+private string Format(triple v, string sep=" ")
+{
+ return Format(v.x)+sep+Format(v.y)+sep+Format(v.z);
+}
+
+private string Format(real[] c)
+{
+ return Format((c[0],c[1],c[2]));
+}
+
+private string format(triple v, string sep=" ")
+{
+ return string(v.x)+sep+string(v.y)+sep+string(v.z);
+}
+
+private string Format(transform3 t, string sep=" ")
+{
+ return
+ Format(t[0][0])+sep+Format(t[1][0])+sep+Format(t[2][0])+sep+
+ Format(t[0][1])+sep+Format(t[1][1])+sep+Format(t[2][1])+sep+
+ Format(t[0][2])+sep+Format(t[1][2])+sep+Format(t[2][2])+sep+
+ Format(t[0][3])+sep+Format(t[1][3])+sep+Format(t[2][3]);
+}
+
+void writeJavaScript(string name, string preamble, string script)
+{
+ file out=output(name);
+ write(out,preamble);
+ if(script != "") {
+ write(out,endl);
+ file in=input(script);
+ while(true) {
+ string line=in;
+ if(eof(in)) break;
+ write(out,line,endl);
+ }
+ }
+ close(out);
+ if(settings.verbose > 1) write("Wrote "+name);
+ if(!settings.inlinetex)
+ file3.push(name);
+}
+
+pair viewportmargin(pair lambda)
+{
+ return maxbound(0.5*(viewportsize-lambda),viewportmargin);
+}
+
+string embed3D(string prefix, string label=prefix, string text=label,
+ frame f, string format="",
+ real width=0, real height=0,
+ string options="", string script="",
+ light light=currentlight, projection P=currentprojection,
+ real viewplanesize=0)
+{
+ if(!prc(format) || Embed == null) return "";
+
+ if(width == 0) width=settings.paperwidth;
+ if(height == 0) height=settings.paperheight;
+
+ if(script == "") script=defaultembed3Dscript;
+
+ if(P.infinity) {
+ if(viewplanesize==0) {
+ triple lambda=max3(f)-min3(f);
+ pair margin=viewportmargin((lambda.x,lambda.y));
+ viewplanesize=(max(lambda.x+2*margin.x,lambda.y+2*margin.y))/P.zoom;
+ }
+ } else
+ if(!P.absolute) P.angle=2*aTan(Tan(0.5*P.angle));
+
+ shipout3(prefix,f);
+
+ string name=prefix+".js";
+
+ if(!settings.inlinetex && !prconly())
+ file3.push(prefix+".prc");
+
+ static transform3 flipxz=xscale3(-1)*zscale3(-1);
+ transform3 inv=inverse(flipxz*P.T.modelview);
+
+ string options3="3Dlights="+
+ (light.on() ? "Headlamp" : "None");
+ if(defaultembed3Doptions != "") options3 += ","+defaultembed3Doptions;
+
+ if((settings.render < 0 || !settings.embed) && settings.auto3D)
+ options3 += ",activate=pagevisible";
+ options3 += ",3Dtoolbar="+(settings.toolbar ? "true" : "false")+
+ ",label="+label+
+ (P.infinity ? ",3Dortho="+Format(1/viewplanesize) :
+ ",3Daac="+Format(P.angle))+
+ ",3Dc2w="+Format(inv)+
+ ",3Droo="+Format(abs(P.vector()))+
+ ",3Dpsob="+(P.infinity ? "Max" : "H")+
+ ",3Dbg="+Format(light.background());
+ if(options != "") options3 += ","+options;
+ if(settings.inlinetex)
+ prefix=jobname(prefix);
+ options3 += ",add3Djscript=asylabels.js";
+
+ return text == "" ? Embed(prefix+".prc","",options3,width,height) :
+ "\hbox to 0pt{"+text+"\hss}"+Embed(prefix+".prc","\phantom{"+text+"}",
+ options3);
+}
+
+struct scene
+{
+ frame f;
+ transform3 t;
+ projection P;
+ bool adjusted;
+ real width,height;
+ pair viewportmargin;
+ transform3 T=identity4;
+ picture pic2;
+
+ void operator init(frame f, real width, real height,
+ projection P=currentprojection) {
+ this.f=f;
+ this.t=identity4;
+ this.P=P;
+ this.width=width;
+ this.height=height;
+ }
+
+ void operator init(picture pic, real xsize=pic.xsize, real ysize=pic.ysize,
+ bool keepAspect=pic.keepAspect, bool is3D=true,
+ projection P=currentprojection) {
+ real xsize3=pic.xsize3, ysize3=pic.ysize3, zsize3=pic.zsize3;
+ bool warn=true;
+
+ if(xsize3 == 0 && ysize3 == 0 && zsize3 == 0) {
+ xsize3=ysize3=zsize3=max(xsize,ysize);
+ warn=false;
+ }
+
+ if(P.absolute)
+ this.P=P.copy();
+ else if(P.showtarget)
+ draw(pic,P.target,nullpen);
+
+ t=pic.scaling(xsize3,ysize3,zsize3,keepAspect,warn);
+ adjusted=false;
+ triple m=pic.min(t);
+ triple M=pic.max(t);
+
+ if(!P.absolute) {
+ this.P=t*P;
+ if(this.P.center && settings.render != 0) {
+ triple target=0.5*(m+M);
+ this.P.target=target;
+ this.P.calculate();
+ }
+ if(this.P.autoadjust || this.P.infinity)
+ adjusted=adjusted | this.P.adjust(m,M);
+ }
+
+ bool scale=xsize != 0 || ysize != 0;
+ bool scaleAdjust=scale && this.P.autoadjust;
+ bool noAdjust=(this.P.absolute || !scaleAdjust);
+
+ if(pic.bounds3.exact && noAdjust)
+ this.P.bboxonly=false;
+
+ f=pic.fit3(t,pic.bounds3.exact ? pic2 : null,this.P);
+
+ if(!pic.bounds3.exact) {
+ if(noAdjust)
+ this.P.bboxonly=false;
+
+ transform3 s=pic.scale3(f,xsize3,ysize3,zsize3,keepAspect);
+ t=s*t;
+ this.P=s*this.P;
+ f=pic.fit3(t,pic2,this.P);
+ }
+
+ if(is3D || scale) {
+ pic2.bounds.exact=true;
+ transform s=pic2.scaling(xsize,ysize,keepAspect);
+
+ pair m2=pic2.min(s);
+ pair M2=pic2.max(s);
+ pair lambda=M2-m2;
+ viewportmargin=viewportmargin(lambda);
+ width=ceil(lambda.x+2*viewportmargin.x);
+ height=ceil(lambda.y+2*viewportmargin.y);
+
+ if(!this.P.absolute) {
+ if(scaleAdjust) {
+ pair v=(s.xx,s.yy);
+ transform3 T=this.P.t;
+ pair x=project(X,T);
+ pair y=project(Y,T);
+ pair z=project(Z,T);
+ real f(pair a, pair b) {
+ return b == 0 ? (0.5*(a.x+a.y)) :
+ (b.x^2*a.x+b.y^2*a.y)/(b.x^2+b.y^2);
+ }
+ transform3 s=keepAspect ? scale3(min(f(v,x),f(v,y),f(v,z))) :
+ xscale3(f(v,x))*yscale3(f(v,y))*zscale3(f(v,z));
+ s=shift(this.P.target)*s*shift(-this.P.target);
+ t=s*t;
+ this.P=s*this.P;
+ this.P.bboxonly=false;
+ if(!is3D) pic2.erase();
+ f=pic.fit3(t,is3D ? null : pic2,this.P);
+ }
+
+ if(this.P.autoadjust || this.P.infinity)
+ adjusted=adjusted | this.P.adjust(min3(f),max3(f));
+ }
+ }
+ }
+
+ // Choose the angle to be just large enough to view the entire image.
+ real angle(projection P) {
+ T=identity4;
+ real h=-0.5*P.target.z;
+ pair r,R;
+ real diff=realMax;
+ pair s;
+ int i;
+ do {
+ r=minratio(f);
+ R=maxratio(f);
+ pair lasts=s;
+ if(P.autoadjust) {
+ s=r+R;
+ if(s != 0) {
+ transform3 t=shift(h*s.x,h*s.y,0);
+ f=t*f;
+ T=t*T;
+ adjusted=true;
+ }
+ }
+ diff=abs(s-lasts);
+ ++i;
+ } while (diff > angleprecision && i < maxangleiterations);
+ real aspect=width > 0 ? height/width : 1;
+ real rx=-r.x*aspect;
+ real Rx=R.x*aspect;
+ real ry=-r.y;
+ real Ry=R.y;
+ if(!P.autoadjust) {
+ if(rx > Rx) Rx=rx;
+ else rx=Rx;
+ if(ry > Ry) Ry=ry;
+ else ry=Ry;
+ }
+ return (1+angleprecision)*max(aTan(rx)+aTan(Rx),aTan(ry)+aTan(Ry));
+ }
+}
+
+object embed(string prefix=outprefix(), string label=prefix,
+ string text=label, scene S, string format="", bool view=true,
+ string options="", string script="", light light=currentlight)
+{
+ object F;
+ transform3 modelview;
+ projection P=S.P;
+ transform3 tinv=inverse(S.t);
+
+ projection Q;
+ triple orthoshift;
+ modelview=P.T.modelview;
+ transform3 inv;
+ if(P.absolute) {
+ Q=modelview*P;
+ inv=inverse(modelview);
+ } else {
+ triple target=P.target;
+ S.f=modelview*S.f;
+ P=modelview*P;
+ Q=P.copy();
+
+ if(Q.t[2][3] == -1) // PRC can't handle oblique projections
+ Q=orthographic(P.camera,P.up,P.target,P.zoom,P.viewportshift,
+ P.showtarget,P.center);
+ if(P.infinity) {
+ triple m=min3(S.f);
+ triple M=max3(S.f);
+ triple lambda=M-m;
+ S.viewportmargin=viewportmargin((lambda.x,lambda.y));
+ S.width=ceil(lambda.x+2*S.viewportmargin.x);
+ S.height=ceil(lambda.y+2*S.viewportmargin.y);
+ orthoshift=(-0.5(m.x+M.x),-0.5*(m.y+M.y),0);
+ S.f=shift(orthoshift)*S.f; // Eye will be at (0,0,0)
+ inv=inverse(modelview);
+ } else {
+ if(P.angle == 0) {
+ P.angle=S.angle(P);
+ modelview=S.T*modelview;
+ if(S.viewportmargin.y != 0)
+ P.angle=2*aTan(Tan(0.5*P.angle)-S.viewportmargin.y/P.target.z);
+ }
+ inv=inverse(modelview);
+ Q.angle=P.angle;
+ if(settings.verbose > 0) {
+ if(S.adjusted)
+ write("adjusting camera to ",tinv*inv*P.camera);
+ target=inv*P.target;
+ }
+ P=S.T*P;
+ }
+ if(settings.verbose > 0) {
+ if((P.center && settings.render != 0) || (!P.infinity && P.autoadjust))
+ write("adjusting target to ",tinv*target);
+ }
+ }
+ light Light=modelview*light;
+
+ if(prefix == "") prefix=outprefix();
+ bool prc=prc(format);
+ bool preview=settings.render > 0 && !prconly();
+ if(prc) {
+ // The media9.sty package cannot handle spaces or dots in filenames.
+ string dir=stripfile(prefix);
+ prefix=dir+replace(stripdirectory(prefix),
+ new string[][]{{" ","_"},{".","_"}});
+ if((settings.embed || nativeformat() == "pdf") && !prconly())
+ prefix += "+"+(string) file3.length;
+ } else
+ preview=false;
+ if(preview || (!prc && settings.render != 0)) {
+ frame f=S.f;
+ triple m,M;
+ real zcenter;
+ real r;
+ if(P.absolute) {
+ f=modelview*f;
+ m=min3(f);
+ M=max3(f);
+ r=0.5*abs(M-m);
+ zcenter=0.5*(M.z+m.z);
+ } else {
+ m=min3(f);
+ M=max3(f);
+ zcenter=P.target.z;
+ r=P.distance(m,M);
+ }
+ M=(M.x,M.y,zcenter+r);
+ m=(m.x,m.y,zcenter-r);
+
+ if(P.infinity) {
+ triple margin=(S.viewportmargin.x,S.viewportmargin.y,0);
+ M += margin;
+ m -= margin;
+ } else if(M.z >= 0) abort("camera too close");
+
+ shipout3(prefix,f,preview ? nativeformat() : format,
+ S.width-defaultrender.margin,S.height-defaultrender.margin,
+ P.infinity ? 0 : 2aTan(Tan(0.5*P.angle)*P.zoom),
+ P.zoom,m,M,P.viewportshift,
+ tinv*inv*shift(0,0,zcenter),Light.background(),Light.position,
+ Light.diffuse,Light.specular,
+ view && !preview);
+ if(!preview) return F;
+ }
+
+ string image;
+ if((preview || (prc && settings.render == 0)) && settings.embed) {
+ image=prefix;
+ if(settings.inlinetex) image += "_0";
+ if(!preview && !S.pic2.empty2()) {
+ transform T=S.pic2.scaling(S.width,S.height);
+ _shipout(image,S.pic2.fit(T),newframe,nativeformat(),false,false);
+ }
+
+ image += "."+nativeformat();
+ if(!settings.inlinetex) file3.push(image);
+ image=graphic(image,"hiresbb");
+ }
+ if(prc) {
+ if(P.viewportshift != 0) {
+ if(!P.infinity)
+ warning("offaxis",
+ "PRC does not support off-axis projections; use pan instead of
+shift");
+
+ triple lambda=max3(S.f)-min3(S.f);
+ Q.target -= (P.viewportshift.x*lambda.x/P.zoom,
+ P.viewportshift.y*lambda.y/P.zoom,0);
+ }
+
+ real viewplanesize=0;
+ if(P.absolute) {
+ if(P.infinity) {
+ S.f=modelview*S.f;
+ triple lambda=max3(S.f)-min3(S.f);
+ pair margin=viewportmargin((lambda.x,lambda.y));
+ viewplanesize=(max(lambda.x+2*margin.x,lambda.y+2*margin.y))/Q.zoom;
+ S.f=inv*S.f;
+ }
+ Q=inv*Q;
+ } else {
+ if(P.infinity) {
+ triple lambda=max3(S.f)-min3(S.f);
+ pair margin=viewportmargin((lambda.x,lambda.y));
+ viewplanesize=(max(lambda.x+2*margin.x,lambda.y+2*margin.y))/(Q.zoom);
+ transform3 t=inv*shift(-orthoshift);
+ Q=t*Q;
+ S.f=t*S.f;
+ } else {
+ Q=inv*Q;
+ S.f=inv*S.f;
+ }
+ }
+ F.L=embed3D(prefix,label,text=image,S.f,format,
+ S.width-2,S.height-2,options,script,light,Q,viewplanesize);
+ }
+ return F;
+}
+
+object embed(string prefix=outprefix(), string label=prefix,
+ string text=label, picture pic, string format="",
+ real xsize=pic.xsize, real ysize=pic.ysize,
+ bool keepAspect=pic.keepAspect, bool view=true, string options="",
+ string script="", light light=currentlight,
+ projection P=currentprojection)
+{
+ bool is3D=is3D(format);
+ scene S=scene(pic,xsize,ysize,keepAspect,is3D,P);
+ if(is3D)
+ return embed(prefix,label,text,S,format,view,options,script,light);
+ else {
+ object F;
+ transform T=S.pic2.scaling(xsize,ysize,keepAspect);
+ F.f=pic.fit(scale(S.t[0][0])*T);
+ add(F.f,S.pic2.fit());
+ return F;
+ }
+}
+
+object embed(string prefix=outprefix(), string label=prefix,
+ string text=label,
+ frame f, string format="", real width=0, real height=0,
+ bool view=true, string options="", string script="",
+ light light=currentlight, projection P=currentprojection)
+{
+ if(is3D(format))
+ return embed(label,text,prefix,scene(f,width,height,P),format,view,options,
+ script,light);
+ else {
+ object F;
+ F.f=f;
+ return F;
+ }
+}
+
+embed3=new object(string prefix, frame f, string format, string options,
+ string script, light light, projection P) {
+ return embed(prefix=prefix,f,format,options,script,light,P);
+};
+
+frame embedder(object embedder(string prefix, string format),
+ string prefix, string format, bool view, light light)
+{
+ frame f;
+ bool prc=prc(format);
+ if(!prc && settings.render != 0 && !view) {
+ static int previewcount=0;
+ bool keep=prefix != "";
+ prefix=outprefix(prefix)+"+"+(string) previewcount;
+ ++previewcount;
+ format=nativeformat();
+ if(!keep) file3.push(prefix+"."+format);
+ }
+ object F=embedder(prefix,format);
+ if(prc)
+ label(f,F.L);
+ else {
+ if(settings.render == 0) {
+ add(f,F.f);
+ if(light.background != nullpen)
+ box(f,light.background,Fill,above=false);
+ } else if(!view)
+ label(f,graphic(prefix,"hiresbb"));
+ }
+ return f;
+}
+
+currentpicture.fitter=new frame(string prefix, picture pic, string format,
+ real xsize, real ysize, bool keepAspect,
+ bool view, string options, string script,
+ light light, projection P) {
+ frame f;
+ bool empty3=pic.empty3();
+ if(!empty3) f=embedder(new object(string prefix, string format) {
+ return embed(prefix=prefix,pic,format,xsize,ysize,keepAspect,view,
+ options,script,light,P);
+ },prefix,format,view,light);
+ if(is3D(format) || empty3) add(f,pic.fit2(xsize,ysize,keepAspect));
+ return f;
+};
+
+frame embedder(string prefix, frame f, string format, real width, real height,
+ bool view, string options, string script, light light,
+ projection P)
+{
+ return embedder(new object(string prefix, string format) {
+ return embed(prefix=prefix,f,format,width,height,view,options,script,
+ light,P);
+ },prefix,format,view,light);
+}
+
+projection[][] ThreeViewsUS={{TopView},
+ {FrontView,RightView}};
+
+projection[][] SixViewsUS={{null,TopView},
+ {LeftView,FrontView,RightView,BackView},
+ {null,BottomView}};
+
+projection[][] ThreeViewsFR={{RightView,FrontView},
+ {null,TopView}};
+
+projection[][] SixViewsFR={{null,BottomView},
+ {RightView,FrontView,LeftView,BackView},
+ {null,TopView}};
+
+projection[][] ThreeViews={{FrontView,TopView,RightView}};
+
+projection[][] SixViews={{FrontView,TopView,RightView},
+ {BackView,BottomView,LeftView}};
+
+void addViews(picture dest, picture src, projection[][] views=SixViewsUS,
+ bool group=true, filltype filltype=NoFill)
+{
+ frame[][] F=array(views.length,new frame[]);
+ pair[][] M=array(views.length,new pair[]);
+ pair[][] m=array(views.length,new pair[]);
+
+ for(int i=0; i < views.length; ++i) {
+ projection[] viewsi=views[i];
+ frame[] Fi=F[i];
+ pair[] Mi=M[i];
+ pair[] mi=m[i];
+ for(projection P : viewsi) {
+ if(P != null) {
+ frame f=src.fit(P);
+ mi.push(min(f));
+ Mi.push(max(f));
+ Fi.push(f);
+ } else {
+ pair Infinity=(infinity,infinity);
+ mi.push(Infinity);
+ Mi.push(-Infinity);
+ Fi.push(newframe);
+ }
+ }
+ }
+
+ real[] my=new real[views.length];
+ real[] My=new real[views.length];
+
+ int Nj=0;
+ for(int i=0; i < views.length; ++i) {
+ my[i]=minbound(m[i]).y;
+ My[i]=maxbound(M[i]).y;
+ Nj=max(Nj,views[i].length);
+ }
+
+ real[] mx=array(Nj,infinity);
+ real[] Mx=array(Nj,-infinity);
+ for(int i=0; i < views.length; ++i) {
+ pair[] mi=m[i];
+ pair[] Mi=M[i];
+ for(int j=0; j < views[i].length; ++j) {
+ mx[j]=min(mx[j],mi[j].x);
+ Mx[j]=max(Mx[j],Mi[j].x);
+ }
+ }
+
+ if(group) begingroup(dest);
+
+ real y;
+ for(int i=0; i < views.length; ++i) {
+ real x;
+ pair[] mi=m[i];
+ for(int j=0; j < views[i].length; ++j) {
+ if(size(F[i][j]) != 0)
+ add(dest,shift(x-mx[j],y+my[i])*F[i][j],filltype);
+ x += (Mx[j]-mx[j]);
+ }
+ y -= (My[i]-my[i]);
+ }
+
+ if(group) endgroup(dest);
+}
+
+void addViews(picture src, projection[][] views=SixViewsUS, bool group=true,
+ filltype filltype=NoFill)
+{
+ addViews(currentpicture,src,views,group,filltype);
+}
+
+void addStereoViews(picture dest, picture src, bool group=true,
+ filltype filltype=NoFill, real eyetoview=defaulteyetoview,
+ bool leftright=true, projection P=currentprojection)
+{
+ triple v=P.vector();
+ triple h=0.5*abs(v)*eyetoview*unit(cross(P.up,v));
+ projection leftEye=P.copy();
+ leftEye.camera -= h;
+ leftEye.calculate();
+ projection rightEye=P.copy();
+ rightEye.camera += h;
+ rightEye.calculate();
+ addViews(dest,src,leftright ?
+ new projection[][] {{leftEye,rightEye}} :
+ new projection[][] {{rightEye,leftEye}},group,filltype);
+}
+
+void addStereoViews(picture src, bool group=true,
+ filltype filltype=NoFill,
+ real eyetoview=defaulteyetoview, bool leftright=true,
+ projection P=currentprojection)
+{
+ addStereoViews(currentpicture,src,group,filltype,eyetoview,leftright,P);
+}
+
+// Fit an array of 3D pictures simultaneously using the sizing of picture all.
+frame[] fit3(string prefix="", picture[] pictures, picture all,
+ string format="", bool view=true, string options="",
+ string script="", light light=currentlight,
+ projection P=currentprojection)
+{
+ frame[] out;
+ scene S=scene(all,P);
+ triple m=all.min(S.t);
+ triple M=all.max(S.t);
+ out=new frame[pictures.length];
+ int i=0;
+ bool reverse=settings.reverse;
+ settings.animating=true;
+
+ for(picture pic : pictures) {
+ picture pic2;
+ frame f=pic.fit3(S.t,pic2,S.P);
+ if(settings.interrupt) break;
+ add(f,pic2.fit2());
+ draw(f,m,nullpen);
+ draw(f,M,nullpen);
+ out[i]=f;
+ ++i;
+ }
+
+ while(!settings.interrupt) {
+ for(int i=settings.reverse ? pictures.length-1 : 0;
+ i >= 0 && i < pictures.length && !settings.interrupt;
+ settings.reverse ? --i : ++i) {
+ frame f=embedder(prefix,out[i],format,S.width,S.height,view,options,
+ script,light,S.P);
+ if(!settings.loop) out[i]=f;
+ }
+ if(!settings.loop) break;
+ }
+
+ settings.animating=false;
+ settings.interrupt=false;
+ settings.reverse=reverse;
+
+ return out;
+}
+
+// Fit an array of pictures simultaneously using the size of the first picture.
+fit=new frame[](string prefix="", picture[] pictures, string format="",
+ bool view=true, string options="", string script="",
+ projection P=currentprojection) {
+ if(pictures.length == 0)
+ return new frame[];
+
+ picture all;
+ size(all,pictures[0]);
+ for(picture pic : pictures)
+ add(all,pic);
+
+ return all.empty3() ? fit2(pictures,all) :
+ fit3(prefix,pictures,all,format,view,options,script,P);
+};
+
+// Add frame src to picture dest about position.
+void add(picture dest=currentpicture, frame src, triple position)
+{
+ if(is3D(src)) {
+ dest.add(new void(frame f, transform3 t, picture, projection) {
+ add(f,shift(t*position)*src);
+ },true);
+ } else {
+ dest.add(new void(frame, transform3 t, picture pic, projection P) {
+ if(pic != null) {
+ pic.add(new void(frame f, transform T) {
+ add(f,T*shift(project(t*position,P))*src);
+ },true);
+ }
+ },true);
+ }
+ dest.addBox(position,position,min3(src),max3(src));
+}
+
+exitfcn currentexitfunction=atexit();
+
+void exitfunction()
+{
+ if(currentexitfunction != null) currentexitfunction();
+ if(!settings.keep)
+ for(int i=0; i < file3.length; ++i)
+ delete(file3[i]);
+ file3=new string[];
+}
+
+atexit(exitfunction);
diff --git a/graphics/asymptote/base/three_arrows.asy b/graphics/asymptote/base/three_arrows.asy
new file mode 100644
index 0000000000..4398b9bd78
--- /dev/null
+++ b/graphics/asymptote/base/three_arrows.asy
@@ -0,0 +1,723 @@
+// A transformation that bends points along a path
+transform3 bend(path3 g, real t)
+{
+ triple dir=dir(g,t);
+ triple a=point(g,0), b=postcontrol(g,0);
+ triple c=precontrol(g,1), d=point(g,1);
+ triple dir1=b-a;
+ triple dir2=c-b;
+ triple dir3=d-c;
+
+ triple u = unit(cross(dir1,dir3));
+ real eps=1000*realEpsilon;
+ if(abs(u) < eps) {
+ u = unit(cross(dir1,dir2));
+ if(abs(u) < eps) {
+ u = unit(cross(dir2,dir3));
+ if(abs(u) < eps)
+ // linear segment: use any direction perpendicular to initial direction
+ u = perp(dir1);
+ }
+ }
+ u = unit(perp(u,dir));
+
+ triple w=cross(dir,u);
+ triple q=point(g,t);
+ return new real[][] {
+ {u.x,w.x,dir.x,q.x},
+ {u.y,w.y,dir.y,q.y},
+ {u.z,w.z,dir.z,q.z},
+ {0,0,0,1}
+ };
+}
+
+// bend a point along a path; assumes that p.z is in [0,scale]
+triple bend(triple p, path3 g, real scale)
+{
+ return bend(g,arctime(g,arclength(g)+p.z-scale))*(p.x,p.y,0);
+}
+
+void bend(surface s, path3 g, real L)
+{
+ for(patch p : s.s) {
+ for(int i=0; i < 4; ++i) {
+ for(int j=0; j < 4; ++j) {
+ p.P[i][j]=bend(p.P[i][j],g,L);
+ }
+ }
+ }
+}
+
+// Refine a noncyclic path3 g so that it approaches its endpoint in
+// geometrically spaced steps.
+path3 approach(path3 p, int n, real radix=3)
+{
+ guide3 G;
+ real L=length(p);
+ real tlast=0;
+ real r=1/radix;
+ for(int i=1; i < n; ++i) {
+ real t=L*(1-r^i);
+ G=G&subpath(p,tlast,t);
+ tlast=t;
+ }
+ return G&subpath(p,tlast,L);
+}
+
+struct arrowhead3
+{
+ arrowhead arrowhead2=DefaultHead;
+ real size(pen p)=arrowsize;
+ real arcsize(pen p)=arcarrowsize;
+ real gap=1;
+ real size;
+ bool splitpath=true;
+
+ surface head(path3 g, position position=EndPoint,
+ pen p=currentpen, real size=0, real angle=arrowangle,
+ filltype filltype=null, bool forwards=true,
+ projection P=currentprojection);
+
+ static surface surface(path3 g, position position, real size,
+ path[] h, pen p, filltype filltype,
+ triple normal, projection P) {
+ bool relative=position.relative;
+ real position=position.position.x;
+ if(relative) position=reltime(g,position);
+ path3 r=subpath(g,position,0);
+ path3 s=subpath(r,arctime(r,size),0);
+ if(filltype == null) filltype=FillDraw(p);
+ bool draw=filltype.type != filltype.Fill;
+ triple v=point(s,length(s));
+ triple N=normal == O ? P.normal : normal;
+ triple w=unit(v-point(s,0));
+ transform3 t=transform3(w,unit(cross(w,N)));
+ path3[] H=t*path3(h);
+ surface s;
+ real width=linewidth(p);
+ if(filltype != NoFill && filltype.type != filltype.UnFill &&
+ filltype.type != filltype.Draw) {
+ triple n=0.5*width*unit(t*Z);
+ s=surface(shift(n)*H,planar=true);
+ s.append(surface(shift(-n)*H,planar=true));
+ if(!draw)
+ for(path g : h)
+ s.append(shift(-n)*t*extrude(g,width*Z));
+ }
+ if(draw)
+ for(path3 g : H)
+ s.append(tube(g,width).s);
+ return shift(v)*s;
+ }
+
+ static path project(path3 g, bool forwards, projection P) {
+ path h=project(forwards ? g : reverse(g),P);
+ return shift(-point(h,length(h)))*h;
+ }
+
+ static path[] align(path H, path h) {
+ static real fuzz=1000*realEpsilon;
+ real[][] t=intersections(H,h,fuzz*max(abs(max(h)),abs(min(h))));
+ return t.length >= 2 ?
+ rotate(-degrees(point(H,t[0][0])-point(H,t[1][0]),warn=false))*H : H;
+ }
+}
+
+arrowhead3 DefaultHead3;
+DefaultHead3.head=new surface(path3 g, position position=EndPoint,
+ pen p=currentpen, real size=0,
+ real angle=arrowangle, filltype filltype=null,
+ bool forwards=true,
+ projection P=currentprojection)
+{
+ if(size == 0) size=DefaultHead3.size(p);
+ bool relative=position.relative;
+ real position=position.position.x;
+ if(relative) position=reltime(g,position);
+
+ path3 r=subpath(g,position,0);
+ path3 s=subpath(r,arctime(r,size),0);
+ int n=length(s);
+ bool straight1=n == 1 && straight(g,0);
+ real aspect=Tan(angle);
+ real width=size*aspect;
+ surface head;
+ if(straight1) {
+ triple v=point(s,0);
+ triple u=point(s,1)-v;
+ return shift(v)*align(unit(u))*scale(width,width,size)*unitsolidcone;
+ } else {
+ real remainL=size;
+ bool first=true;
+ for(int i=0; i < n; ++i) {
+ render(subpath(s,i,i+1),new void(path3 q, real) {
+ if(remainL > 0) {
+ real l=arclength(q);
+ real w=remainL*aspect;
+ surface segment=scale(w,w,l)*unitcylinder;
+ if(first) { // add base
+ first=false;
+ segment.append(scale(w,w,1)*unitdisk);
+ }
+ for(patch p : segment.s) {
+ for(int i=0; i < 4; ++i) {
+ for(int j=0; j < 4; ++j) {
+ real k=1-p.P[i][j].z/remainL;
+ p.P[i][j]=bend((k*p.P[i][j].x,k*p.P[i][j].y,p.P[i][j].z),q,l);
+ }
+ }
+ }
+ head.append(segment);
+ remainL -= l;
+ }
+ });
+ }
+ }
+ return head;
+};
+
+arrowhead3 HookHead3(real dir=arrowdir, real barb=arrowbarb)
+{
+ arrowhead3 a;
+ a.head=new surface(path3 g, position position=EndPoint,
+ pen p=currentpen, real size=0, real angle=arrowangle,
+ filltype filltype=null, bool forwards=true,
+ projection P=currentprojection) {
+ if(size == 0) size=a.size(p);
+
+ bool relative=position.relative;
+ real position=position.position.x;
+ if(relative) position=reltime(g,position);
+
+ path3 r=subpath(g,position,0);
+ path3 s=subpath(r,arctime(r,size),0);
+ bool straight1=length(s) == 1 && straight(g,0);
+ path3 H=path3(HookHead(dir,barb).head((0,0)--(0,size),p,size,angle),
+ YZplane);
+ surface head=surface(O,reverse(approach(subpath(H,1,0),7,1.5))&
+ approach(subpath(H,1,2),4,2),Z);
+
+ if(straight1) {
+ triple v=point(s,0);
+ triple u=point(s,1)-v;
+ return shift(v)*align(unit(u))*head;
+ } else {
+ bend(head,s,size);
+ return head;
+ }
+ };
+ a.arrowhead2=HookHead;
+ a.gap=0.7;
+ return a;
+}
+arrowhead3 HookHead3=HookHead3();
+
+arrowhead3 TeXHead3;
+TeXHead3.size=TeXHead.size;
+TeXHead3.arcsize=TeXHead.size;
+TeXHead3.arrowhead2=TeXHead;
+TeXHead3.head=new surface(path3 g, position position=EndPoint,
+ pen p=currentpen, real size=0, real angle=arrowangle,
+ filltype filltype=null, bool forwards=true,
+ projection P=currentprojection)
+{
+ real texsize=TeXHead3.size(p);
+ if(size == 0) size=texsize;
+ bool relative=position.relative;
+ real position=position.position.x;
+ if(relative) position=reltime(g,position);
+
+ path3 r=subpath(g,position,0);
+ path3 s=subpath(r,arctime(r,size),0);
+ bool straight1=length(s) == 1 && straight(g,0);
+
+ surface head=surface(O,approach(subpath(path3(TeXHead.head((0,0)--(0,1),p,
+ size),
+ YZplane),5,0),8,1.5),Z);
+ if(straight1) {
+ triple v=point(s,0);
+ triple u=point(s,1)-v;
+ return shift(v)*align(unit(u))*head;
+ } else {
+ path3 s=subpath(r,arctime(r,size/texsize*arrowsize(p)),0);
+ bend(head,s,size);
+ return head;
+ }
+};
+
+path3 arrowbase(path3 r, triple y, real t, real size)
+{
+ triple perp=2*size*perp(dir(r,t));
+ return size == 0 ? y : y+perp--y-perp;
+}
+
+arrowhead3 DefaultHead2(triple normal=O) {
+ arrowhead3 a;
+ a.head=new surface(path3 g, position position=EndPoint,
+ pen p=currentpen, real size=0,
+ real angle=arrowangle,
+ filltype filltype=null, bool forwards=true,
+ projection P=currentprojection) {
+ if(size == 0) size=a.size(p);
+ path h=a.project(g,forwards,P);
+ a.size=min(size,arclength(h));
+ path[] H=a.align(DefaultHead.head(h,p,size,angle),h);
+ H=forwards ? yscale(-1)*H : H;
+ return a.surface(g,position,size,H,p,filltype,normal,P);
+ };
+ a.gap=1.005;
+ return a;
+}
+arrowhead3 DefaultHead2=DefaultHead2();
+
+arrowhead3 HookHead2(real dir=arrowdir, real barb=arrowbarb, triple normal=O)
+{
+ arrowhead3 a;
+ a.head=new surface(path3 g, position position=EndPoint,
+ pen p=currentpen, real size=0, real angle=arrowangle,
+ filltype filltype=null, bool forwards=true,
+ projection P=currentprojection) {
+ if(size == 0) size=a.size(p);
+ path h=a.project(g,forwards,P);
+ a.size=min(size,arclength(h));
+ path[] H=a.align(HookHead.head(h,p,size,angle),h);
+ H=forwards ? yscale(-1)*H : H;
+ return a.surface(g,position,size,H,p,filltype,normal,P);
+ };
+ a.arrowhead2=HookHead;
+ a.gap=1.005;
+ return a;
+}
+arrowhead3 HookHead2=HookHead2();
+
+arrowhead3 TeXHead2(triple normal=O) {
+ arrowhead3 a;
+ a.head=new surface(path3 g, position position=EndPoint,
+ pen p=currentpen, real size=0,
+ real angle=arrowangle, filltype filltype=null,
+ bool forwards=true, projection P=currentprojection) {
+ if(size == 0) size=a.size(p);
+ path h=a.project(g,forwards,P);
+ a.size=min(size,arclength(h));
+ h=rotate(-degrees(dir(h,length(h)),warn=false))*h;
+ path[] H=TeXHead.head(h,p,size,angle);
+ H=forwards ? yscale(-1)*H : H;
+ return a.surface(g,position,size,H,p,
+ filltype == null ? TeXHead.defaultfilltype(p) : filltype,
+ normal,P);
+ };
+ a.arrowhead2=TeXHead;
+ a.size=TeXHead.size;
+ a.splitpath=false;
+ a.gap=1.005;
+ return a;
+}
+arrowhead3 TeXHead2=TeXHead2();
+
+private real position(position position, real size, path3 g, bool center)
+{
+ bool relative=position.relative;
+ real position=position.position.x;
+ if(relative) {
+ position *= arclength(g);
+ if(center) position += 0.5*size;
+ position=arctime(g,position);
+ } else if(center)
+ position=arctime(g,arclength(subpath(g,0,position))+0.5*size);
+ return position;
+}
+
+void drawarrow(picture pic, arrowhead3 arrowhead=DefaultHead3,
+ path3 g, material p=currentpen, material arrowheadpen=nullpen,
+ real size=0, real angle=arrowangle, position position=EndPoint,
+ filltype filltype=null, bool forwards=true,
+ margin3 margin=NoMargin3, bool center=false, light light=nolight,
+ light arrowheadlight=currentlight,
+ projection P=currentprojection)
+{
+ pen q=(pen) p;
+ if(filltype != null) {
+ if(arrowheadpen == nullpen && filltype != null)
+ arrowheadpen=filltype.fillpen;
+ if(arrowheadpen == nullpen && filltype != null)
+ arrowheadpen=filltype.drawpen;
+ }
+ if(arrowheadpen == nullpen) arrowheadpen=p;
+ if(size == 0) size=arrowhead.size(q);
+ size=min(arrowsizelimit*arclength(g),size);
+ real position=position(position,size,g,center);
+
+ g=margin(g,q).g;
+ int L=length(g);
+ if(!forwards) {
+ g=reverse(g);
+ position=L-position;
+ }
+ path3 r=subpath(g,position,0);
+ size=min(arrowsizelimit*arclength(r),size);
+ surface head=arrowhead.head(g,position,q,size,angle,filltype,forwards,P);
+ if(arrowhead.size > 0) size=arrowhead.size;
+ bool endpoint=position > L-sqrtEpsilon;
+ if(arrowhead.splitpath || endpoint) {
+ if(position > 0) {
+ real Size=size*arrowhead.gap;
+ draw(pic,subpath(r,arctime(r,Size),length(r)),p,light);
+ }
+ if(!endpoint)
+ draw(pic,subpath(g,position,L),p,light);
+ } else draw(pic,g,p,light);
+ draw(pic,head,arrowheadpen,arrowheadlight);
+}
+
+void drawarrow2(picture pic, arrowhead3 arrowhead=DefaultHead3,
+ path3 g, material p=currentpen, material arrowheadpen=nullpen,
+ real size=0, real angle=arrowangle, filltype filltype=null,
+ margin3 margin=NoMargin3, light light=nolight,
+ light arrowheadlight=currentlight,
+ projection P=currentprojection)
+{
+ pen q=(pen) p;
+ if(filltype != null) {
+ if(arrowheadpen == nullpen && filltype != null)
+ arrowheadpen=filltype.fillpen;
+ if(arrowheadpen == nullpen && filltype != null)
+ arrowheadpen=filltype.drawpen;
+ }
+ if(arrowheadpen == nullpen) arrowheadpen=p;
+ if(size == 0) size=arrowhead.size(q);
+ g=margin(g,q).g;
+ size=min(arrow2sizelimit*arclength(g),size);
+
+ path3 r=reverse(g);
+ int L=length(g);
+ real Size=size*arrowhead.gap;
+ draw(pic,subpath(r,arctime(r,Size),L-arctime(g,Size)),p,light);
+ draw(pic,arrowhead.head(g,L,q,size,angle,filltype,forwards=true,P),
+ arrowheadpen,arrowheadlight);
+ draw(pic,arrowhead.head(r,L,q,size,angle,filltype,forwards=false,P),
+ arrowheadpen,arrowheadlight);
+}
+
+// Add to picture an estimate of the bounding box contribution of arrowhead
+// using the local slope at endpoint.
+void addArrow(picture pic, arrowhead3 arrowhead, path3 g, pen p, real size,
+ real angle, filltype filltype, real position)
+{
+ triple v=point(g,position);
+ path3 g=v-(size+linewidth(p))*dir(g,position)--v;
+ surface s=arrowhead.head(g,position,p,size,angle);
+ if(s.s.length > 0) {
+ pic.addPoint(v,min(s)-v);
+ pic.addPoint(v,max(s)-v);
+ } else pic.addPoint(v);
+}
+
+picture arrow(arrowhead3 arrowhead=DefaultHead3,
+ path3 g, material p=currentpen, material arrowheadpen=p,
+ real size=0, real angle=arrowangle,
+ filltype filltype=null, position position=EndPoint,
+ bool forwards=true, margin3 margin=NoMargin3,
+ bool center=false, light light=nolight,
+ light arrowheadlight=currentlight)
+{
+ pen q=(pen) p;
+ if(size == 0) size=arrowhead.size(q);
+ picture pic;
+ if(is3D())
+ pic.add(new void(frame f, transform3 t, picture pic2, projection P) {
+ picture opic;
+ drawarrow(opic,arrowhead,t*g,p,arrowheadpen,size,angle,position,
+ filltype,forwards,margin,center,light,arrowheadlight,P);
+ add(f,opic.fit3(identity4,pic2,P));
+ });
+
+ addPath(pic,g,q);
+
+ real position=position(position,size,g,center);
+ path3 G;
+ if(!forwards) {
+ G=reverse(g);
+ position=length(g)-position;
+ } else G=g;
+ addArrow(pic,arrowhead,G,q,size,angle,filltype,position);
+
+ return pic;
+}
+
+picture arrow2(arrowhead3 arrowhead=DefaultHead3,
+ path3 g, material p=currentpen, material arrowheadpen=p,
+ real size=0, real angle=arrowangle, filltype filltype=null,
+ margin3 margin=NoMargin3, light light=nolight,
+ light arrowheadlight=currentlight)
+{
+ pen q=(pen) p;
+ if(size == 0) size=arrowhead.size(q);
+ picture pic;
+
+ if(is3D())
+ pic.add(new void(frame f, transform3 t, picture pic2, projection P) {
+ picture opic;
+ drawarrow2(opic,arrowhead,t*g,p,arrowheadpen,size,angle,filltype,
+ margin,light,arrowheadlight,P);
+ add(f,opic.fit3(identity4,pic2,P));
+ });
+
+ addPath(pic,g,q);
+
+ int L=length(g);
+ addArrow(pic,arrowhead,g,q,size,angle,filltype,L);
+ addArrow(pic,arrowhead,reverse(g),q,size,angle,filltype,L);
+
+ return pic;
+}
+
+void add(picture pic, arrowhead3 arrowhead, real size, real angle,
+ filltype filltype, position position, material arrowheadpen,
+ path3 g, material p, bool forwards=true, margin3 margin,
+ bool center=false, light light, light arrowheadlight)
+{
+ add(pic,arrow(arrowhead,g,p,arrowheadpen,size,angle,filltype,position,
+ forwards,margin,center,light,arrowheadlight));
+ if(!is3D()) {
+ pic.add(new void(frame f, transform3 t, picture pic, projection P) {
+ if(pic != null) {
+ pen q=(pen) p;
+ path3 G=t*g;
+ marginT3 m=margin(G,q);
+ add(pic,arrow(arrowhead.arrowhead2,project(G,P),q,size,angle,
+ filltype == null ?
+ arrowhead.arrowhead2.defaultfilltype
+ ((pen) arrowheadpen) : filltype,position,
+ forwards,TrueMargin(m.begin,m.end),center));
+ }
+ },true);
+ }
+}
+
+void add2(picture pic, arrowhead3 arrowhead, real size, real angle,
+ filltype filltype, material arrowheadpen, path3 g, material p,
+ margin3 margin, light light, light arrowheadlight)
+{
+ add(pic,arrow2(arrowhead,g,p,arrowheadpen,size,angle,filltype,margin,light,
+ arrowheadlight));
+ if(!is3D()) {
+ pic.add(new void(frame f, transform3 t, picture pic, projection P) {
+ if(pic != null) {
+ pen q=(pen) p;
+ path3 G=t*g;
+ marginT3 m=margin(G,q);
+ add(pic,arrow2(arrowhead.arrowhead2,project(G,P),q,size,angle,
+ filltype == null ?
+ arrowhead.arrowhead2.defaultfilltype
+ ((pen) arrowheadpen) : filltype,
+ TrueMargin(m.begin,m.end)));
+ }
+ },true);
+ }
+}
+
+void bar(picture pic, triple a, triple d, triple perp=O,
+ material p=currentpen, light light=nolight)
+{
+ d *= 0.5;
+ perp *= 0.5;
+ pic.add(new void(frame f, transform3 t, picture pic2, projection P) {
+ picture opic;
+ triple A=t*a;
+ triple v=d == O ? abs(perp)*unit(cross(P.normal,perp)) : d;
+ draw(opic,A-v--A+v,p,light);
+ add(f,opic.fit3(identity4,pic2,P));
+ });
+ triple v=d == O ? cross(currentprojection.normal,perp) : d;
+ pen q=(pen) p;
+ triple m=min3(q);
+ triple M=max3(q);
+ pic.addPoint(a,-v-m);
+ pic.addPoint(a,-v+m);
+ pic.addPoint(a,v-M);
+ pic.addPoint(a,v+M);
+}
+
+picture bar(triple a, triple dir, triple perp=O, material p=currentpen)
+{
+ picture pic;
+ bar(pic,a,dir,perp,p);
+ return pic;
+}
+
+typedef bool arrowbar3(picture, path3, material, margin3, light, light);
+
+bool Blank(picture, path3, material, margin3, light, light)
+{
+ return false;
+}
+
+bool None(picture, path3, material, margin3, light, light)
+{
+ return true;
+}
+
+arrowbar3 BeginArrow3(arrowhead3 arrowhead=DefaultHead3,
+ real size=0, real angle=arrowangle,
+ filltype filltype=null, position position=BeginPoint,
+ material arrowheadpen=nullpen)
+{
+ return new bool(picture pic, path3 g, material p, margin3 margin,
+ light light, light arrowheadlight) {
+ add(pic,arrowhead,size,angle,filltype,position,arrowheadpen,g,p,
+ forwards=false,margin,light,arrowheadlight);
+ return false;
+ };
+}
+
+arrowbar3 Arrow3(arrowhead3 arrowhead=DefaultHead3,
+ real size=0, real angle=arrowangle,
+ filltype filltype=null, position position=EndPoint,
+ material arrowheadpen=nullpen)
+{
+ return new bool(picture pic, path3 g, material p, margin3 margin,
+ light light, light arrowheadlight) {
+ add(pic,arrowhead,size,angle,filltype,position,arrowheadpen,g,p,margin,
+ light,arrowheadlight);
+ return false;
+ };
+}
+
+arrowbar3 EndArrow3(arrowhead3 arrowhead=DefaultHead3,
+ real size=0, real angle=arrowangle,
+ filltype filltype=null, position position=EndPoint,
+ material arrowheadpen=nullpen)=Arrow3;
+
+arrowbar3 MidArrow3(arrowhead3 arrowhead=DefaultHead3,
+ real size=0, real angle=arrowangle,
+ filltype filltype=null, material arrowheadpen=nullpen)
+{
+ return new bool(picture pic, path3 g, material p, margin3 margin,
+ light light, light arrowheadlight) {
+ add(pic,arrowhead,size,angle,filltype,MidPoint,
+ arrowheadpen,g,p,margin,center=true,light,arrowheadlight);
+ return false;
+ };
+}
+
+arrowbar3 Arrows3(arrowhead3 arrowhead=DefaultHead3,
+ real size=0, real angle=arrowangle,
+ filltype filltype=null, material arrowheadpen=nullpen)
+{
+ return new bool(picture pic, path3 g, material p, margin3 margin,
+ light light, light arrowheadlight) {
+ add2(pic,arrowhead,size,angle,filltype,arrowheadpen,g,p,margin,light,
+ arrowheadlight);
+ return false;
+ };
+}
+
+arrowbar3 BeginArcArrow3(arrowhead3 arrowhead=DefaultHead3,
+ real size=0, real angle=arcarrowangle,
+ filltype filltype=null, position position=BeginPoint,
+ material arrowheadpen=nullpen)
+{
+ return new bool(picture pic, path3 g, material p, margin3 margin,
+ light light, light arrowheadlight) {
+ real size=size == 0 ? arrowhead.arcsize((pen) p) : size;
+ add(pic,arrowhead,size,angle,filltype,position,arrowheadpen,g,p,
+ forwards=false,margin,light,arrowheadlight);
+ return false;
+ };
+}
+
+arrowbar3 ArcArrow3(arrowhead3 arrowhead=DefaultHead3,
+ real size=0, real angle=arcarrowangle,
+ filltype filltype=null, position position=EndPoint,
+ material arrowheadpen=nullpen)
+{
+ return new bool(picture pic, path3 g, material p, margin3 margin,
+ light light, light arrowheadlight) {
+ real size=size == 0 ? arrowhead.arcsize((pen) p) : size;
+ add(pic,arrowhead,size,angle,filltype,position,arrowheadpen,g,p,margin,
+ light,arrowheadlight);
+ return false;
+ };
+}
+
+arrowbar3 EndArcArrow3(arrowhead3 arrowhead=DefaultHead3,
+ real size=0, real angle=arcarrowangle,
+ filltype filltype=null,
+ position position=EndPoint,
+ material arrowheadpen=nullpen)=ArcArrow3;
+
+
+arrowbar3 MidArcArrow3(arrowhead3 arrowhead=DefaultHead3,
+ real size=0, real angle=arcarrowangle,
+ filltype filltype=null, material arrowheadpen=nullpen)
+{
+ return new bool(picture pic, path3 g, material p, margin3 margin,
+ light light, light arrowheadlight) {
+ real size=size == 0 ? arrowhead.arcsize((pen) p) : size;
+ add(pic,arrowhead,size,angle,filltype,MidPoint,arrowheadpen,g,p,margin,
+ center=true,light,arrowheadlight);
+ return false;
+ };
+}
+
+arrowbar3 ArcArrows3(arrowhead3 arrowhead=DefaultHead3,
+ real size=0, real angle=arcarrowangle,
+ filltype filltype=null, material arrowheadpen=nullpen)
+{
+ return new bool(picture pic, path3 g, material p, margin3 margin,
+ light light, light arrowheadlight) {
+ real size=size == 0 ? arrowhead.arcsize((pen) p) : size;
+ add2(pic,arrowhead,size,angle,filltype,arrowheadpen,g,p,margin,light,
+ arrowheadlight);
+ return false;
+ };
+}
+
+arrowbar3 BeginBar3(real size=0, triple dir=O)
+{
+ return new bool(picture pic, path3 g, material p, margin3 margin, light light,
+ light) {
+ real size=size == 0 ? barsize((pen) p) : size;
+ bar(pic,point(g,0),size*unit(dir),size*dir(g,0),p,light);
+ return true;
+ };
+}
+
+arrowbar3 Bar3(real size=0, triple dir=O)
+{
+ return new bool(picture pic, path3 g, material p, margin3 margin, light light,
+ light) {
+ int L=length(g);
+ real size=size == 0 ? barsize((pen) p) : size;
+ bar(pic,point(g,L),size*unit(dir),size*dir(g,L),p,light);
+ return true;
+ };
+}
+
+arrowbar3 EndBar3(real size=0, triple dir=O)=Bar3;
+
+arrowbar3 Bars3(real size=0, triple dir=O)
+{
+ return new bool(picture pic, path3 g, material p, margin3 margin, light light,
+ light) {
+ real size=size == 0 ? barsize((pen) p) : size;
+ BeginBar3(size,dir)(pic,g,p,margin,light,nolight);
+ EndBar3(size,dir)(pic,g,p,margin,light,nolight);
+ return true;
+ };
+}
+
+arrowbar3 BeginArrow3=BeginArrow3(),
+MidArrow3=MidArrow3(),
+Arrow3=Arrow3(),
+EndArrow3=Arrow3(),
+Arrows3=Arrows3(),
+BeginArcArrow3=BeginArcArrow3(),
+MidArcArrow3=MidArcArrow3(),
+ArcArrow3=ArcArrow3(),
+EndArcArrow3=ArcArrow3(),
+ArcArrows3=ArcArrows3(),
+BeginBar3=BeginBar3(),
+Bar3=Bar3(),
+EndBar3=Bar3(),
+Bars3=Bars3();
diff --git a/graphics/asymptote/base/three_light.asy b/graphics/asymptote/base/three_light.asy
new file mode 100644
index 0000000000..6e0bc366c7
--- /dev/null
+++ b/graphics/asymptote/base/three_light.asy
@@ -0,0 +1,133 @@
+struct material {
+ pen[] p; // diffusepen,emissivepen,specularpen
+ real opacity;
+ real shininess;
+ real metallic;
+ real fresnel0; // Reflectance rate at a perfect normal angle.
+
+ void operator init(pen diffusepen=black,
+ pen emissivepen=black, pen specularpen=mediumgray,
+ real opacity=opacity(diffusepen),
+ real shininess=defaultshininess,
+ real metallic=defaultmetallic,
+ real fresnel0=defaultfresnel0) {
+
+ p=new pen[] {diffusepen,emissivepen,specularpen};
+ this.opacity=opacity;
+ this.shininess=shininess;
+ this.metallic=metallic;
+ this.fresnel0=fresnel0;
+ }
+ void operator init(material m) {
+ p=copy(m.p);
+ opacity=m.opacity;
+ shininess=m.shininess;
+ metallic=m.metallic;
+ fresnel0=m.fresnel0;
+ }
+ pen diffuse() {return p[0];}
+ pen emissive() {return p[1];}
+ pen specular() {return p[2];}
+
+ void diffuse(pen q) {p[0]=q;}
+ void emissive(pen q) {p[1]=q;}
+ void specular(pen q) {p[2]=q;}
+}
+
+material operator init()
+{
+ return material();
+}
+
+void write(file file, string s="", material x, suffix suffix=none)
+{
+ write(file,s);
+ write(file,"{");
+ write(file,"diffuse=",x.diffuse());
+ write(file,", emissive=",x.emissive());
+ write(file,", specular=",x.specular());
+ write(file,", opacity=",x.opacity);
+ write(file,", shininess=",x.shininess);
+ write(file,", metallic=",x.metallic);
+ write(file,", F0=",x.fresnel0);
+ write(file,"}",suffix);
+}
+
+void write(string s="", material x, suffix suffix=endl)
+{
+ write(stdout,s,x,suffix);
+}
+
+bool operator == (material m, material n)
+{
+ return all(m.p == n.p) && m.opacity == n.opacity &&
+ m.shininess == n.shininess && m.metallic == n.metallic &&
+ m.fresnel0 == n.fresnel0;
+}
+
+material operator cast(pen p)
+{
+ return material(p);
+}
+
+material[] operator cast(pen[] p)
+{
+ return sequence(new material(int i) {return p[i];},p.length);
+}
+
+pen operator ecast(material m)
+{
+ return m.p.length > 0 ? m.diffuse() : nullpen;
+}
+
+material emissive(material m)
+{
+ return material(black+opacity(m.opacity),m.diffuse(),black,m.opacity,1);
+}
+
+pen color(triple normal, material m, light light, transform3 T=light.T) {
+ triple[] position=light.position;
+ if(invisible((pen) m)) return invisible;
+ if(position.length == 0) return m.diffuse();
+ normal=unit(transpose(inverse(shiftless(T)))*normal);
+ if(settings.twosided) normal *= sgn(normal.z);
+ real s=m.shininess*128;
+ real[] Diffuse=rgba(m.diffuse());
+ real[] Specular=rgba(m.specular());
+ real[] p=rgba(m.emissive());
+ real[] diffuse={0,0,0,0};
+ real[] specular={0,0,0,0};
+ for(int i=0; i < position.length; ++i) {
+ triple L=position[i];
+ real dotproduct=abs(dot(normal,L));
+ diffuse += dotproduct*light.diffuse[i];
+ dotproduct=abs(dot(normal,unit(L+Z)));
+ // Phong-Blinn model of specular reflection
+ specular += dotproduct^s*light.specular[i];
+ }
+ p += diffuse*Diffuse;
+ // Apply specularfactor to partially compensate non-pixel-based rendering.
+ p += specular*Specular*light.specularfactor;
+ return rgb(p[0],p[1],p[2])+opacity(opacity(m.diffuse()));
+}
+
+light operator * (transform3 t, light light)
+{
+ light light=light(light);
+ return light;
+}
+
+light operator cast(triple v) {return light(v);}
+
+light Viewport=light(specularfactor=3,(0.25,-0.25,1));
+
+light White=light(new pen[] {rgb(0.38,0.38,0.45),rgb(0.6,0.6,0.67),
+ rgb(0.5,0.5,0.57)},specularfactor=3,
+ new triple[] {(-2,-1.5,-0.5),(2,1.1,-2.5),(-0.5,0,2)});
+
+light Headlamp=light(gray(0.8),specular=gray(0.7),
+ specularfactor=3,dir(42,48));
+
+currentlight=Headlamp;
+
+light nolight;
diff --git a/graphics/asymptote/base/three_margins.asy b/graphics/asymptote/base/three_margins.asy
new file mode 100644
index 0000000000..b6f286ee4f
--- /dev/null
+++ b/graphics/asymptote/base/three_margins.asy
@@ -0,0 +1,104 @@
+struct marginT3 {
+ path3 g;
+ real begin,end;
+};
+
+typedef marginT3 margin3(path3, pen);
+
+path3 trim(path3 g, real begin, real end) {
+ real a=arctime(g,begin);
+ real b=arctime(g,arclength(g)-end);
+ return a <= b ? subpath(g,a,b) : point(g,a);
+}
+
+margin3 operator +(margin3 ma, margin3 mb)
+{
+ return new marginT3(path3 g, pen p) {
+ marginT3 margin;
+ real ba=ma(g,p).begin < 0 ? 0 : ma(g,p).begin;
+ real bb=mb(g,p).begin < 0 ? 0 : mb(g,p).begin;
+ real ea=ma(g,p).end < 0 ? 0 : ma(g,p).end;
+ real eb=mb(g,p).end < 0 ? 0 : mb(g,p).end;
+ margin.begin=ba+bb;
+ margin.end=ea+eb;
+ margin.g=trim(g,margin.begin,margin.end);
+ return margin;
+ };
+}
+
+margin3 NoMargin3()
+{
+ return new marginT3(path3 g, pen) {
+ marginT3 margin;
+ margin.begin=margin.end=0;
+ margin.g=g;
+ return margin;
+ };
+}
+
+margin3 Margin3(real begin, real end)
+{
+ return new marginT3(path3 g, pen p) {
+ marginT3 margin;
+ real factor=labelmargin(p);
+ real w=0.5*linewidth(p);
+ margin.begin=begin*factor-w;
+ margin.end=end*factor-w;
+ margin.g=trim(g,margin.begin,margin.end);
+ return margin;
+ };
+}
+
+margin3 PenMargin3(real begin, real end)
+{
+ return new marginT3(path3 g, pen p) {
+ marginT3 margin;
+ real factor=linewidth(p);
+ margin.begin=begin*factor;
+ margin.end=end*factor;
+ margin.g=trim(g,margin.begin,margin.end);
+ return margin;
+ };
+}
+
+margin3 DotMargin3(real begin, real end)
+{
+ return new marginT3(path3 g, pen p) {
+ marginT3 margin;
+ real margindot(real x) {return x > 0 ? dotfactor*x : x;}
+ real factor=linewidth(p);
+ margin.begin=margindot(begin)*factor;
+ margin.end=margindot(end)*factor;
+ margin.g=trim(g,margin.begin,margin.end);
+ return margin;
+ };
+}
+
+margin3 TrueMargin3(real begin, real end)
+{
+ return new marginT3(path3 g, pen p) {
+ marginT3 margin;
+ margin.begin=begin;
+ margin.end=end;
+ margin.g=trim(g,begin,end);
+ return margin;
+ };
+}
+
+margin3 NoMargin3=NoMargin3(),
+ BeginMargin3=Margin3(1,0),
+ Margin3=Margin3(0,1),
+ EndMargin3=Margin3,
+ Margins3=Margin3(1,1),
+ BeginPenMargin3=PenMargin3(0.5,-0.5),
+ BeginPenMargin2=PenMargin3(1.0,-0.5),
+ PenMargin3=PenMargin3(-0.5,0.5),
+ PenMargin2=PenMargin3(-0.5,1.0),
+ EndPenMargin3=PenMargin3,
+ EndPenMargin2=PenMargin2,
+ PenMargins3=PenMargin3(0.5,0.5),
+ PenMargins2=PenMargin3(1.0,1.0),
+ BeginDotMargin3=DotMargin3(0.5,-0.5),
+ DotMargin3=DotMargin3(-0.5,0.5),
+ EndDotMargin3=DotMargin3,
+ DotMargins3=DotMargin3(0.5,0.5);
diff --git a/graphics/asymptote/base/three_surface.asy b/graphics/asymptote/base/three_surface.asy
new file mode 100644
index 0000000000..bdb56cb0ca
--- /dev/null
+++ b/graphics/asymptote/base/three_surface.asy
@@ -0,0 +1,2455 @@
+import bezulate;
+private import interpolate;
+
+int nslice=12;
+real camerafactor=1.2;
+
+string meshname(string name) {return name+" mesh";}
+
+private real Fuzz=10.0*realEpsilon;
+private real nineth=1/9;
+
+// Return the default Coons interior control point for a Bezier triangle
+// based on the cyclic path3 external.
+triple coons3(path3 external) {
+ return 0.25*(precontrol(external,0)+postcontrol(external,0)+
+ precontrol(external,1)+postcontrol(external,1)+
+ precontrol(external,2)+postcontrol(external,2))-
+ (point(external,0)+point(external,1)+point(external,2))/6;
+}
+
+struct patch {
+ triple[][] P;
+ pen[] colors; // Optionally specify 4 corner colors.
+ bool straight; // Patch is based on a piecewise straight external path.
+ bool3 planar; // Patch is planar.
+ bool triangular; // Patch is a Bezier triangle.
+
+ path3 external() {
+ return straight ? P[0][0]--P[3][0]--P[3][3]--P[0][3]--cycle :
+ P[0][0]..controls P[1][0] and P[2][0]..
+ P[3][0]..controls P[3][1] and P[3][2]..
+ P[3][3]..controls P[2][3] and P[1][3]..
+ P[0][3]..controls P[0][2] and P[0][1]..cycle;
+ }
+
+ path3 externaltriangular() {
+ return
+ P[0][0]..controls P[1][0] and P[2][0]..
+ P[3][0]..controls P[3][1] and P[3][2]..
+ P[3][3]..controls P[2][2] and P[1][1]..cycle;
+ }
+
+ triple[] internal() {
+ return new triple[] {P[1][1],P[2][1],P[2][2],P[1][2]};
+ }
+
+ triple[] internaltriangular() {
+ return new triple[] {P[2][1]};
+ }
+
+ triple cornermean() {
+ return 0.25*(P[0][0]+P[0][3]+P[3][0]+P[3][3]);
+ }
+
+ triple cornermeantriangular() {
+ return (P[0][0]+P[3][0]+P[3][3])/3;
+ }
+
+ triple[] corners() {return new triple[] {P[0][0],P[3][0],P[3][3],P[0][3]};}
+ triple[] cornerstriangular() {return new triple[] {P[0][0],P[3][0],P[3][3]};}
+
+ real[] map(real f(triple)) {
+ return new real[] {f(P[0][0]),f(P[3][0]),f(P[3][3]),f(P[0][3])};
+ }
+
+ real[] maptriangular(real f(triple)) {
+ return new real[] {f(P[0][0]),f(P[3][0]),f(P[3][3])};
+ }
+
+ triple Bu(int j, real u) {return bezier(P[0][j],P[1][j],P[2][j],P[3][j],u);}
+ triple BuP(int j, real u) {
+ return bezierP(P[0][j],P[1][j],P[2][j],P[3][j],u);
+ }
+ triple BuPP(int j, real u) {
+ return bezierPP(P[0][j],P[1][j],P[2][j],P[3][j],u);
+ }
+ triple BuPPP(int j) {return bezierPPP(P[0][j],P[1][j],P[2][j],P[3][j]);}
+
+ path3 uequals(real u) {
+ triple z0=Bu(0,u);
+ triple z1=Bu(3,u);
+ return path3(new triple[] {z0,Bu(2,u)},new triple[] {z0,z1},
+ new triple[] {Bu(1,u),z1},new bool[] {straight,false},false);
+ }
+
+ triple Bv(int i, real v) {return bezier(P[i][0],P[i][1],P[i][2],P[i][3],v);}
+ triple BvP(int i, real v) {
+ return bezierP(P[i][0],P[i][1],P[i][2],P[i][3],v);
+ }
+ triple BvPP(int i, real v) {
+ return bezierPP(P[i][0],P[i][1],P[i][2],P[i][3],v);
+ }
+ triple BvPPP(int i) {return bezierPPP(P[i][0],P[i][1],P[i][2],P[i][3]);}
+
+ path3 vequals(real v) {
+ triple z0=Bv(0,v);
+ triple z1=Bv(3,v);
+ return path3(new triple[] {z0,Bv(2,v)},new triple[] {z0,z1},
+ new triple[] {Bv(1,v),z1},new bool[] {straight,false},false);
+ }
+
+ triple point(real u, real v) {
+ return bezier(Bu(0,u),Bu(1,u),Bu(2,u),Bu(3,u),v);
+ }
+
+ // compute normal vectors for degenerate cases
+ private triple normal0(real u, real v, real epsilon) {
+ triple n=0.5*(cross(bezier(BuPP(0,u),BuPP(1,u),BuPP(2,u),BuPP(3,u),v),
+ bezier(BvP(0,v),BvP(1,v),BvP(2,v),BvP(3,v),u))+
+ cross(bezier(BuP(0,u),BuP(1,u),BuP(2,u),BuP(3,u),v),
+ bezier(BvPP(0,v),BvPP(1,v),BvPP(2,v),BvPP(3,v),u)));
+ return abs(n) > epsilon ? n :
+ 0.25*cross(bezier(BuPP(0,u),BuPP(1,u),BuPP(2,u),BuPP(3,u),v),
+ bezier(BvPP(0,v),BvPP(1,v),BvPP(2,v),BvPP(3,v),u))+
+ 1/6*(cross(bezier(BuP(0,u),BuP(1,u),BuP(2,u),BuP(3,u),v),
+ bezier(BvPPP(0),BvPPP(1),BvPPP(2),BvPPP(3),u))+
+ cross(bezier(BuPPP(0),BuPPP(1),BuPPP(2),BuPPP(3),v),
+ bezier(BvP(0,v),BvP(1,v),BvP(2,v),BvP(3,v),u)))+
+ 1/12*(cross(bezier(BuPPP(0),BuPPP(1),BuPPP(2),BuPPP(3),v),
+ bezier(BvPP(0,v),BvPP(1,v),BvPP(2,v),BvPP(3,v),u))+
+ cross(bezier(BuPP(0,u),BuPP(1,u),BuPP(2,u),BuPP(3,u),v),
+ bezier(BvPPP(0),BvPPP(1),BvPPP(2),BvPPP(3),u)))+
+ 1/36*cross(bezier(BuPPP(0),BuPPP(1),BuPPP(2),BuPPP(3),v),
+ bezier(BvPPP(0),BvPPP(1),BvPPP(2),BvPPP(3),u));
+ }
+
+ static real fuzz=1000*realEpsilon;
+
+ triple partialu(real u, real v) {
+ return bezier(BuP(0,u),BuP(1,u),BuP(2,u),BuP(3,u),v);
+ }
+
+ triple partialv(real u, real v) {
+ return bezier(BvP(0,v),BvP(1,v),BvP(2,v),BvP(3,v),u);
+ }
+
+ triple normal(real u, real v) {
+ triple n=cross(partialu(u,v),partialv(u,v));
+ real epsilon=fuzz*change2(P);
+ return (abs(n) > epsilon) ? n : normal0(u,v,epsilon);
+ }
+
+ triple normal00() {
+ triple n=9*cross(P[1][0]-P[0][0],P[0][1]-P[0][0]);
+ real epsilon=fuzz*change2(P);
+ return abs(n) > epsilon ? n : normal0(0,0,epsilon);
+ }
+
+ triple normal10() {
+ triple n=9*cross(P[3][0]-P[2][0],P[3][1]-P[3][0]);
+ real epsilon=fuzz*change2(P);
+ return abs(n) > epsilon ? n : normal0(1,0,epsilon);
+ }
+
+ triple normal11() {
+ triple n=9*cross(P[3][3]-P[2][3],P[3][3]-P[3][2]);
+ real epsilon=fuzz*change2(P);
+ return abs(n) > epsilon ? n : normal0(1,1,epsilon);
+ }
+
+ triple normal01() {
+ triple n=9*cross(P[1][3]-P[0][3],P[0][3]-P[0][2]);
+ real epsilon=fuzz*change2(P);
+ return abs(n) > epsilon ? n : normal0(0,1,epsilon);
+ }
+
+ triple pointtriangular(real u, real v) {
+ real w=1-u-v;
+ return w^2*(w*P[0][0]+3*(u*P[1][0]+v*P[1][1]))+
+ u^2*(3*(w*P[2][0]+v*P[3][1])+u*P[3][0])+
+ 6*u*v*w*P[2][1]+v^2*(3*(w*P[2][2]+u*P[3][2])+v*P[3][3]);
+ }
+
+ triple bu(real u, real v) {
+ // Compute one-third of the directional derivative of a Bezier triangle
+ // in the u direction at (u,v).
+ real w=1-u-v;
+ return -w^2*P[0][0]+w*(w-2*u)*P[1][0]-2*w*v*P[1][1]+u*(2*w-u)*P[2][0]+
+ 2*v*(w-u)*P[2][1]-v^2*P[2][2]+u^2*P[3][0]+2*u*v*P[3][1]+v^2*P[3][2];
+ }
+
+ triple buu(real u, real v) {
+ // Compute one-sixth of the second directional derivative of a Bezier
+ // triangle in the u direction at (u,v).
+ real w=1-u-v;
+ return w*P[0][0]+(u-2*w)*P[1][0]+v*P[1][1]+(w-2*u)*P[2][0]-2*v*P[2][1]+
+ u*P[3][0]+v*P[3][1];
+ }
+
+ triple buuu() {
+ // Compute one-sixth of the third directional derivative of a Bezier
+ // triangle in the u direction at (u,v).
+ return -P[0][0]+3*P[1][0]-3*P[2][0]+P[3][0];
+ }
+
+ triple bv(real u, real v) {
+ // Compute one-third of the directional derivative of a Bezier triangle
+ // in the v direction at (u,v).
+ real w=1-u-v;
+ return -w^2*P[0][0]-2*u*w*P[1][0]+w*(w-2*v)*P[1][1]-u^2*P[2][0]+
+ 2*u*(w-v)*P[2][1]+v*(2*w-v)*P[2][2]+u*u*P[3][1]+2*u*v*P[3][2]+
+ v^2*P[3][3];
+ }
+
+ triple bvv(real u, real v) {
+ // Compute one-sixth of the second directional derivative of a Bezier
+ // triangle in the v direction at (u,v).
+ real w=1-u-v;
+ return w*P[0][0]+u*P[1][0]+(v-2*w)*P[1][1]-2*u*P[2][1]+(w-2*v)*P[2][2]+
+ u*P[3][2]+v*P[3][3];
+ }
+
+ triple bvvv() {
+ // Compute one-sixth of the third directional derivative of a Bezier
+ // triangle in the v direction at (u,v).
+ return -P[0][0]+3*P[1][1]-3*P[2][2]+P[3][3];
+ }
+
+ // compute normal vectors for a degenerate Bezier triangle
+ private triple normaltriangular0(real u, real v, real epsilon) {
+ triple n=9*(cross(buu(u,v),bv(u,v))+
+ cross(bu(u,v),bvv(u,v)));
+ return abs(n) > epsilon ? n :
+ 9*cross(buu(u,v),bvv(u,v))+
+ 3*(cross(buuu(),bv(u,v))+cross(bu(u,v),bvvv())+
+ cross(buuu(),bvv(u,v))+cross(buu(u,v),bvvv()))+
+ cross(buuu(),bvvv());
+ }
+
+ // Compute the normal of a Bezier triangle at (u,v)
+ triple normaltriangular(real u, real v) {
+ triple n=9*cross(bu(u,v),bv(u,v));
+ real epsilon=fuzz*change2(P);
+ return (abs(n) > epsilon) ? n : normal0(u,v,epsilon);
+ }
+
+ triple normal00triangular() {
+ triple n=9*cross(P[1][0]-P[0][0],P[1][1]-P[0][0]);
+ real epsilon=fuzz*change2(P);
+ return abs(n) > epsilon ? n : normaltriangular0(0,0,epsilon);
+ }
+
+ triple normal10triangular() {
+ triple n=9*cross(P[3][0]-P[2][0],P[3][1]-P[2][0]);
+ real epsilon=fuzz*change2(P);
+ return abs(n) > epsilon ? n : normaltriangular0(1,0,epsilon);
+ }
+
+ triple normal01triangular() {
+ triple n=9*cross(P[3][2]-P[2][2],P[3][3]-P[2][2]);
+ real epsilon=fuzz*change2(P);
+ return abs(n) > epsilon ? n : normaltriangular0(0,1,epsilon);
+ }
+
+ pen[] colors(material m, light light=currentlight) {
+ bool nocolors=colors.length == 0;
+ if(planar) {
+ triple normal=normal(0.5,0.5);
+ return new pen[] {color(normal,nocolors ? m : colors[0],light),
+ color(normal,nocolors ? m : colors[1],light),
+ color(normal,nocolors ? m : colors[2],light),
+ color(normal,nocolors ? m : colors[3],light)};
+ }
+ return new pen[] {color(normal00(),nocolors ? m : colors[0],light),
+ color(normal10(),nocolors ? m : colors[1],light),
+ color(normal11(),nocolors ? m : colors[2],light),
+ color(normal01(),nocolors ? m : colors[3],light)};
+ }
+
+ pen[] colorstriangular(material m, light light=currentlight) {
+ bool nocolors=colors.length == 0;
+ if(planar) {
+ triple normal=normal(1/3,1/3);
+ return new pen[] {color(normal,nocolors ? m : colors[0],light),
+ color(normal,nocolors ? m : colors[1],light),
+ color(normal,nocolors ? m : colors[2],light)};
+ }
+ return new pen[] {color(normal00(),nocolors ? m : colors[0],light),
+ color(normal10(),nocolors ? m : colors[1],light),
+ color(normal01(),nocolors ? m : colors[2],light)};
+ }
+
+ triple min3,max3;
+ bool havemin3,havemax3;
+
+ void init() {
+ havemin3=false;
+ havemax3=false;
+ if(triangular) {
+ external=externaltriangular;
+ internal=internaltriangular;
+ cornermean=cornermeantriangular;
+ corners=cornerstriangular;
+ map=maptriangular;
+ point=pointtriangular;
+ normal=normaltriangular;
+ normal00=normal00triangular;
+ normal10=normal10triangular;
+ normal01=normal01triangular;
+ colors=colorstriangular;
+ uequals=new path3(real u) {return nullpath3;};
+ vequals=new path3(real u) {return nullpath3;};
+ }
+ }
+
+ triple min(triple bound=P[0][0]) {
+ if(havemin3) return minbound(min3,bound);
+ havemin3=true;
+ return min3=minbezier(P,bound);
+ }
+
+ triple max(triple bound=P[0][0]) {
+ if(havemax3) return maxbound(max3,bound);
+ havemax3=true;
+ return max3=maxbezier(P,bound);
+ }
+
+ triple center() {
+ return 0.5*(this.min()+this.max());
+ }
+
+ pair min(projection P, pair bound=project(this.P[0][0],P.t)) {
+ triple[][] Q=P.T.modelview*this.P;
+ if(P.infinity)
+ return xypart(minbezier(Q,(bound.x,bound.y,0)));
+ real d=P.T.projection[3][2];
+ return maxratio(Q,d*bound)/d; // d is negative
+ }
+
+ pair max(projection P, pair bound=project(this.P[0][0],P.t)) {
+ triple[][] Q=P.T.modelview*this.P;
+ if(P.infinity)
+ return xypart(maxbezier(Q,(bound.x,bound.y,0)));
+ real d=P.T.projection[3][2];
+ return minratio(Q,d*bound)/d; // d is negative
+ }
+
+ void operator init(triple[][] P,
+ pen[] colors=new pen[], bool straight=false,
+ bool3 planar=default, bool triangular=false,
+ bool copy=true) {
+ this.P=copy ? copy(P) : P;
+ if(colors.length != 0)
+ this.colors=copy(colors);
+ this.straight=straight;
+ this.planar=planar;
+ this.triangular=triangular;
+ init();
+ }
+
+ void operator init(pair[][] P, triple plane(pair)=XYplane,
+ bool straight=false, bool triangular=false) {
+ triple[][] Q=new triple[4][];
+ for(int i=0; i < 4; ++i) {
+ pair[] Pi=P[i];
+ Q[i]=sequence(new triple(int j) {return plane(Pi[j]);},4);
+ }
+ operator init(Q,straight,planar=true,triangular);
+ }
+
+ void operator init(patch s) {
+ operator init(s.P,s.colors,s.straight,s.planar,s.triangular);
+ }
+
+ // A constructor for a cyclic path3 of length 3 with a specified
+ // internal point, corner normals, and pens (rendered as a Bezier triangle).
+ void operator init(path3 external, triple internal, pen[] colors=new pen[],
+ bool3 planar=default) {
+ triangular=true;
+ this.planar=planar;
+ init();
+ if(colors.length != 0)
+ this.colors=copy(colors);
+
+ P=new triple[][] {
+ {point(external,0)},
+ {postcontrol(external,0),precontrol(external,0)},
+ {precontrol(external,1),internal,postcontrol(external,2)},
+ {point(external,1),postcontrol(external,1),precontrol(external,2),
+ point(external,2)}
+ };
+ }
+
+ // A constructor for a convex cyclic path3 of length <= 4 with optional
+ // arrays of internal points (4 for a Bezier patch, 1 for a Bezier
+ // triangle), and pens.
+ void operator init(path3 external, triple[] internal=new triple[],
+ pen[] colors=new pen[], bool3 planar=default) {
+ if(internal.length == 0 && planar == default)
+ this.planar=normal(external) != O;
+ else this.planar=planar;
+
+ int L=length(external);
+
+ if(L == 3) {
+ operator init(external,internal.length == 1 ? internal[0] :
+ coons3(external),colors,this.planar);
+ straight=piecewisestraight(external);
+ return;
+ }
+
+ if(L > 4 || !cyclic(external))
+ abort("cyclic path3 of length <= 4 expected");
+ if(L == 1) {
+ external=external--cycle--cycle--cycle;
+ if(colors.length > 0) colors.append(array(3,colors[0]));
+ } else if(L == 2) {
+ external=external--cycle--cycle;
+ if(colors.length > 0) colors.append(array(2,colors[0]));
+ }
+
+ init();
+ if(colors.length != 0)
+ this.colors=copy(colors);
+
+ if(internal.length == 0) {
+ straight=piecewisestraight(external);
+ internal=new triple[4];
+ for(int j=0; j < 4; ++j)
+ internal[j]=nineth*(-4*point(external,j)
+ +6*(precontrol(external,j)+postcontrol(external,j))
+ -2*(point(external,j-1)+point(external,j+1))
+ +3*(precontrol(external,j-1)+
+ postcontrol(external,j+1))
+ -point(external,j+2));
+ }
+
+ P=new triple[][] {
+ {point(external,0),precontrol(external,0),postcontrol(external,3),
+ point(external,3)},
+ {postcontrol(external,0),internal[0],internal[3],precontrol(external,3)},
+ {precontrol(external,1),internal[1],internal[2],postcontrol(external,2)},
+ {point(external,1),postcontrol(external,1),precontrol(external,2),
+ point(external,2)}
+ };
+ }
+
+ // A constructor for a convex quadrilateral.
+ void operator init(triple[] external, triple[] internal=new triple[],
+ pen[] colors=new pen[], bool3 planar=default) {
+ init();
+
+ if(internal.length == 0 && planar == default)
+ this.planar=normal(external) != O;
+ else this.planar=planar;
+
+ if(colors.length != 0)
+ this.colors=copy(colors);
+
+ if(internal.length == 0) {
+ internal=new triple[4];
+ for(int j=0; j < 4; ++j)
+ internal[j]=nineth*(4*external[j]+2*external[(j+1)%4]+
+ external[(j+2)%4]+2*external[(j+3)%4]);
+ }
+
+ straight=true;
+
+ triple delta[]=new triple[4];
+ for(int j=0; j < 4; ++j)
+ delta[j]=(external[(j+1)% 4]-external[j])/3;
+
+ P=new triple[][] {
+ {external[0],external[0]-delta[3],external[3]+delta[3],external[3]},
+ {external[0]+delta[0],internal[0],internal[3],external[3]-delta[2]},
+ {external[1]-delta[0],internal[1],internal[2],external[2]+delta[2]},
+ {external[1],external[1]+delta[1],external[2]-delta[1],external[2]}
+ };
+ }
+}
+
+patch operator * (transform3 t, patch s)
+{
+ patch S;
+ S.P=new triple[s.P.length][];
+ for(int i=0; i < s.P.length; ++i) {
+ triple[] si=s.P[i];
+ triple[] Si=S.P[i];
+ for(int j=0; j < si.length; ++j)
+ Si[j]=t*si[j];
+ }
+
+ S.colors=copy(s.colors);
+ S.planar=s.planar;
+ S.straight=s.straight;
+ S.triangular=s.triangular;
+ S.init();
+ return S;
+}
+
+patch reverse(patch s)
+{
+ assert(!s.triangular);
+ patch S;
+ S.P=transpose(s.P);
+ if(s.colors.length > 0)
+ S.colors=new pen[] {s.colors[0],s.colors[3],s.colors[2],s.colors[1]};
+ S.straight=s.straight;
+ S.planar=s.planar;
+ return S;
+}
+
+// Return a degenerate tensor patch representation of a Bezier triangle.
+patch tensor(patch s) {
+ if(!s.triangular) return patch(s);
+ triple[][] P=s.P;
+ return patch(new triple[][] {{P[0][0],P[0][0],P[0][0],P[0][0]},
+ {P[1][0],P[1][0]*2/3+P[1][1]/3,P[1][0]/3+P[1][1]*2/3,P[1][1]},
+ {P[2][0],P[2][0]/3+P[2][1]*2/3,P[2][1]*2/3+P[2][2]/3,P[2][2]},
+ {P[3][0],P[3][1],P[3][2],P[3][3]}},
+ s.colors.length > 0 ? new pen[] {s.colors[0],s.colors[1],s.colors[2],s.colors[0]} : new pen[],
+ s.straight,s.planar,false,false);
+}
+
+// Return the tensor product patch control points corresponding to path p
+// and points internal.
+pair[][] tensor(path p, pair[] internal)
+{
+ return new pair[][] {
+ {point(p,0),precontrol(p,0),postcontrol(p,3),point(p,3)},
+ {postcontrol(p,0),internal[0],internal[3],precontrol(p,3)},
+ {precontrol(p,1),internal[1],internal[2],postcontrol(p,2)},
+ {point(p,1),postcontrol(p,1),precontrol(p,2),point(p,2)}
+ };
+}
+
+// Return the Coons patch control points corresponding to path p.
+pair[][] coons(path p)
+{
+ int L=length(p);
+ if(L == 1)
+ p=p--cycle--cycle--cycle;
+ else if(L == 2)
+ p=p--cycle--cycle;
+ else if(L == 3)
+ p=p--cycle;
+
+ pair[] internal=new pair[4];
+ for(int j=0; j < 4; ++j) {
+ internal[j]=nineth*(-4*point(p,j)
+ +6*(precontrol(p,j)+postcontrol(p,j))
+ -2*(point(p,j-1)+point(p,j+1))
+ +3*(precontrol(p,j-1)+postcontrol(p,j+1))
+ -point(p,j+2));
+ }
+ return tensor(p,internal);
+}
+
+// Decompose a possibly nonconvex cyclic path into an array of paths that
+// yield nondegenerate Coons patches.
+path[] regularize(path p, bool checkboundary=true)
+{
+ path[] s;
+
+ if(!cyclic(p))
+ abort("cyclic path expected");
+
+ int L=length(p);
+
+ if(L > 4) {
+ for(path g : bezulate(p))
+ s.append(regularize(g,checkboundary));
+ return s;
+ }
+
+ bool straight=piecewisestraight(p);
+ if(L <= 3 && straight) {
+ return new path[] {p};
+ }
+
+ // Split p along the angle bisector at t.
+ bool split(path p, real t) {
+ pair dir=dir(p,t);
+ if(dir != 0) {
+ path g=subpath(p,t,t+length(p));
+ int L=length(g);
+ pair z=point(g,0);
+ real[] T=intersections(g,z,z+I*dir);
+ for(int i=0; i < T.length; ++i) {
+ real cut=T[i];
+ if(cut > sqrtEpsilon && cut < L-sqrtEpsilon) {
+ pair w=point(g,cut);
+ if(!inside(p,0.5*(z+w),zerowinding)) continue;
+ pair delta=sqrtEpsilon*(w-z);
+ if(intersections(g,z-delta--w+delta).length != 2) continue;
+ s.append(regularize(subpath(g,0,cut)--cycle,checkboundary));
+ s.append(regularize(subpath(g,cut,L)--cycle,checkboundary));
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // Ensure that all interior angles are less than 180 degrees.
+ real fuzz=1e-4;
+ int sign=sgn(windingnumber(p,inside(p,zerowinding)));
+ for(int i=0; i < L; ++i) {
+ if(sign*(conj(dir(p,i,-1))*dir(p,i,1)).y < -fuzz) {
+ if(split(p,i)) return s;
+ }
+ }
+
+ if(straight)
+ return new path[] {p};
+
+ pair[][] P=coons(p);
+
+ // Check for degeneracy.
+ pair[][] U=new pair[3][4];
+ pair[][] V=new pair[4][3];
+
+ for(int i=0; i < 3; ++i) {
+ for(int j=0; j < 4; ++j)
+ U[i][j]=P[i+1][j]-P[i][j];
+ }
+
+ for(int i=0; i < 4; ++i) {
+ for(int j=0; j < 3; ++j)
+ V[i][j]=P[i][j+1]-P[i][j];
+ }
+
+ int[] choose2={1,2,1};
+ int[] choose3={1,3,3,1};
+
+ real T[][]=new real[6][6];
+ for(int p=0; p < 6; ++p) {
+ int kstart=max(p-2,0);
+ int kstop=min(p,3);
+ real[] Tp=T[p];
+ for(int q=0; q < 6; ++q) {
+ real Tpq;
+ int jstop=min(q,3);
+ int jstart=max(q-2,0);
+ for(int k=kstart; k <= kstop; ++k) {
+ int choose3k=choose3[k];
+ for(int j=jstart; j <= jstop; ++j) {
+ int i=p-k;
+ int l=q-j;
+ Tpq += (conj(U[i][j])*V[k][l]).y*
+ choose2[i]*choose3k*choose3[j]*choose2[l];
+ }
+ }
+ Tp[q]=Tpq;
+ }
+ }
+
+ bool3 aligned=default;
+ bool degenerate=false;
+
+ for(int p=0; p < 6; ++p) {
+ for(int q=0; q < 6; ++q) {
+ if(aligned == default) {
+ if(T[p][q] > sqrtEpsilon) aligned=true;
+ if(T[p][q] < -sqrtEpsilon) aligned=false;
+ } else {
+ if((T[p][q] > sqrtEpsilon && aligned == false) ||
+ (T[p][q] < -sqrtEpsilon && aligned == true)) degenerate=true;
+ }
+ }
+ }
+
+ if(!degenerate) {
+ if(aligned == (sign >= 0))
+ return new path[] {p};
+ return s;
+ }
+
+ if(checkboundary) {
+ // Polynomial coefficients of (B_i'' B_j + B_i' B_j')/3.
+ static real[][][] fpv0={
+ {{5, -20, 30, -20, 5},
+ {-3, 24, -54, 48, -15},
+ {0, -6, 27, -36, 15},
+ {0, 0, -3, 8, -5}},
+ {{-7, 36, -66, 52, -15},
+ {3, -36, 108, -120, 45},
+ {0, 6, -45, 84, -45},
+ {0, 0, 3, -16, 15}},
+ {{2, -18, 45, -44, 15},
+ {0, 12, -63, 96, -45},
+ {0, 0, 18, -60, 45},
+ {0, 0, 0, 8, -15}},
+ {{0, 2, -9, 12, -5},
+ {0, 0, 9, -24, 15},
+ {0, 0, 0, 12, -15},
+ {0, 0, 0, 0, 5}}
+ };
+
+ // Compute one-ninth of the derivative of the Jacobian along the boundary.
+ real[][] c=array(4,array(5,0.0));
+ for(int i=0; i < 4; ++i) {
+ real[][] fpv0i=fpv0[i];
+ for(int j=0; j < 4; ++j) {
+ real[] w=fpv0i[j];
+ c[0] += w*(conj(P[i][0])*(P[j][1]-P[j][0])).y; // v=0
+ c[1] += w*(conj(P[3][j]-P[2][j])*P[3][i]).y; // u=1
+ c[2] += w*(conj(P[i][3])*(P[j][3]-P[j][2])).y; // v=1
+ c[3] += w*(conj(P[0][j]-P[1][j])*P[0][i]).y; // u=0
+ }
+ }
+
+ pair BuP(int j, real u) {
+ return bezierP(P[0][j],P[1][j],P[2][j],P[3][j],u);
+ }
+ pair BvP(int i, real v) {
+ return bezierP(P[i][0],P[i][1],P[i][2],P[i][3],v);
+ }
+ real normal(real u, real v) {
+ return (conj(bezier(BuP(0,u),BuP(1,u),BuP(2,u),BuP(3,u),v))*
+ bezier(BvP(0,v),BvP(1,v),BvP(2,v),BvP(3,v),u)).y;
+ }
+
+ // Use Rolle's theorem to check for degeneracy on the boundary.
+ real M=0;
+ real cut;
+ for(int i=0; i < 4; ++i) {
+ if(!straight(p,i)) {
+ real[] ci=c[i];
+ pair[] R=quarticroots(ci[4],ci[3],ci[2],ci[1],ci[0]);
+ for(pair r : R) {
+ if(fabs(r.y) < sqrtEpsilon) {
+ real t=r.x;
+ if(0 <= t && t <= 1) {
+ real[] U={t,1,t,0};
+ real[] V={0,t,1,t};
+ real[] T={t,t,1-t,1-t};
+ real N=sign*normal(U[i],V[i]);
+ if(N < M) {
+ M=N; cut=i+T[i];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Split at the worst boundary degeneracy.
+ if(M < 0 && split(p,cut)) return s;
+ }
+
+ // Split arbitrarily to resolve any remaining (internal) degeneracy.
+ checkboundary=false;
+ for(int i=0; i < L; ++i)
+ if(!straight(p,i) && split(p,i+0.5)) return s;
+
+ while(true)
+ for(int i=0; i < L; ++i)
+ if(!straight(p,i) && split(p,i+unitrand())) return s;
+
+ return s;
+}
+
+struct surface {
+ patch[] s;
+ int index[][];// Position of patch corresponding to major U,V parameter in s.
+ bool vcyclic;
+
+ bool empty() {
+ return s.length == 0;
+ }
+
+ void operator init(int n) {
+ s=new patch[n];
+ }
+
+ void operator init(... patch[] s) {
+ this.s=s;
+ }
+
+ void operator init(surface s) {
+ this.s=new patch[s.s.length];
+ for(int i=0; i < s.s.length; ++i)
+ this.s[i]=patch(s.s[i]);
+ this.index=copy(s.index);
+ this.vcyclic=s.vcyclic;
+ }
+
+ void operator init(triple[][][] P, pen[][] colors=new pen[][],
+ bool3 planar=default, bool triangular=false) {
+ s=sequence(new patch(int i) {
+ return patch(P[i],colors.length == 0 ? new pen[] : colors[i],planar,
+ triangular);
+ },P.length);
+ }
+
+ void colors(pen[][] palette) {
+ for(int i=0; i < s.length; ++i)
+ s[i].colors=copy(palette[i]);
+ }
+
+ triple[][] corners() {
+ triple[][] a=new triple[s.length][];
+ for(int i=0; i < s.length; ++i)
+ a[i]=s[i].corners();
+ return a;
+ }
+
+ real[][] map(real f(triple)) {
+ real[][] a=new real[s.length][];
+ for(int i=0; i < s.length; ++i)
+ a[i]=s[i].map(f);
+ return a;
+ }
+
+ triple[] cornermean() {
+ return sequence(new triple(int i) {return s[i].cornermean();},s.length);
+ }
+
+ triple point(real u, real v) {
+ int U=floor(u);
+ int V=floor(v);
+ int index=index.length == 0 ? U+V : index[U][V];
+ return s[index].point(u-U,v-V);
+ }
+
+ triple normal(real u, real v) {
+ int U=floor(u);
+ int V=floor(v);
+ int index=index.length == 0 ? U+V : index[U][V];
+ return s[index].normal(u-U,v-V);
+ }
+
+ void ucyclic(bool f)
+ {
+ index.cyclic=f;
+ }
+
+ void vcyclic(bool f)
+ {
+ for(int[] i : index)
+ i.cyclic=f;
+ vcyclic=f;
+ }
+
+ bool ucyclic()
+ {
+ return index.cyclic;
+ }
+
+ bool vcyclic()
+ {
+ return vcyclic;
+ }
+
+ path3 uequals(real u) {
+ if(index.length == 0) return nullpath3;
+ int U=floor(u);
+ int[] index=index[U];
+ path3 g;
+ for(int i : index)
+ g=g&s[i].uequals(u-U);
+ return vcyclic() ? g&cycle : g;
+ }
+
+ path3 vequals(real v) {
+ if(index.length == 0) return nullpath3;
+ int V=floor(v);
+ path3 g;
+ for(int[] i : index)
+ g=g&s[i[V]].vequals(v-V);
+ return ucyclic() ? g&cycle : g;
+ }
+
+ // A constructor for a possibly nonconvex simple cyclic path in a given
+ // plane.
+ void operator init(path p, triple plane(pair)=XYplane) {
+ for(path g : regularize(p)) {
+ if(length(g) == 3) {
+ path3 G=path3(g,plane);
+ s.push(patch(G,coons3(G),planar=true));
+ } else
+ s.push(patch(coons(g),plane,piecewisestraight(g)));
+ }
+ }
+
+ void operator init(explicit path[] g, triple plane(pair)=XYplane) {
+ for(path p : bezulate(g))
+ s.append(surface(p,plane).s);
+ }
+
+ // A general surface constructor for both planar and nonplanar 3D paths.
+ void construct(path3 external, triple[] internal=new triple[],
+ pen[] colors=new pen[], bool3 planar=default) {
+ int L=length(external);
+ if(!cyclic(external)) abort("cyclic path expected");
+
+ if(L <= 3 && piecewisestraight(external)) {
+ s.push(patch(external,internal,colors,planar));
+ return;
+ }
+
+ // Construct a surface from a possibly nonconvex planar cyclic path3.
+ if(planar != false && internal.length == 0 && colors.length == 0) {
+ triple n=normal(external);
+ if(n != O) {
+ transform3 T=align(n);
+ external=transpose(T)*external;
+ T *= shift(0,0,point(external,0).z);
+ for(patch p : surface(path(external)).s)
+ s.push(T*p);
+ return;
+ }
+ }
+
+ if(L <= 4 || internal.length > 0) {
+ s.push(patch(external,internal,colors,planar));
+ return;
+ }
+
+ // Path is not planar; split into patches.
+ real factor=1/L;
+ pen[] p;
+ triple[] n;
+ bool nocolors=colors.length == 0;
+ triple center;
+ for(int i=0; i < L; ++i)
+ center += point(external,i);
+ center *= factor;
+ if(!nocolors)
+ p=new pen[] {mean(colors)};
+ // Use triangles for nonplanar surfaces.
+ int step=normal(external) == O ? 1 : 2;
+ int i=0;
+ int end;
+ while((end=i+step) < L) {
+ s.push(patch(subpath(external,i,end)--center--cycle,
+ nocolors ? p : concat(colors[i:end+1],p),planar));
+ i=end;
+ }
+ s.push(patch(subpath(external,i,L)--center--cycle,
+ nocolors ? p : concat(colors[i:],colors[0:1],p),planar));
+ }
+
+ void operator init(path3 external, triple[] internal=new triple[],
+ pen[] colors=new pen[], bool3 planar=default) {
+ s=new patch[];
+ construct(external,internal,colors,planar);
+ }
+
+ void operator init(explicit path3[] external,
+ triple[][] internal=new triple[][],
+ pen[][] colors=new pen[][], bool3 planar=default) {
+ s=new patch[];
+ if(planar == true) {// Assume all path3 elements share a common normal.
+ if(external.length != 0) {
+ triple n=normal(external[0]);
+ if(n != O) {
+ transform3 T=align(n);
+ external=transpose(T)*external;
+ T *= shift(0,0,point(external[0],0).z);
+ path[] g=sequence(new path(int i) {return path(external[i]);},
+ external.length);
+ for(patch p : surface(g).s)
+ s.push(T*p);
+ return;
+ }
+ }
+ }
+
+ for(int i=0; i < external.length; ++i)
+ construct(external[i],
+ internal.length == 0 ? new triple[] : internal[i],
+ colors.length == 0 ? new pen[] : colors[i],planar);
+ }
+
+ void push(path3 external, triple[] internal=new triple[],
+ pen[] colors=new pen[], bool3 planar=default) {
+ s.push(patch(external,internal,colors,planar));
+ }
+
+ // Construct the surface of rotation generated by rotating g
+ // from angle1 to angle2 sampled n times about the line c--c+axis.
+ // An optional surface pen color(int i, real j) may be specified
+ // to override the color at vertex(i,j).
+ void operator init(triple c, path3 g, triple axis, int n=nslice,
+ real angle1=0, real angle2=360,
+ pen color(int i, real j)=null) {
+ axis=unit(axis);
+ real w=(angle2-angle1)/n;
+ int L=length(g);
+ s=new patch[L*n];
+ index=new int[n][L];
+ int m=-1;
+ transform3[] T=new transform3[n+1];
+ transform3 t=rotate(w,c,c+axis);
+ T[0]=rotate(angle1,c,c+axis);
+ for(int k=1; k <= n; ++k)
+ T[k]=T[k-1]*t;
+
+ typedef pen colorfcn(int i, real j);
+ bool defaultcolors=(colorfcn) color == null;
+
+ for(int i=0; i < L; ++i) {
+ path3 h=subpath(g,i,i+1);
+ path3 r=reverse(h);
+ path3 H=shift(-c)*h;
+ real M=0;
+ triple perp;
+ void test(real[] t) {
+ for(int i=0; i < 3; ++i) {
+ triple v=point(H,t[i]);
+ triple V=v-dot(v,axis)*axis;
+ real a=abs(V);
+ if(a > M) {M=a; perp=V;}
+ }
+ }
+ test(maxtimes(H));
+ test(mintimes(H));
+
+ perp=unit(perp);
+ triple normal=unit(cross(axis,perp));
+ triple dir(real j) {return Cos(j)*normal-Sin(j)*perp;}
+ real j=angle1;
+ transform3 Tk=T[0];
+ triple dirj=dir(j);
+ for(int k=0; k < n; ++k, j += w) {
+ transform3 Tp=T[k+1];
+ triple dirp=dir(j+w);
+ path3 G=reverse(Tk*h{dirj}..{dirp}Tp*r{-dirp}..{-dirj}cycle);
+ Tk=Tp;
+ dirj=dirp;
+ s[++m]=defaultcolors ? patch(G) :
+ patch(G,new pen[] {color(i,j),color(i,j+w),color(i+1,j+w),
+ color(i+1,j)});
+ index[k][i]=m;
+ }
+ ucyclic((angle2-angle1) % 360 == 0);
+ vcyclic(cyclic(g));
+ }
+ }
+
+ void push(patch s) {
+ this.s.push(s);
+ }
+
+ void append(surface s) {
+ this.s.append(s.s);
+ }
+
+ void operator init(... surface[] s) {
+ for(surface S : s)
+ this.s.append(S.s);
+ }
+}
+
+surface operator * (transform3 t, surface s)
+{
+ surface S;
+ S.s=new patch[s.s.length];
+ for(int i=0; i < s.s.length; ++i)
+ S.s[i]=t*s.s[i];
+ S.index=copy(s.index);
+ S.vcyclic=(bool) s.vcyclic;
+
+ return S;
+}
+
+private string nullsurface="null surface";
+
+triple min(surface s)
+{
+ if(s.s.length == 0)
+ abort(nullsurface);
+ triple bound=s.s[0].min();
+ for(int i=1; i < s.s.length; ++i)
+ bound=s.s[i].min(bound);
+ return bound;
+}
+
+triple max(surface s)
+{
+ if(s.s.length == 0)
+ abort(nullsurface);
+ triple bound=s.s[0].max();
+ for(int i=1; i < s.s.length; ++i)
+ bound=s.s[i].max(bound);
+ return bound;
+}
+
+pair min(surface s, projection P)
+{
+ if(s.s.length == 0)
+ abort(nullsurface);
+ pair bound=s.s[0].min(P);
+ for(int i=1; i < s.s.length; ++i)
+ bound=s.s[i].min(P,bound);
+ return bound;
+}
+
+pair max(surface s, projection P)
+{
+ if(s.s.length == 0)
+ abort(nullsurface);
+ pair bound=s.s[0].max(P);
+ for(int i=1; i < s.s.length; ++i)
+ bound=s.s[i].max(P,bound);
+ return bound;
+}
+
+private triple[] split(triple z0, triple c0, triple c1, triple z1, real t=0.5)
+{
+ triple m0=interp(z0,c0,t);
+ triple m1=interp(c0,c1,t);
+ triple m2=interp(c1,z1,t);
+ triple m3=interp(m0,m1,t);
+ triple m4=interp(m1,m2,t);
+ triple m5=interp(m3,m4,t);
+
+ return new triple[] {m0,m3,m5,m4,m2};
+}
+
+// Return the control points of the subpatches
+// produced by a horizontal split of P
+triple[][][] hsplit(triple[][] P, real v=0.5)
+{
+ // get control points in rows
+ triple[] P0=P[0];
+ triple[] P1=P[1];
+ triple[] P2=P[2];
+ triple[] P3=P[3];
+
+ triple[] c0=split(P0[0],P0[1],P0[2],P0[3],v);
+ triple[] c1=split(P1[0],P1[1],P1[2],P1[3],v);
+ triple[] c2=split(P2[0],P2[1],P2[2],P2[3],v);
+ triple[] c3=split(P3[0],P3[1],P3[2],P3[3],v);
+ // bottom, top
+ return new triple[][][] {
+ {{P0[0],c0[0],c0[1],c0[2]},
+ {P1[0],c1[0],c1[1],c1[2]},
+ {P2[0],c2[0],c2[1],c2[2]},
+ {P3[0],c3[0],c3[1],c3[2]}},
+ {{c0[2],c0[3],c0[4],P0[3]},
+ {c1[2],c1[3],c1[4],P1[3]},
+ {c2[2],c2[3],c2[4],P2[3]},
+ {c3[2],c3[3],c3[4],P3[3]}}
+ };
+}
+
+// Return the control points of the subpatches
+// produced by a vertical split of P
+triple[][][] vsplit(triple[][] P, real u=0.5)
+{
+ // get control points in rows
+ triple[] P0=P[0];
+ triple[] P1=P[1];
+ triple[] P2=P[2];
+ triple[] P3=P[3];
+
+ triple[] c0=split(P0[0],P1[0],P2[0],P3[0],u);
+ triple[] c1=split(P0[1],P1[1],P2[1],P3[1],u);
+ triple[] c2=split(P0[2],P1[2],P2[2],P3[2],u);
+ triple[] c3=split(P0[3],P1[3],P2[3],P3[3],u);
+ // left, right
+ return new triple[][][] {
+ {{P0[0],P0[1],P0[2],P0[3]},
+ {c0[0],c1[0],c2[0],c3[0]},
+ {c0[1],c1[1],c2[1],c3[1]},
+ {c0[2],c1[2],c2[2],c3[2]}},
+ {{c0[2],c1[2],c2[2],c3[2]},
+ {c0[3],c1[3],c2[3],c3[3]},
+ {c0[4],c1[4],c2[4],c3[4]},
+ {P3[0],P3[1],P3[2],P3[3]}}
+ };
+}
+
+// Return a 2D array of the control point arrays of the subpatches
+// produced by horizontal and vertical splits of P at u and v
+triple[][][][] split(triple[][] P, real u=0.5, real v=0.5)
+{
+ triple[] P0=P[0];
+ triple[] P1=P[1];
+ triple[] P2=P[2];
+ triple[] P3=P[3];
+
+ // slice horizontally
+ triple[] c0=split(P0[0],P0[1],P0[2],P0[3],v);
+ triple[] c1=split(P1[0],P1[1],P1[2],P1[3],v);
+ triple[] c2=split(P2[0],P2[1],P2[2],P2[3],v);
+ triple[] c3=split(P3[0],P3[1],P3[2],P3[3],v);
+
+ // bottom patch
+ triple[] c4=split(P0[0],P1[0],P2[0],P3[0],u);
+ triple[] c5=split(c0[0],c1[0],c2[0],c3[0],u);
+ triple[] c6=split(c0[1],c1[1],c2[1],c3[1],u);
+ triple[] c7=split(c0[2],c1[2],c2[2],c3[2],u);
+
+ // top patch
+ triple[] c8=split(c0[3],c1[3],c2[3],c3[3],u);
+ triple[] c9=split(c0[4],c1[4],c2[4],c3[4],u);
+ triple[] cA=split(P0[3],P1[3],P2[3],P3[3],u);
+
+ // {{bottom-left, top-left}, {bottom-right, top-right}}
+ return new triple[][][][] {
+ {{{P0[0],c0[0],c0[1],c0[2]},
+ {c4[0],c5[0],c6[0],c7[0]},
+ {c4[1],c5[1],c6[1],c7[1]},
+ {c4[2],c5[2],c6[2],c7[2]}},
+ {{c0[2],c0[3],c0[4],P0[3]},
+ {c7[0],c8[0],c9[0],cA[0]},
+ {c7[1],c8[1],c9[1],cA[1]},
+ {c7[2],c8[2],c9[2],cA[2]}}},
+ {{{c4[2],c5[2],c6[2],c7[2]},
+ {c4[3],c5[3],c6[3],c7[3]},
+ {c4[4],c5[4],c6[4],c7[4]},
+ {P3[0],c3[0],c3[1],c3[2]}},
+ {{c7[2],c8[2],c9[2],cA[2]},
+ {c7[3],c8[3],c9[3],cA[3]},
+ {c7[4],c8[4],c9[4],cA[4]},
+ {c3[2],c3[3],c3[4],P3[3]}}}
+ };
+}
+
+// Return the control points for a subpatch of P on [u,1] x [v,1].
+triple[][] subpatchend(triple[][] P, real u, real v)
+{
+ triple[] P0=P[0];
+ triple[] P1=P[1];
+ triple[] P2=P[2];
+ triple[] P3=P[3];
+
+ triple[] c0=split(P0[0],P0[1],P0[2],P0[3],v);
+ triple[] c1=split(P1[0],P1[1],P1[2],P1[3],v);
+ triple[] c2=split(P2[0],P2[1],P2[2],P2[3],v);
+ triple[] c3=split(P3[0],P3[1],P3[2],P3[3],v);
+
+ triple[] c7=split(c0[2],c1[2],c2[2],c3[2],u);
+ triple[] c8=split(c0[3],c1[3],c2[3],c3[3],u);
+ triple[] c9=split(c0[4],c1[4],c2[4],c3[4],u);
+ triple[] cA=split(P0[3],P1[3],P2[3],P3[3],u);
+
+ return new triple[][] {
+ {c7[2],c8[2],c9[2],cA[2]},
+ {c7[3],c8[3],c9[3],cA[3]},
+ {c7[4],c8[4],c9[4],cA[4]},
+ {c3[2],c3[3],c3[4],P3[3]}};
+}
+
+// Return the control points for a subpatch of P on [0,u] x [0,v].
+triple[][] subpatchbegin(triple[][] P, real u, real v)
+{
+ triple[] P0=P[0];
+ triple[] P1=P[1];
+ triple[] P2=P[2];
+ triple[] P3=P[3];
+
+ triple[] c0=split(P0[0],P0[1],P0[2],P0[3],v);
+ triple[] c1=split(P1[0],P1[1],P1[2],P1[3],v);
+ triple[] c2=split(P2[0],P2[1],P2[2],P2[3],v);
+ triple[] c3=split(P3[0],P3[1],P3[2],P3[3],v);
+
+ triple[] c4=split(P0[0],P1[0],P2[0],P3[0],u);
+ triple[] c5=split(c0[0],c1[0],c2[0],c3[0],u);
+ triple[] c6=split(c0[1],c1[1],c2[1],c3[1],u);
+ triple[] c7=split(c0[2],c1[2],c2[2],c3[2],u);
+
+ return new triple[][] {
+ {P0[0],c0[0],c0[1],c0[2]},
+ {c4[0],c5[0],c6[0],c7[0]},
+ {c4[1],c5[1],c6[1],c7[1]},
+ {c4[2],c5[2],c6[2],c7[2]}};
+}
+
+triple[][] subpatch(triple[][] P, pair a, pair b)
+{
+ return subpatchend(subpatchbegin(P,b.x,b.y),a.x/b.x,a.y/b.y);
+}
+
+patch subpatch(patch s, pair a, pair b)
+{
+ assert(a.x >= 0 && a.y >= 0 && b.x <= 1 && b.y <= 1 &&
+ a.x < b.x && a.y < b.y && !s.triangular);
+ return patch(subpatch(s.P,a,b),s.straight,s.planar);
+}
+
+private string triangular=
+ "Intersection of path3 with Bezier triangle is not yet implemented";
+
+// return an array containing the times for one intersection of path p and
+// patch s.
+real[] intersect(path3 p, patch s, real fuzz=-1)
+{
+ if(s.triangular) abort(triangular);
+ return intersect(p,s.P,fuzz);
+}
+
+// return an array containing the times for one intersection of path p and
+// surface s.
+real[] intersect(path3 p, surface s, real fuzz=-1)
+{
+ for(int i=0; i < s.s.length; ++i) {
+ real[] T=intersect(p,s.s[i],fuzz);
+ if(T.length > 0) return T;
+ }
+ return new real[];
+}
+
+// return an array containing all intersection times of path p and patch s.
+real[][] intersections(path3 p, patch s, real fuzz=-1)
+{
+ if(s.triangular) abort(triangular);
+ return sort(intersections(p,s.P,fuzz));
+}
+
+// return an array containing all intersection times of path p and surface s.
+real[][] intersections(path3 p, surface s, real fuzz=-1)
+{
+ real[][] T;
+ if(length(p) < 0) return T;
+ for(int i=0; i < s.s.length; ++i)
+ for(real[] s: intersections(p,s.s[i],fuzz))
+ T.push(s);
+
+ static real Fuzz=1000*realEpsilon;
+ real fuzz=max(10*fuzz,Fuzz*max(abs(min(s)),abs(max(s))));
+
+ // Remove intrapatch duplicate points.
+ for(int i=0; i < T.length; ++i) {
+ triple v=point(p,T[i][0]);
+ for(int j=i+1; j < T.length;) {
+ if(abs(v-point(p,T[j][0])) < fuzz)
+ T.delete(j);
+ else ++j;
+ }
+ }
+ return sort(T);
+}
+
+// return an array containing all intersection points of path p and surface s.
+triple[] intersectionpoints(path3 p, patch s, real fuzz=-1)
+{
+ real[][] t=intersections(p,s,fuzz);
+ return sequence(new triple(int i) {return point(p,t[i][0]);},t.length);
+}
+
+// return an array containing all intersection points of path p and surface s.
+triple[] intersectionpoints(path3 p, surface s, real fuzz=-1)
+{
+ real[][] t=intersections(p,s,fuzz);
+ return sequence(new triple(int i) {return point(p,t[i][0]);},t.length);
+}
+
+// Return true iff the control point bounding boxes of patches p and q overlap.
+bool overlap(triple[][] p, triple[][] q, real fuzz=-1)
+{
+ triple pmin=minbound(p);
+ triple pmax=maxbound(p);
+ triple qmin=minbound(q);
+ triple qmax=maxbound(q);
+
+ if(fuzz == -1)
+ fuzz=1000*realEpsilon*max(abs(pmin),abs(pmax),abs(qmin),abs(qmax));
+
+ return
+ pmax.x+fuzz >= qmin.x &&
+ pmax.y+fuzz >= qmin.y &&
+ pmax.z+fuzz >= qmin.z &&
+ qmax.x+fuzz >= pmin.x &&
+ qmax.y+fuzz >= pmin.y &&
+ qmax.z+fuzz >= pmin.z; // Overlapping bounding boxes?
+}
+
+triple point(patch s, real u, real v)
+{
+ return s.point(u,v);
+}
+
+real PRCshininess(real shininess)
+{
+ // Empirical translation table from Phong-Blinn to PRC shininess model:
+ static real[] x={0.015,0.025,0.05,0.07,0.1,0.14,0.23,0.5,0.65,0.75,0.85,
+ 0.875,0.9,1};
+ static real[] y={0.05,0.1,0.15,0.2,0.25,0.3,0.4,0.5,0.55,0.6,0.7,0.8,0.9,1};
+ static realfunction s=fspline(x,y,monotonic);
+ return s(shininess);
+}
+
+struct interaction
+{
+ int type;
+ bool targetsize;
+ void operator init(int type, bool targetsize=false) {
+ this.type=type;
+ this.targetsize=targetsize;
+ }
+}
+
+restricted interaction Embedded=interaction(0);
+restricted interaction Billboard=interaction(1);
+
+interaction LabelInteraction()
+{
+ return settings.autobillboard ? Billboard : Embedded;
+}
+
+material material(material m, light light)
+{
+ return light.on() || invisible((pen) m) ? m : emissive(m);
+}
+
+void draw3D(frame f, int type=0, patch s, triple center=O, material m,
+ light light=currentlight, interaction interaction=Embedded,
+ bool prc=true)
+{
+ if(s.colors.length > 0)
+ m=mean(s.colors);
+ m=material(m,light);
+ real PRCshininess;
+ if(prc())
+ PRCshininess=PRCshininess(m.shininess);
+
+ (s.triangular ? drawbeziertriangle : draw)
+ (f,s.P,center,s.straight && s.planar,m.p,m.opacity,m.shininess,
+ m.metallic,m.fresnel0,PRCshininess,s.colors,interaction.type,prc);
+}
+
+int computeNormals(triple[] v, int[][] vi, triple[] n, int[][] ni)
+{
+ triple lastnormal=O;
+ for(int i=0; i < vi.length; ++i) {
+ int[] vii=vi[i];
+ int[] nii=ni[i];
+ triple normal=normal(new triple[] {v[vii[0]],v[vii[1]],v[vii[2]]});
+ if(normal != lastnormal || n.length == 0) {
+ n.push(normal);
+ lastnormal=normal;
+ }
+ nii[0]=nii[1]=nii[2]=n.length-1;
+ }
+ return ni.length;
+}
+
+// Draw triangles on a frame.
+void draw(frame f, triple[] v, int[][] vi,
+ triple[] n={}, int[][] ni={}, material m=currentpen, pen[] p={},
+ int[][] pi={}, light light=currentlight)
+{
+ bool normals=ni.length > 0;
+ if(!normals) {
+ ni=new int[vi.length][3];
+ normals=computeNormals(v,vi,n,ni) > 0;
+ }
+ if(p.length > 0)
+ m=mean(p);
+ m=material(m,light);
+ real PRCshininess;
+ if(prc())
+ PRCshininess=PRCshininess(m.shininess);
+ draw(f,v,vi,n,ni,m.p,m.opacity,m.shininess,m.metallic,m.fresnel0,
+ PRCshininess,p,pi);
+}
+
+// Draw triangles on a picture.
+void draw(picture pic=currentpicture, triple[] v, int[][] vi,
+ triple[] n={}, int[][] ni={}, material m=currentpen, pen[] p={},
+ int[][] pi={}, light light=currentlight)
+{
+ bool normals=ni.length > 0;
+ if(!normals) {
+ ni=new int[vi.length][3];
+ normals=computeNormals(v,vi,n,ni) > 0;
+ }
+ bool colors=pi.length > 0;
+
+ pic.add(new void(frame f, transform3 t, picture pic, projection P) {
+ triple[] v=t*v;
+ triple[] n=t*n;
+
+ if(is3D()) {
+ draw(f,v,vi,n,ni,m,p,pi,light);
+ if(pic != null) {
+ for(int[] vii : vi)
+ for(int viij : vii)
+ pic.addPoint(project(v[viij],P));
+ }
+ } else if(pic != null) {
+ static int[] edges={0,0,1};
+ if(colors) {
+ for(int i=0; i < vi.length; ++i) {
+ int[] vii=vi[i];
+ int[] pii=pi[i];
+ gouraudshade(pic,project(v[vii[0]],P)--project(v[vii[1]],P)--
+ project(v[vii[2]],P)--cycle,
+ new pen[] {p[pii[0]],p[pii[1]],p[pii[2]]},edges);
+ }
+ } else {
+ if(normals) {
+ for(int i=0; i < vi.length; ++i) {
+ int[] vii=vi[i];
+ int[] nii=ni[i];
+ gouraudshade(pic,project(v[vii[0]],P)--project(v[vii[1]],P)--
+ project(v[vii[2]],P)--cycle,
+ new pen[] {color(n[nii[0]],m,light),
+ color(n[nii[1]],m,light),
+ color(n[nii[2]],m,light)},edges);
+ }
+ } else {
+ for(int i=0; i < vi.length; ++i) {
+ int[] vii=vi[i];
+ path g=project(v[vii[0]],P)--project(v[vii[1]],P)--
+ project(v[vii[2]],P)--cycle;
+ pen p=color(n[ni[i][0]],m,light);
+ fill(pic,g,p);
+ if(opacity(m.diffuse()) == 1) // Fill subdivision cracks
+ draw(pic,g,p);
+ }
+ }
+ }
+ }
+ },true);
+
+ for(int[] vii : vi)
+ for(int viij : vii)
+ pic.addPoint(v[viij]);
+}
+
+void drawPRCsphere(frame f, transform3 t=identity4, bool half=false,
+ material m, light light=currentlight,
+ render render=defaultrender)
+{
+ m=material(m,light);
+ drawPRCsphere(f,t,half,m.p,m.opacity,PRCshininess(m.shininess),
+ render.sphere);
+}
+
+void drawPRCcylinder(frame f, transform3 t=identity4, material m,
+ light light=currentlight)
+{
+ m=material(m,light);
+ drawPRCcylinder(f,t,m.p,m.opacity,PRCshininess(m.shininess));
+}
+
+void drawPRCdisk(frame f, transform3 t=identity4, material m,
+ light light=currentlight)
+{
+ m=material(m,light);
+ drawPRCdisk(f,t,m.p,m.opacity,PRCshininess(m.shininess));
+}
+
+void drawPRCtube(frame f, path3 center, path3 g, material m,
+ light light=currentlight)
+{
+ m=material(m,light);
+ drawPRCtube(f,center,g,m.p,m.opacity,PRCshininess(m.shininess));
+}
+
+void tensorshade(transform t=identity(), frame f, patch s,
+ material m, light light=currentlight, projection P)
+{
+
+ pen[] p;
+ if(s.triangular) {
+ p=s.colorstriangular(m,light);
+ p.push(p[0]);
+ s=tensor(s);
+ } else p=s.colors(m,light);
+ tensorshade(f,box(t*s.min(P),t*s.max(P)),m.diffuse(),
+ p,t*project(s.external(),P,1),t*project(s.internal(),P));
+}
+
+restricted pen[] nullpens={nullpen};
+nullpens.cyclic=true;
+
+void draw(transform t=identity(), frame f, surface s, int nu=1, int nv=1,
+ material[] surfacepen, pen[] meshpen=nullpens,
+ light light=currentlight, light meshlight=nolight, string name="",
+ render render=defaultrender, projection P=currentprojection)
+{
+ bool is3D=is3D();
+ if(is3D) {
+ bool group=name != "" || render.defaultnames;
+ if(group)
+ begingroup3(f,name == "" ? "surface" : name,render);
+
+ // Sort patches by mean distance from camera
+ triple camera=P.camera;
+ if(P.infinity) {
+ triple m=min(s);
+ triple M=max(s);
+ camera=P.target+camerafactor*(abs(M-m)+abs(m-P.target))*unit(P.vector());
+ }
+
+ real[][] depth=new real[s.s.length][];
+ for(int i=0; i < depth.length; ++i)
+ depth[i]=new real[] {dot(P.normal,camera-s.s[i].cornermean()),i};
+
+ depth=sort(depth);
+
+ for(int p=depth.length-1; p >= 0; --p) {
+ real[] a=depth[p];
+ int k=round(a[1]);
+ draw3D(f,s.s[k],surfacepen[k],light);
+ }
+
+ if(group)
+ endgroup3(f);
+
+ pen modifiers=thin()+squarecap;
+ for(int p=depth.length-1; p >= 0; --p) {
+ real[] a=depth[p];
+ int k=round(a[1]);
+ patch S=s.s[k];
+ pen meshpen=meshpen[k];
+ if(!invisible(meshpen) && !S.triangular) {
+ if(group)
+ begingroup3(f,meshname(name),render);
+ meshpen=modifiers+meshpen;
+ real step=nu == 0 ? 0 : 1/nu;
+ for(int i=0; i <= nu; ++i)
+ draw(f,S.uequals(i*step),meshpen,meshlight,partname(i,render),
+ render);
+ step=nv == 0 ? 0 : 1/nv;
+ for(int j=0; j <= nv; ++j)
+ draw(f,S.vequals(j*step),meshpen,meshlight,partname(j,render),
+ render);
+ if(group)
+ endgroup3(f);
+ }
+ }
+ }
+ if(!is3D || settings.render == 0) {
+ begingroup(f);
+ // Sort patches by mean distance from camera
+ triple camera=P.camera;
+ if(P.infinity) {
+ triple m=min(s);
+ triple M=max(s);
+ camera=P.target+camerafactor*(abs(M-m)+abs(m-P.target))*unit(P.vector());
+ }
+
+ real[][] depth=new real[s.s.length][];
+ for(int i=0; i < depth.length; ++i)
+ depth[i]=new real[] {dot(P.normal,camera-s.s[i].cornermean()),i};
+
+ depth=sort(depth);
+
+ light.T=shiftless(P.T.modelview);
+
+ // Draw from farthest to nearest
+ for(int p=depth.length-1; p >= 0; --p) {
+ real[] a=depth[p];
+ int k=round(a[1]);
+ tensorshade(t,f,s.s[k],surfacepen[k],light,P);
+ pen meshpen=meshpen[k];
+ if(!invisible(meshpen))
+ draw(f,t*project(s.s[k].external(),P),meshpen);
+ }
+ endgroup(f);
+ }
+}
+
+void draw(transform t=identity(), frame f, surface s, int nu=1, int nv=1,
+ material surfacepen=currentpen, pen meshpen=nullpen,
+ light light=currentlight, light meshlight=nolight, string name="",
+ render render=defaultrender, projection P=currentprojection)
+{
+ material[] surfacepen={surfacepen};
+ pen[] meshpen={meshpen};
+ surfacepen.cyclic=true;
+ meshpen.cyclic=true;
+ draw(t,f,s,nu,nv,surfacepen,meshpen,light,meshlight,name,render,P);
+}
+
+void draw(picture pic=currentpicture, surface s, int nu=1, int nv=1,
+ material[] surfacepen, pen[] meshpen=nullpens,
+ light light=currentlight, light meshlight=nolight, string name="",
+ render render=defaultrender)
+{
+ if(s.empty()) return;
+
+ bool cyclic=surfacepen.cyclic;
+ surfacepen=copy(surfacepen);
+ surfacepen.cyclic=cyclic;
+ cyclic=meshpen.cyclic;
+ meshpen=copy(meshpen);
+ meshpen.cyclic=cyclic;
+
+ pic.add(new void(frame f, transform3 t, picture pic, projection P) {
+ surface S=t*s;
+ if(is3D())
+ draw(f,S,nu,nv,surfacepen,meshpen,light,meshlight,name,render);
+ if(pic != null) {
+ pic.add(new void(frame f, transform T) {
+ draw(T,f,S,nu,nv,surfacepen,meshpen,light,meshlight,P);
+ },true);
+ pic.addPoint(min(S,P));
+ pic.addPoint(max(S,P));
+ }
+ },true);
+ pic.addPoint(min(s));
+ pic.addPoint(max(s));
+
+ pen modifiers;
+ if(is3D()) modifiers=thin()+squarecap;
+ for(int k=0; k < s.s.length; ++k) {
+ patch S=s.s[k];
+ pen meshpen=meshpen[k];
+ if(!invisible(meshpen) && !S.triangular) {
+ meshpen=modifiers+meshpen;
+ real step=nu == 0 ? 0 : 1/nu;
+ for(int i=0; i <= nu; ++i)
+ addPath(pic,s.s[k].uequals(i*step),meshpen);
+ step=nv == 0 ? 0 : 1/nv;
+ for(int j=0; j <= nv; ++j)
+ addPath(pic,s.s[k].vequals(j*step),meshpen);
+ }
+ }
+}
+
+void draw(picture pic=currentpicture, surface s, int nu=1, int nv=1,
+ material surfacepen=currentpen, pen meshpen=nullpen,
+ light light=currentlight, light meshlight=nolight, string name="",
+ render render=defaultrender)
+{
+ material[] surfacepen={surfacepen};
+ pen[] meshpen={meshpen};
+ surfacepen.cyclic=true;
+ meshpen.cyclic=true;
+ draw(pic,s,nu,nv,surfacepen,meshpen,light,meshlight,name,render);
+}
+
+void draw(picture pic=currentpicture, surface s, int nu=1, int nv=1,
+ material[] surfacepen, pen meshpen,
+ light light=currentlight, light meshlight=nolight, string name="",
+ render render=defaultrender)
+{
+ pen[] meshpen={meshpen};
+ meshpen.cyclic=true;
+ draw(pic,s,nu,nv,surfacepen,meshpen,light,meshlight,name,render);
+}
+
+surface extrude(path3 p, path3 q)
+{
+ static patch[] allocate;
+ return surface(...sequence(new patch(int i) {
+ return patch(subpath(p,i,i+1)--subpath(q,i+1,i)--cycle);
+ },length(p)));
+}
+
+surface extrude(path3 p, triple axis=Z)
+{
+ return extrude(p,shift(axis)*p);
+}
+
+surface extrude(path p, triple plane(pair)=XYplane, triple axis=Z)
+{
+ return extrude(path3(p,plane),axis);
+}
+
+surface extrude(explicit path[] p, triple axis=Z)
+{
+ surface s;
+ for(path g:p)
+ s.append(extrude(g,axis));
+ return s;
+}
+
+triple rectify(triple dir)
+{
+ real scale=max(abs(dir.x),abs(dir.y),abs(dir.z));
+ if(scale != 0) dir *= 0.5/scale;
+ dir += (0.5,0.5,0.5);
+ return dir;
+}
+
+path3[] align(path3[] g, transform3 t=identity4, triple position,
+ triple align, pen p=currentpen)
+{
+ if(determinant(t) == 0 || g.length == 0) return g;
+ triple m=min(g);
+ triple dir=rectify(inverse(t)*-align);
+ triple a=m+realmult(dir,max(g)-m);
+ return shift(position+align*labelmargin(p))*t*shift(-a)*g;
+}
+
+surface align(surface s, transform3 t=identity4, triple position,
+ triple align, pen p=currentpen)
+{
+ if(determinant(t) == 0 || s.s.length == 0) return s;
+ triple m=min(s);
+ triple dir=rectify(inverse(t)*-align);
+ triple a=m+realmult(dir,max(s)-m);
+ return shift(position+align*labelmargin(p))*t*shift(-a)*s;
+}
+
+surface surface(Label L, triple position=O, bool bbox=false)
+{
+ surface s=surface(texpath(L,bbox=bbox));
+ return L.align.is3D ? align(s,L.T3,position,L.align.dir3,L.p) :
+ shift(position)*L.T3*s;
+}
+
+private path[] path(Label L, pair z=0, projection P)
+{
+ path[] g=texpath(L,bbox=P.bboxonly);
+ return L.align.is3D ? align(g,z,project(L.align.dir3,P)-project(O,P),L.p) :
+ shift(z)*g;
+}
+
+transform3 alignshift(path3[] g, transform3 t=identity4, triple position,
+ triple align)
+{
+ if(determinant(t) == 0) return identity4;
+ triple m=min(g);
+ triple dir=rectify(inverse(t)*-align);
+ triple a=m+realmult(dir,max(g)-m);
+ return shift(-a);
+}
+
+transform3 alignshift(surface s, transform3 t=identity4, triple position,
+ triple align)
+{
+ if(determinant(t) == 0) return identity4;
+ triple m=min(s);
+ triple dir=rectify(inverse(t)*-align);
+ triple a=m+realmult(dir,max(s)-m);
+ return shift(-a);
+}
+
+transform3 aligntransform(path3[] g, transform3 t=identity4, triple position,
+ triple align, pen p=currentpen)
+{
+ if(determinant(t) == 0) return identity4;
+ triple m=min(g);
+ triple dir=rectify(inverse(t)*-align);
+ triple a=m+realmult(dir,max(g)-m);
+ return shift(position+align*labelmargin(p))*t*shift(-a);
+}
+
+transform3 aligntransform(surface s, transform3 t=identity4, triple position,
+ triple align, pen p=currentpen)
+{
+ if(determinant(t) == 0) return identity4;
+ triple m=min(s);
+ triple dir=rectify(inverse(t)*-align);
+ triple a=m+realmult(dir,max(s)-m);
+ return shift(position+align*labelmargin(p))*t*shift(-a);
+}
+
+void label(frame f, Label L, triple position, align align=NoAlign,
+ pen p=currentpen, light light=nolight,
+ string name="", render render=defaultrender,
+ interaction interaction=LabelInteraction(),
+ projection P=currentprojection)
+{
+ Label L=L.copy();
+ L.align(align);
+ L.p(p);
+ if(interaction.targetsize && settings.render != 0)
+ L.T=L.T*scale(abs(P.camera-position)/abs(P.vector()));
+ transform3 T=transform3(P);
+ if(L.defaulttransform3)
+ L.T3=T;
+
+ if(is3D()) {
+ bool lighton=light.on();
+ if(name == "") name=L.s;
+ if(prc() && interaction.type == Billboard.type) {
+ surface s=surface(texpath(L));
+ transform3 centering=L.align.is3D ?
+ alignshift(s,L.T3,position,L.align.dir3) : identity4;
+ transform3 positioning=
+ shift(L.align.is3D ? position+L.align.dir3*labelmargin(L.p) : position);
+ frame f1,f2,f3;
+ begingroup3(f1,name,render);
+ if(L.defaulttransform3)
+ begingroup3(f3,render,position,interaction.type);
+ else {
+ begingroup3(f2,render,position,interaction.type);
+ begingroup3(f3,render,position);
+ }
+ for(patch S : s.s) {
+ S=centering*S;
+ draw3D(f3,S,position,L.p,light,interaction);
+ // Fill subdivision cracks
+ if(render.labelfill && opacity(L.p) == 1 && !lighton)
+ _draw(f3,S.external(),position,L.p,interaction.type);
+ }
+ endgroup3(f3);
+ if(L.defaulttransform3)
+ add(f1,T*f3);
+ else {
+ add(f2,inverse(T)*L.T3*f3);
+ endgroup3(f2);
+ add(f1,T*f2);
+ }
+ endgroup3(f1);
+ add(f,positioning*f1);
+ } else {
+ begingroup3(f,name,render);
+ for(patch S : surface(L,position).s) {
+ triple V=L.align.is3D ? position+L.align.dir3*labelmargin(L.p) :
+ position;
+ draw3D(f,S,V,L.p,light,interaction);
+ // Fill subdivision cracks
+ if(render.labelfill && opacity(L.p) == 1 && !lighton)
+ _draw(f,S.external(),V,L.p,interaction.type);
+ }
+ endgroup3(f);
+ }
+ } else {
+ pen p=color(L.T3*Z,L.p,light,shiftless(P.T.modelview));
+ if(L.defaulttransform3) {
+ if(L.filltype == NoFill)
+ fill(f,path(L,project(position,P.t),P),p);
+ else {
+ frame d;
+ fill(d,path(L,project(position,P.t),P),p);
+ add(f,d,L.filltype);
+ }
+ } else
+ for(patch S : surface(L,position).s)
+ fill(f,project(S.external(),P,1),p);
+ }
+}
+
+void label(picture pic=currentpicture, Label L, triple position,
+ align align=NoAlign, pen p=currentpen,
+ light light=nolight, string name="",
+ render render=defaultrender,
+ interaction interaction=LabelInteraction())
+{
+ Label L=L.copy();
+ L.align(align);
+ L.p(p);
+ L.position(0);
+
+ pic.add(new void(frame f, transform3 t, picture pic2, projection P) {
+ // Handle relative projected 3D alignments.
+ Label L=L.copy();
+ triple v=t*position;
+ if(!align.is3D && L.align.relative && L.align.dir3 != O &&
+ determinant(P.t) != 0)
+ L.align(L.align.dir*unit(project(v+L.align.dir3,P.t)-project(v,P.t)));
+
+ if(interaction.targetsize && settings.render != 0)
+ L.T=L.T*scale(abs(P.camera-v)/abs(P.vector()));
+ transform3 T=transform3(P);
+ if(L.defaulttransform3)
+ L.T3=T;
+
+ if(is3D()) {
+ bool lighton=light.on();
+ if(name == "") name=L.s;
+ if(prc() && interaction.type == Billboard.type) {
+ surface s=surface(texpath(L,bbox=P.bboxonly));
+ transform3 centering=L.align.is3D ?
+ alignshift(s,L.T3,v,L.align.dir3) : identity4;
+ transform3 positioning=
+ shift(L.align.is3D ? v+L.align.dir3*labelmargin(L.p) : v);
+ frame f1,f2,f3;
+ begingroup3(f1,name,render);
+ if(L.defaulttransform3)
+ begingroup3(f3,render,v,interaction.type);
+ else {
+ begingroup3(f2,render,v,interaction.type);
+ begingroup3(f3,render,v);
+ }
+ for(patch S : s.s) {
+ S=centering*S;
+ draw3D(f3,S,v,L.p,light,interaction);
+ // Fill subdivision cracks
+ if(render.labelfill && opacity(L.p) == 1 && !lighton)
+ _draw(f3,S.external(),v,L.p,interaction.type);
+ }
+ endgroup3(f3);
+ if(L.defaulttransform3)
+ add(f1,T*f3);
+ else {
+ add(f2,inverse(T)*L.T3*f3);
+ endgroup3(f2);
+ add(f1,T*f2);
+ }
+ endgroup3(f1);
+ add(f,positioning*f1);
+ } else {
+ begingroup3(f,name,render);
+ for(patch S : surface(L,v,bbox=P.bboxonly).s) {
+ triple V=L.align.is3D ? v+L.align.dir3*labelmargin(L.p) : v;
+ draw3D(f,S,V,L.p,light,interaction);
+ // Fill subdivision cracks
+ if(render.labelfill && opacity(L.p) == 1 && !lighton)
+ _draw(f,S.external(),V,L.p,interaction.type);
+ }
+ endgroup3(f);
+ }
+ }
+
+ if(pic2 != null) {
+ pen p=color(L.T3*Z,L.p,light,shiftless(P.T.modelview));
+ if(L.defaulttransform3) {
+ if(L.filltype == NoFill)
+ fill(project(v,P.t),pic2,path(L,P),p);
+ else {
+ picture d;
+ fill(project(v,P.t),d,path(L,P),p);
+ add(pic2,d,L.filltype);
+ }
+ } else
+ pic2.add(new void(frame f, transform T) {
+ for(patch S : surface(L,v).s)
+ fill(f,T*project(S.external(),P,1),p);
+ });
+ }
+
+ },!L.defaulttransform3);
+
+ Label L=L.copy();
+
+ if(interaction.targetsize && settings.render != 0)
+ L.T=L.T*scale(abs(currentprojection.camera-position)/
+ abs(currentprojection.vector()));
+ path[] g=texpath(L,bbox=true);
+ if(g.length == 0 || (g.length == 1 && size(g[0]) == 0)) return;
+ if(L.defaulttransform3)
+ L.T3=transform3(currentprojection);
+ path3[] G=path3(g);
+ G=L.align.is3D ? align(G,L.T3,O,L.align.dir3,L.p) : L.T3*G;
+ pic.addBox(position,position,min(G),max(G));
+}
+
+void label(picture pic=currentpicture, Label L, path3 g, align align=NoAlign,
+ pen p=currentpen, light light=nolight, string name="",
+ interaction interaction=LabelInteraction())
+{
+ Label L=L.copy();
+ L.align(align);
+ L.p(p);
+ bool relative=L.position.relative;
+ real position=L.position.position.x;
+ if(L.defaultposition) {relative=true; position=0.5;}
+ if(relative) position=reltime(g,position);
+ if(L.align.default) {
+ align a;
+ a.init(-I*(position <= sqrtEpsilon ? S :
+ position >= length(g)-sqrtEpsilon ? N : E),relative=true);
+ a.dir3=dir(g,position); // Pass 3D direction via unused field.
+ L.align(a);
+ }
+ label(pic,L,point(g,position),light,name,interaction);
+}
+
+surface extrude(Label L, triple axis=Z)
+{
+ Label L=L.copy();
+ path[] g=texpath(L);
+ surface S=extrude(g,axis);
+ surface s=surface(g);
+ S.append(s);
+ S.append(shift(axis)*s);
+ return S;
+}
+
+restricted surface nullsurface;
+
+// Embed a Label onto a surface.
+surface surface(Label L, surface s, real uoffset, real voffset,
+ real height=0, bool bottom=true, bool top=true)
+{
+ int nu=s.index.length;
+ int nv;
+ if(nu == 0) nu=nv=1;
+ else {
+ nv=s.index[0].length;
+ if(nv == 0) nv=1;
+ }
+
+ path[] g=texpath(L);
+ pair m=min(g);
+ pair M=max(g);
+ pair lambda=inverse(L.T*scale(nu-epsilon,nv-epsilon))*(M-m);
+ lambda=(abs(lambda.x),abs(lambda.y));
+ path[] G=bezulate(g);
+
+ path3 transpath(path p, real height) {
+ return path3(unstraighten(p),new triple(pair z) {
+ real u=uoffset+(z.x-m.x)/lambda.x;
+ real v=voffset+(z.y-m.y)/lambda.y;
+ if(((u < 0 || u >= nu) && !s.ucyclic()) ||
+ ((v < 0 || v >= nv) && !s.vcyclic())) {
+ warning("cannotfit","cannot fit string to surface");
+ u=v=0;
+ }
+ return s.point(u,v)+height*unit(s.normal(u,v));
+ });
+ }
+
+ surface s;
+ for(path p : G) {
+ for(path g : regularize(p)) {
+ path3 b;
+ bool extrude=height > 0;
+ if(bottom || extrude)
+ b=transpath(g,0);
+ if(bottom) s.s.push(patch(b));
+ if(top || extrude) {
+ path3 h=transpath(g,height);
+ if(top) s.s.push(patch(h));
+ if(extrude) s.append(extrude(b,h));
+ }
+ }
+ }
+ return s;
+}
+
+private real a=4/3*(sqrt(2)-1);
+private transform3 t1=rotate(90,O,Z);
+private transform3 t2=t1*t1;
+private transform3 t3=t2*t1;
+private transform3 i=xscale3(-1)*zscale3(-1);
+
+restricted patch octant1=patch(X{Y}..{-X}Y{Z}..{-Y}Z..Z{X}..{-Z}cycle,
+ new triple[] {(1,a,a),(a,1,a),(a^2,a,1),
+ (a,a^2,1)});
+
+restricted surface unithemisphere=surface(octant1,t1*octant1,t2*octant1,
+ t3*octant1);
+restricted surface unitsphere=surface(octant1,t1*octant1,t2*octant1,t3*octant1,
+ i*octant1,i*t1*octant1,i*t2*octant1,
+ i*t3*octant1);
+
+restricted patch unitfrustum(real t1, real t2)
+{
+ real s1=interp(t1,t2,1/3);
+ real s2=interp(t1,t2,2/3);
+ return patch(interp(Z,X,t2){Y}..{-X}interp(Z,Y,t2)--interp(Z,Y,t1){X}..{-Y}
+ interp(Z,X,t1)--cycle,
+ new triple[] {(s2,s2*a,1-s2),(s2*a,s2,1-s2),(s1*a,s1,1-s1),
+ (s1,s1*a,1-s1)});
+}
+
+// Return a unitcone constructed from n frusta (the final one being degenerate)
+surface unitcone(int n=6)
+{
+ surface unitcone;
+ unitcone.s=new patch[4*n];
+ real r=1/3;
+ for(int i=0; i < n; ++i) {
+ patch s=unitfrustum(i < n-1 ? r^(i+1) : 0,r^i);
+ unitcone.s[i]=s;
+ unitcone.s[n+i]=t1*s;
+ unitcone.s[2n+i]=t2*s;
+ unitcone.s[3n+i]=t3*s;
+ }
+ return unitcone;
+}
+
+restricted surface unitcone=unitcone();
+restricted surface unitsolidcone=surface(patch(unitcircle3)...unitcone.s);
+
+// Construct an approximate cone over an arbitrary base.
+surface cone(path3 base, triple vertex) {return extrude(base,vertex--cycle);}
+
+private patch unitcylinder1=patch(X{Y}..{-X}Y--Y+Z{X}..{-Y}X+Z--cycle);
+
+restricted surface unitcylinder=surface(unitcylinder1,t1*unitcylinder1,
+ t2*unitcylinder1,t3*unitcylinder1);
+
+private patch unitplane=patch(new triple[] {O,X,X+Y,Y});
+restricted surface unitcube=surface(reverse(unitplane),
+ rotate(90,O,X)*unitplane,
+ rotate(-90,O,Y)*unitplane,
+ shift(Z)*unitplane,
+ rotate(90,X,X+Y)*unitplane,
+ rotate(-90,Y,X+Y)*unitplane);
+restricted surface unitplane=surface(unitplane);
+restricted surface unitdisk=surface(unitcircle3);
+
+void dot(frame f, triple v, material p=currentpen,
+ light light=nolight, string name="",
+ render render=defaultrender, projection P=currentprojection)
+{
+ pen q=(pen) p;
+ if(is3D()) {
+ bool group=name != "" || render.defaultnames;
+ if(group)
+ begingroup3(f,name == "" ? "dot" : name,render);
+ real size=0.5*linewidth(dotsize(q)+q);
+ transform3 T=shift(v)*scale3(size);
+ for(patch s : unitsphere.s)
+ draw3D(f,T*s,v,p,light,prc=false);
+ if(prc())
+ drawPRCsphere(f,T,p,light);
+ if(group)
+ endgroup3(f);
+ } else dot(f,project(v,P.t),q);
+}
+
+void dot(frame f, triple[] v, material p=currentpen, light light=nolight,
+ string name="", render render=defaultrender,
+ projection P=currentprojection)
+{
+ if(v.length > 0) {
+ // Remove duplicate points.
+ v=sort(v,lexorder);
+
+ triple last=v[0];
+ dot(f,last,p,light,name,render,P);
+ for(int i=1; i < v.length; ++i) {
+ triple V=v[i];
+ if(V != last) {
+ dot(f,V,p,light,name,render,P);
+ last=V;
+ }
+ }
+ }
+}
+
+void dot(frame f, path3 g, material p=currentpen, light light=nolight,
+ string name="", render render=defaultrender,
+ projection P=currentprojection)
+{
+ dot(f,sequence(new triple(int i) {return point(g,i);},size(g)),
+ p,light,name,render,P);
+}
+
+void dot(frame f, path3[] g, material p=currentpen, light light=nolight,
+ string name="", render render=defaultrender,
+ projection P=currentprojection)
+{
+ int sum;
+ for(path3 G : g)
+ sum += size(G);
+ int i,j;
+ dot(f,sequence(new triple(int) {
+ while(j >= size(g[i])) {
+ ++i;
+ j=0;
+ }
+ triple v=point(g[i],j);
+ ++j;
+ return v;
+ },sum),p,light,name,render,P);
+}
+
+void dot(picture pic=currentpicture, triple v, material p=currentpen,
+ light light=nolight, string name="", render render=defaultrender)
+{
+ pen q=(pen) p;
+ real size=0.5*linewidth(dotsize(q)+q);
+ pic.add(new void(frame f, transform3 t, picture pic, projection P) {
+ triple V=t*v;
+ if(is3D()) {
+ bool group=name != "" || render.defaultnames;
+ if(group)
+ begingroup3(f,name == "" ? "dot" : name,render);
+ transform3 T=shift(V)*scale3(size);
+ for(patch s : unitsphere.s)
+ draw3D(f,T*s,V,p,light,prc=false);
+ if(prc())
+ drawPRCsphere(f,T,p,light,render);
+ if(group)
+ endgroup3(f);
+ }
+ if(pic != null)
+ dot(pic,project(V,P.t),q);
+ },true);
+ triple R=size*(1,1,1);
+ pic.addBox(v,v,-R,R);
+}
+
+void dot(picture pic=currentpicture, triple[] v, material p=currentpen,
+ light light=nolight, string name="", render render=defaultrender)
+{
+ if(v.length > 0) {
+ // Remove duplicate points.
+ v=sort(v,lexorder);
+
+ triple last=v[0];
+ bool group=name != "" || render.defaultnames;
+ if(group)
+ begingroup3(pic,name == "" ? "dots" : name,render);
+ dot(pic,last,p,light,partname(0,render),render);
+ int k=0;
+ for(int i=1; i < v.length; ++i) {
+ triple V=v[i];
+ if(V != last) {
+ dot(pic,V,p,light,partname(++k,render),render);
+ last=V;
+ }
+ }
+ if(group)
+ endgroup3(pic);
+ }
+}
+
+void dot(picture pic=currentpicture, explicit path3 g, material p=currentpen,
+ light light=nolight, string name="",
+ render render=defaultrender)
+{
+ dot(pic,sequence(new triple(int i) {return point(g,i);},size(g)),
+ p,light,name,render);
+}
+
+void dot(picture pic=currentpicture, path3[] g, material p=currentpen,
+ light light=nolight, string name="", render render=defaultrender)
+{
+ int sum;
+ for(path3 G : g)
+ sum += size(G);
+ int i,j;
+ dot(pic,sequence(new triple(int) {
+ while(j >= size(g[i])) {
+ ++i;
+ j=0;
+ }
+ triple v=point(g[i],j);
+ ++j;
+ return v;
+ },sum),p,light,name,render);
+}
+
+void dot(picture pic=currentpicture, Label L, triple v, align align=NoAlign,
+ string format=defaultformat, material p=currentpen,
+ light light=nolight, string name="", render render=defaultrender)
+{
+ Label L=L.copy();
+ if(L.s == "") {
+ if(format == "") format=defaultformat;
+ L.s="("+format(format,v.x)+","+format(format,v.y)+","+
+ format(format,v.z)+")";
+ }
+ L.align(align,E);
+ L.p((pen) p);
+ dot(pic,v,p,light,name,render);
+ label(pic,L,v,render);
+}
+
+void pixel(picture pic=currentpicture, triple v, pen p=currentpen,
+ real width=1)
+{
+ real h=0.5*width;
+ pic.add(new void(frame f, transform3 t, picture pic, projection P) {
+ triple V=t*v;
+ if(is3D())
+ drawpixel(f,V,p,width);
+ if(pic != null) {
+ triple R=h*unit(cross(unit(P.vector()),P.up));
+ pair z=project(V,P.t);
+ real h=0.5*abs(project(V+R,P.t)-project(V-R,P.t));
+ pair r=h*(1,1)/mm;
+ fill(pic,box(z-r,z+r),p,false);
+ }
+ },true);
+ triple R=h*(1,1,1);
+ pic.addBox(v,v,-R,R);
+}
+
+pair minbound(triple[] A, projection P)
+{
+ pair b=project(A[0],P);
+ for(triple v : A)
+ b=minbound(b,project(v,P.t));
+ return b;
+}
+
+pair maxbound(triple[] A, projection P)
+{
+ pair b=project(A[0],P);
+ for(triple v : A)
+ b=maxbound(b,project(v,P.t));
+ return b;
+}
+
+pair minbound(triple[][] A, projection P)
+{
+ pair b=project(A[0][0],P);
+ for(triple[] a : A) {
+ for(triple v : a) {
+ b=minbound(b,project(v,P.t));
+ }
+ }
+ return b;
+}
+
+pair maxbound(triple[][] A, projection P)
+{
+ pair b=project(A[0][0],P);
+ for(triple[] a : A) {
+ for(triple v : a) {
+ b=maxbound(b,project(v,P.t));
+ }
+ }
+ return b;
+}
+
+triple[][] operator / (triple[][] a, real[][] b)
+{
+ triple[][] A=new triple[a.length][];
+ for(int i=0; i < a.length; ++i) {
+ triple[] ai=a[i];
+ real[] bi=b[i];
+ A[i]=sequence(new triple(int j) {return ai[j]/bi[j];},ai.length);
+ }
+ return A;
+}
+
+// Draw a NURBS curve.
+void draw(picture pic=currentpicture, triple[] P, real[] knot,
+ real[] weights=new real[], pen p=currentpen, string name="",
+ render render=defaultrender)
+{
+ P=copy(P);
+ knot=copy(knot);
+ weights=copy(weights);
+ pic.add(new void(frame f, transform3 t, picture pic, projection Q) {
+ if(is3D()) {
+ triple[] P=t*P;
+ bool group=name != "" || render.defaultnames;
+ if(group)
+ begingroup3(f,name == "" ? "curve" : name,render);
+ draw(f,P,knot,weights,p);
+ if(group)
+ endgroup3(f);
+ if(pic != null)
+ pic.addBox(minbound(P,Q),maxbound(P,Q));
+ }
+ },true);
+ pic.addBox(minbound(P),maxbound(P));
+}
+
+// Draw a NURBS surface.
+void draw(picture pic=currentpicture, triple[][] P, real[] uknot, real[] vknot,
+ real[][] weights=new real[][], material m=currentpen,
+ pen[] colors=new pen[], light light=currentlight, string name="",
+ render render=defaultrender)
+{
+ if(colors.length > 0)
+ m=mean(colors);
+ m=material(m,light);
+ bool lighton=light.on();
+ P=copy(P);
+ uknot=copy(uknot);
+ vknot=copy(vknot);
+ weights=copy(weights);
+ colors=copy(colors);
+ pic.add(new void(frame f, transform3 t, picture pic, projection Q) {
+ if(is3D()) {
+ bool group=name != "" || render.defaultnames;
+ if(group)
+ begingroup3(f,name == "" ? "surface" : name,render);
+ triple[][] P=t*P;
+ real PRCshininess;
+ if(prc())
+ PRCshininess=PRCshininess(m.shininess);
+ draw(f,P,uknot,vknot,weights,m.p,m.opacity,m.shininess,m.metallic,m.fresnel0,
+ PRCshininess,colors);
+ if(group)
+ endgroup3(f);
+ if(pic != null)
+ pic.addBox(minbound(P,Q),maxbound(P,Q));
+ }
+ },true);
+ pic.addBox(minbound(P),maxbound(P));
+}
diff --git a/graphics/asymptote/base/three_tube.asy b/graphics/asymptote/base/three_tube.asy
new file mode 100644
index 0000000000..25bb9385ec
--- /dev/null
+++ b/graphics/asymptote/base/three_tube.asy
@@ -0,0 +1,426 @@
+void render(path3 s, void f(path3, real), render render=defaultrender)
+{
+ real granularity=render.tubegranularity;
+ void Split(triple z0, triple c0, triple c1, triple z1, real t0=0, real t1=1,
+ real depth=mantissaBits) {
+ if(depth > 0) {
+ real S=straightness(z0,c0,c1,z1);
+ if(S > 0) {
+ --depth;
+ if(S > max(granularity*max(abs(z0),abs(c0),abs(c1),abs(z1)))) {
+ triple m0=0.5*(z0+c0);
+ triple m1=0.5*(c0+c1);
+ triple m2=0.5*(c1+z1);
+ triple m3=0.5*(m0+m1);
+ triple m4=0.5*(m1+m2);
+ triple m5=0.5*(m3+m4);
+ real tm=0.5*(t0+t1);
+ Split(z0,m0,m3,m5,t0,tm,depth);
+ Split(m5,m4,m2,z1,tm,t1,depth);
+ return;
+ }
+ }
+ }
+ f(z0..controls c0 and c1..z1,t0);
+ }
+ Split(point(s,0),postcontrol(s,0),precontrol(s,1),point(s,1));
+}
+
+struct rmf
+{
+ triple p,r,t,s;
+ void operator init(triple p, triple r, triple t)
+ {
+ this.p=p;
+ this.r=r;
+ this.t=t;
+ s=cross(t,r);
+ }
+}
+
+// Rotation minimizing frame
+// http://www.cs.hku.hk/research/techreps/document/TR-2007-07.pdf
+rmf[] rmf(path3 g, real[] t)
+{
+ rmf[] R=new rmf[t.length];
+ triple d=dir(g,0);
+ R[0]=rmf(point(g,0),perp(d),d);
+ for(int i=1; i < t.length; ++i) {
+ rmf Ri=R[i-1];
+ real t=t[i];
+ triple p=point(g,t);
+ triple v1=p-Ri.p;
+ if(v1 != O) {
+ triple r=Ri.r;
+ triple u1=unit(v1);
+ triple ti=Ri.t;
+ triple tp=ti-2*dot(u1,ti)*u1;
+ ti=dir(g,t);
+ triple rp=r-2*dot(u1,r)*u1;
+ triple u2=unit(ti-tp);
+ rp=rp-2*dot(u2,rp)*u2;
+ R[i]=rmf(p,unit(rp),unit(ti));
+ } else
+ R[i]=R[i-1];
+ }
+ return R;
+}
+
+private real[][][] bispline0(real[][] z, real[][] p, real[][] q, real[][] r,
+ real[] x, real[] y, bool[][] cond={})
+{ // z[i][j] is the value at (x[i],y[j])
+ // p and q are the first derivatives with respect to x and y, respectively
+ // r is the second derivative ddu/dxdy
+ int n=x.length-1;
+ int m=y.length-1;
+
+ bool all=cond.length == 0;
+
+ int count;
+ if(all)
+ count=n*m;
+ else {
+ count=0;
+ for(int i=0; i < n; ++i) {
+ bool[] condi=cond[i];
+ bool[] condp=cond[i+1];
+ for(int j=0; j < m; ++j)
+ if(all || (condi[j] && condi[j+1] && condp[j] && condp[j+1]))
+ ++count;
+ }
+ }
+
+ real[][][] s=new real[count][][];
+ int k=0;
+ for(int i=0; i < n; ++i) {
+ int ip=i+1;
+ real xi=x[i];
+ real xp=x[ip];
+ real hx=(xp-xi)/3;
+ real[] zi=z[i];
+ real[] zp=z[ip];
+ real[] ri=r[i];
+ real[] rp=r[ip];
+ real[] pi=p[i];
+ real[] pp=p[ip];
+ real[] qi=q[i];
+ real[] qp=q[ip];
+ bool[] condi=all ? null : cond[i];
+ bool[] condp=all ? null : cond[i+1];
+ for(int j=0; j < m; ++j) {
+ if(all || (condi[j] && condi[j+1] && condp[j] && condp[j+1])) {
+ real yj=y[j];
+ int jp=j+1;
+ real yp=y[jp];
+ real hy=(yp-yj)/3;
+ real hxy=hx*hy;
+ real zij=zi[j];
+ real zip=zi[jp];
+ real zpj=zp[j];
+ real zpp=zp[jp];
+ real pij=hx*pi[j];
+ real ppj=hx*pp[j];
+ real qip=hy*qi[jp];
+ real qpp=hy*qp[jp];
+ real zippip=zip+hx*pi[jp];
+ real zppmppp=zpp-hx*pp[jp];
+ real zijqij=zij+hy*qi[j];
+ real zpjqpj=zpj+hy*qp[j];
+
+ s[k]=new real[][] {{zij,zijqij,zip-qip,zip},
+ {zij+pij,zijqij+pij+hxy*ri[j],
+ zippip-qip-hxy*ri[jp],zippip},
+ {zpj-ppj,zpjqpj-ppj-hxy*rp[j],
+ zppmppp-qpp+hxy*rp[jp],zppmppp},
+ {zpj,zpjqpj,zpp-qpp,zpp}};
+ ++k;
+ }
+ }
+ }
+
+ return s;
+}
+
+// return the surface values described by a real matrix f, interpolated with
+// xsplinetype and ysplinetype.
+real[][][] bispline(real[][] f, real[] x, real[] y,
+ splinetype xsplinetype=null,
+ splinetype ysplinetype=xsplinetype, bool[][] cond={})
+{
+ real epsilon=sqrtEpsilon*norm(y);
+ if(xsplinetype == null)
+ xsplinetype=(abs(x[0]-x[x.length-1]) <= epsilon) ? periodic : notaknot;
+ if(ysplinetype == null)
+ ysplinetype=(abs(y[0]-y[y.length-1]) <= epsilon) ? periodic : notaknot;
+ int n=x.length; int m=y.length;
+ real[][] ft=transpose(f);
+ real[][] tp=new real[m][];
+ for(int j=0; j < m; ++j)
+ tp[j]=xsplinetype(x,ft[j]);
+ real[][] q=new real[n][];
+ for(int i=0; i < n; ++i)
+ q[i]=ysplinetype(y,f[i]);
+ real[][] qt=transpose(q);
+ real[] d1=xsplinetype(x,qt[0]);
+ real[] d2=xsplinetype(x,qt[m-1]);
+ real[][] r=new real[n][];
+ real[][] p=transpose(tp);
+ for(int i=0; i < n; ++i)
+ r[i]=clamped(d1[i],d2[i])(y,p[i]);
+ return bispline0(f,p,q,r,x,y,cond);
+}
+
+bool uperiodic(real[][] a) {
+ int n=a.length;
+ if(n == 0) return false;
+ int m=a[0].length;
+ real[] a0=a[0];
+ real[] a1=a[n-1];
+ for(int j=0; j < m; ++j) {
+ real norm=0;
+ for(int i=0; i < n; ++i)
+ norm=max(norm,abs(a[i][j]));
+ real epsilon=sqrtEpsilon*norm;
+ if(abs(a0[j]-a1[j]) > epsilon) return false;
+ }
+ return true;
+}
+bool vperiodic(real[][] a) {
+ int n=a.length;
+ if(n == 0) return false;
+ int m=a[0].length-1;
+ for(int i=0; i < n; ++i) {
+ real[] ai=a[i];
+ real epsilon=sqrtEpsilon*norm(ai);
+ if(abs(ai[0]-ai[m]) > epsilon) return false;
+ }
+ return true;
+}
+
+// return the surface described by a parametric function f evaluated at u and v
+// and interpolated with usplinetype and vsplinetype.
+surface surface(triple f(pair z), real[] u, real[] v,
+ splinetype[] usplinetype, splinetype[] vsplinetype=Spline,
+ bool cond(pair z)=null)
+{
+ int nu=u.length-1;
+ int nv=v.length-1;
+ real[] ipt=sequence(u.length);
+ real[] jpt=sequence(v.length);
+ real[][] fx=new real[u.length][v.length];
+ real[][] fy=new real[u.length][v.length];
+ real[][] fz=new real[u.length][v.length];
+
+ bool[][] active;
+ bool all=cond == null;
+ if(!all) active=new bool[u.length][v.length];
+
+ for(int i=0; i <= nu; ++i) {
+ real ui=u[i];
+ real[] fxi=fx[i];
+ real[] fyi=fy[i];
+ real[] fzi=fz[i];
+ bool[] activei=all ? null : active[i];
+ for(int j=0; j <= nv; ++j) {
+ pair z=(ui,v[j]);
+ if(!all) activei[j]=cond(z);
+ triple f=f(z);
+ fxi[j]=f.x;
+ fyi[j]=f.y;
+ fzi[j]=f.z;
+ }
+ }
+
+ if(usplinetype.length == 0) {
+ usplinetype=new splinetype[] {uperiodic(fx) ? periodic : notaknot,
+ uperiodic(fy) ? periodic : notaknot,
+ uperiodic(fz) ? periodic : notaknot};
+ } else if(usplinetype.length != 3) abort("usplinetype must have length 3");
+
+ if(vsplinetype.length == 0) {
+ vsplinetype=new splinetype[] {vperiodic(fx) ? periodic : notaknot,
+ vperiodic(fy) ? periodic : notaknot,
+ vperiodic(fz) ? periodic : notaknot};
+ } else if(vsplinetype.length != 3) abort("vsplinetype must have length 3");
+
+ real[][][] sx=bispline(fx,ipt,jpt,usplinetype[0],vsplinetype[0],active);
+ real[][][] sy=bispline(fy,ipt,jpt,usplinetype[1],vsplinetype[1],active);
+ real[][][] sz=bispline(fz,ipt,jpt,usplinetype[2],vsplinetype[2],active);
+
+ surface s=surface(sx.length);
+ s.index=new int[nu][nv];
+ int k=-1;
+ for(int i=0; i < nu; ++i) {
+ int[] indexi=s.index[i];
+ for(int j=0; j < nv; ++j)
+ indexi[j]=++k;
+ }
+
+ for(int k=0; k < sx.length; ++k) {
+ triple[][] Q=new triple[4][];
+ real[][] Px=sx[k];
+ real[][] Py=sy[k];
+ real[][] Pz=sz[k];
+ for(int i=0; i < 4 ; ++i) {
+ real[] Pxi=Px[i];
+ real[] Pyi=Py[i];
+ real[] Pzi=Pz[i];
+ Q[i]=new triple[] {(Pxi[0],Pyi[0],Pzi[0]),
+ (Pxi[1],Pyi[1],Pzi[1]),
+ (Pxi[2],Pyi[2],Pzi[2]),
+ (Pxi[3],Pyi[3],Pzi[3])};
+ }
+ s.s[k]=patch(Q);
+ }
+
+ if(usplinetype[0] == periodic && usplinetype[1] == periodic &&
+ usplinetype[1] == periodic) s.ucyclic(true);
+
+ if(vsplinetype[0] == periodic && vsplinetype[1] == periodic &&
+ vsplinetype[1] == periodic) s.vcyclic(true);
+
+ return s;
+}
+
+path3 interp(path3 a, path3 b, real t)
+{
+ int n=size(a);
+ return path3(sequence(new triple(int i) {
+ return interp(precontrol(a,i),precontrol(b,i),t);},n),
+ sequence(new triple(int i) {return interp(point(a,i),point(b,i),t);},n),
+ sequence(new triple(int i) {return interp(postcontrol(a,i),
+ postcontrol(b,i),t);},n),
+ sequence(new bool(int i) {return straight(a,i) && straight(b,i);},n),
+ cyclic(a) && cyclic(b));
+}
+
+struct tube
+{
+ surface s;
+ path3 center; // tube axis
+
+ void Null(transform3) {}
+ void Null(transform3, bool) {}
+
+ void operator init(path3 p, real width, render render=defaultrender,
+ void cylinder(transform3)=Null,
+ void sphere(transform3, bool half)=Null,
+ void pipe(path3, path3)=null) {
+ real r=0.5*width;
+
+ void generate(path3 p) {
+ int n=length(p);
+ if(piecewisestraight(p)) {
+ for(int i=0; i < n; ++i) {
+ triple v=point(p,i);
+ triple u=point(p,i+1)-v;
+ transform3 t=shift(v)*align(unit(u))*scale(r,r,abs(u));
+ s.append(t*unitcylinder);
+ cylinder(t);
+ }
+ center=center&p;
+ } else {
+ real[] T;
+ path3 G;
+ for(int i=0; i < n; ++i)
+ render(subpath(p,i,i+1),
+ new void(path3 g, real s) {
+ G=G&g;
+ T.push(i+s);
+ },render);
+ T.push(n);
+ T.cyclic=cyclic(p);
+ rmf[] rmf=rmf(p,T);
+ triple f(pair t) {
+ rmf R=rmf[round(t.x)];
+ int n=round(t.y);
+ static real[] x={1,0,-1,0};
+ static real[] y={0,1,0,-1};
+ return point(G,t.x)+r*(R.r*x[n]-R.s*y[n]);
+ }
+
+ static real[] v={0,1,2,3,0};
+ static real[] circular(real[] x, real[] y) {
+ static real a=8/3*(sqrt(2)-1);
+ return a*periodic(x,y);
+ }
+
+ static splinetype[] Monotonic={monotonic,monotonic,monotonic};
+ static splinetype[] Circular={circular,circular,circular};
+ if(T.length > 0) {
+ surface S=surface(f,sequence(T.length),v,Monotonic,Circular);
+ s.append(S);
+
+ // Compute center of tube:
+ int n=S.index.length;
+ if(T.cyclic) --n;
+ triple[] pre=new triple[n+1];
+ triple[] point=new triple[n+1];
+ triple[] post=new triple[n+1];
+
+ int[] index=S.index[0];
+ triple Point;
+ for(int m=0; m < 4; ++m)
+ Point += S.s[index[m]].P[0][0];
+ pre[0]=point[0]=0.25*Point;
+
+ for(int i=0; i < n; ++i) {
+ index=S.index[i];
+ triple Pre,Point,Post;
+ for(int m=0; m < 4; ++m) {
+ triple [][] P=S.s[index[m]].P;
+ Post += P[1][0];
+ Pre += P[2][0];
+ Point += P[3][0];
+ }
+ post[i]=0.25*Post;
+ pre[i+1]=0.25*Pre;
+ point[i+1]=0.25*Point;
+
+ }
+
+ index=S.index[n-1];
+ triple Post;
+ for(int m=0; m < 4; ++m)
+ Post += S.s[index[m]].P[3][0];
+ post[n]=0.25*Post;
+
+ bool[] b=array(n+1,false);
+ path3 Center=path3(pre,point,post,b,T.cyclic);
+ center=center&Center;
+
+ if(pipe != null) { // Compute path along tube
+ triple[] pre=new triple[n+1];
+ triple[] point=new triple[n+1];
+ triple[] post=new triple[n+1];
+ pre[0]=point[0]=S.s[S.index[0][0]].P[0][0];
+ for(int i=0; i < n; ++i) {
+ triple [][] P=S.s[S.index[i][0]].P;
+ post[i]=P[1][0];
+ pre[i+1]=P[2][0];
+ point[i+1]=P[3][0];
+ }
+ post[n]=S.s[S.index[n-1][0]].P[3][0];
+ pipe(Center,path3(pre,point,post,b,T.cyclic));
+ }
+ }
+ }
+ }
+
+ transform3 t=scale3(r);
+ bool cyclic=cyclic(p);
+ int begin=0;
+ int n=length(p);
+ for(int i=cyclic ? 0 : 1; i < n; ++i)
+ if(abs(dir(p,i,1)-dir(p,i,-1)) > sqrtEpsilon) {
+ generate(subpath(p,begin,i));
+ triple dir=dir(p,i,-1);
+ transform3 T=t*align(dir);
+ s.append(shift(point(p,i))*T*(dir != O ? unithemisphere : unitsphere));
+ sphere(shift(point(center,length(center)))*T,
+ half=straight(p,i-1) && straight(p,i));
+ begin=i;
+ }
+ generate(subpath(p,begin,n));
+ }
+}
diff --git a/graphics/asymptote/base/tree.asy b/graphics/asymptote/base/tree.asy
new file mode 100644
index 0000000000..1e603ec4b6
--- /dev/null
+++ b/graphics/asymptote/base/tree.asy
@@ -0,0 +1,86 @@
+/*****
+ * treedef.asy
+ * Andy Hammerlindl 2003/10/25
+ *
+ * Implements a dynamic binary search tree.
+ *****/
+
+struct tree
+{
+ tree left;
+ tree right;
+ int key = 0;
+ int value = 0;
+}
+
+tree newtree()
+{
+ return null;
+}
+
+tree add(tree t, int key, int value)
+{
+ if (t == null) {
+ tree tt;
+ tt.key = key; tt.value = value;
+ return tt;
+ }
+ else if (key == t.key) {
+ return t;
+ }
+ else if (key < t.key) {
+ tree tt;
+ tt.left = add(t.left, key, value);
+ tt.key = t.key;
+ tt.value = t.value;
+ tt.right = t.right;
+ return tt;
+ }
+ else {
+ tree tt;
+ tt.left = t.left;
+ tt.key = t.key;
+ tt.value = t.value;
+ tt.right = add(t.right, key, value);
+ return tt;
+ }
+}
+
+bool contains(tree t, int key)
+{
+ if (t == null)
+ return false;
+ else if (key == t.key)
+ return true;
+ else if (key < t.key)
+ return contains(t.left, key);
+ else
+ return contains(t.right, key);
+}
+
+int lookup(tree t, int key)
+{
+ if (t == null)
+ return 0;
+ else if (key == t.key)
+ return t.value;
+ else if (key < t.key)
+ return lookup(t.left, key);
+ else
+ return lookup(t.right, key);
+}
+
+void write(file out=stdout, tree t)
+{
+ if (t != null) {
+ if(t.left != null) {
+ write(out,t.left);
+ }
+ write(out,t.key);
+ write(out,"->");
+ write(out,t.value,endl);
+ if (t.right != null) {
+ write(out,t.right);
+ }
+ }
+}
diff --git a/graphics/asymptote/base/trembling.asy b/graphics/asymptote/base/trembling.asy
new file mode 100644
index 0000000000..f9f0d6a747
--- /dev/null
+++ b/graphics/asymptote/base/trembling.asy
@@ -0,0 +1,199 @@
+// Copyright(c) 2008, Philippe Ivaldi.
+// Simplified by John Bowman 02Feb2011
+// http: //www.piprime.fr/
+// trembling.asy: handwriting package for the software Asymptote.
+
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Lesser 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
+// Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+// COMMENTARY:
+
+// THANKS:
+
+// BUGS:
+// magnetic points are experimental...
+
+// CODE:
+
+real magneticRadius=1; // unit is bp in postscript coordinates.
+real trembleFuzz(){return min(1e-3,magneticRadius/10);}
+
+real trembleAngle=4, trembleFrequency=0.5, trembleRandom=2;
+
+struct tremble
+{
+ static real test=5;
+
+ real angle,frequency,random,fuzz;
+
+ pair[] single(pair[] P)
+ {
+ pair[] op;
+ bool allow;
+ for(int i=0; i < P.length-1; ++i) {
+ allow=true;
+ for(int j=i+1; j < P.length; ++j) {
+ if(abs(P[i]-P[j]) < magneticRadius) {
+ allow=false;
+ break;
+ }
+ }
+ if(allow) op.push(P[i]);
+ }
+ if(P.length > 0) op.push(P[P.length-1]);
+ return op;
+ }
+
+ real atime(pair m, path g, real fuzz=trembleFuzz())
+ {// Return the time of the point on path g nearest to m, within fuzz.
+ if(length(g) == 0) return 0.0;
+ real[] t=intersect(m,g,fuzz);
+ if(t.length > 0) return t[1];
+ real ot;
+ static real eps=sqrt(realEpsilon);
+ real abmax=abs(max(g)-m), abmin=abs(min(g)-m);
+ real initr=abs(m-midpoint(g));
+ real maxR=2*max(abmax,abmin), step=eps, r=initr;
+ real shx=1e-4;
+ transform T=shift(m);
+ path ig;
+ if(t.length > 0) ot=t[1];
+ real rm=0, rM=r;
+ while(rM-rm > eps) {
+ r=(rm+rM)/2;
+ t=intersect(T*scale(r)*unitcircle,g,fuzz);
+ if(t.length <= 0) {
+ rm=r;
+ } else {
+ rM=r;
+ ot=t[1];
+ }
+ }
+ return ot;
+ }
+
+ path addnode(path g, real t)
+ {// Add a node to 'g' at point(g,t).
+ real l=length(g);
+ real rt=t % 1;
+ if(l == 0 || (t > l && !cyclic(g)) || rt == 0) return g;
+ if(cyclic(g)) t=t % l;
+ int t0=floor(t);
+ int t1=t0+1;
+ pair z0=point(g,t0), z1=point(g,t1),
+ c0=postcontrol(g,t0), c1=precontrol(g,t1),
+ m0=(1-rt)*z0+rt*c0, m1=(1-rt)*c0+rt*c1,
+ m2=(1-rt)*c1+rt*z1, m3=(1-rt)*m0+rt*m1,
+ m4=(1-rt)*m1+rt*m2;
+ guide og=subpath(g,0,t0)..controls m0 and m3..point(g,t);
+ if(cyclic(g)) {
+ if(t1 < l)
+ og=og..controls m4 and m2..subpath(g,t1,l)&cycle;
+ else og=og..controls m4 and m2..cycle;
+ } else og=og..controls m4 and m2..subpath(g,t1,l);
+ return og;
+ }
+
+ path addnodes(path g, real fuzz=trembleFuzz()...pair[] P)
+ {
+ pair[] P=single(P);
+ if(length(g) == 0 || P.length == 0 || magneticRadius <= 0) return g;
+ path og=g;
+ for(pair tp: P) {
+ real t=atime(tp,og,fuzz);
+ real d=abs(tp-point(og,t));
+ if(d < magneticRadius) og=addnode(og,t);
+ }
+ return og;
+ }
+
+ path addnodes(path g, int n)
+ {// Add 'n' nodes between each node of 'g'.
+ real l=length(g);
+ if(n == 0 || l == 0) return g;
+ path og=g;
+ int np=0;
+ for(int i=0; i < l; ++i) {
+ real step=1/(n+1);
+ for(int j=0; j < n; ++j) {
+ og=addnode(og,i*(n+1)+j+step);
+ step=1/(n-j);
+ }
+ }
+ return og;
+ }
+
+ void operator init(real angle=trembleAngle, real frequency=trembleFrequency,
+ real random=trembleRandom, real fuzz=trembleFuzz()) {
+ this.angle=angle;
+ this.frequency=frequency;
+ this.random=random;
+ this.fuzz=fuzz;
+ }
+
+ path deform(path g...pair[] magneticPoints) {
+ /* Return g as it was handwriting.
+ The postcontrols and precontrols of the nodes of g will be rotated
+ by an angle proportional to 'angle'(in degrees).
+ If frequency < 1, floor(1/frequency) nodes will be added to g to
+ increase the control points.
+ If frequency>= 1, one point for floor(frequency) will be used to deform
+ the path.
+ 'random' controls the randomized coefficient which will be multiplied
+ by 'angle'.
+ random is 0 means don't use randomized coefficient;
+ The higher 'random' is, the more the trembling is randomized. */
+ if(length(g) == 0) return g;
+ g=addnodes(g,fuzz*abs(max(g)-min(g))...magneticPoints);
+ path tg=g;
+ frequency=abs(frequency);
+ int f=abs(floor(1/frequency)-1);
+ tg=addnodes(tg,f);
+ int frequency=floor(frequency);
+ int tf=(frequency == 0) ? 1 : frequency;
+ int l=length(tg);
+ guide og=point(tg,0);
+ random=abs(random);
+ int rsgn(real x){
+ int d2=floor(100*x)-10*floor(10*x);
+ if(d2 == 0) return 1;
+ return 2 % d2 == 0 ? 1 : -1;
+ }
+ real randf()
+ {
+ real or;
+ if(random != 0) {
+ if(1 % tf != 0) or=0;
+ else {
+ real ur=unitrand();
+ or=rsgn(ur)*angle*(1+ur^(1/random));
+ }
+ } else or=rsgn(unitrand())*1.5*angle;
+ return or;
+ }
+
+ real first=randf();
+ for(int i=1; i <= l; ++i) {
+ pair P=point(tg,i);
+ real a=randf();
+ pair post=rotate(a,point(tg,i-1))*postcontrol(tg,i-1);
+ pair pre=rotate((a+randf())/2,P)*precontrol(tg,i);
+ if(i == l && (cyclic(tg)))
+ og=og..controls post and pre..cycle;
+ else
+ og=og..controls post and pre..P;
+ }
+ return og;
+ }
+}
diff --git a/graphics/asymptote/base/tube.asy b/graphics/asymptote/base/tube.asy
new file mode 100644
index 0000000000..33e2ea3c86
--- /dev/null
+++ b/graphics/asymptote/base/tube.asy
@@ -0,0 +1,167 @@
+// Author: Philippe Ivaldi
+// http://www.piprime.fr/
+// Based on this paper:
+// http://www.cs.hku.hk/research/techreps/document/TR-2007-07.pdf
+// Note: the additional rotation for a cyclic smooth spine curve is not
+// yet properly determined.
+// TODO: Implement variational principles for RMF with boundary conditions:
+// minimum total angular speed OR minimum total squared angular speed
+
+import three;
+
+// A 3D version of roundedpath(path, real).
+path3 roundedpath(path3 A, real r)
+{
+ // Author of this routine: Jens Schwaiger
+ guide3 rounded;
+ triple before, after, indir, outdir;
+ int len=length(A);
+ bool cyclic=cyclic(A);
+ if(len < 2) {return A;};
+ if(cyclic) {rounded=point(point(A,0)--point(A,1),r);}
+ else {rounded=point(A,0);}
+ for(int i=1; i < len; i=i+1) {
+ before=point(point(A,i)--point(A,i-1),r);
+ after=point(point(A,i)--point(A,i+1),r);
+ indir=dir(point(A,i-1)--point(A,i),1);
+ outdir=dir(point(A,i)--point(A,i+1),1);
+ rounded=rounded--before{indir}..{outdir}after;
+ }
+ if(cyclic) {
+ before=point(point(A,0)--point(A,len-1),r);
+ indir=dir(point(A,len-1)--point(A,0),1);
+ outdir=dir(point(A,0)--point(A,1),1);
+ rounded=rounded--before{indir}..{outdir}cycle;
+ } else rounded=rounded--point(A,len);
+
+ return rounded;
+}
+
+real[] sample(path3 g, real r, real relstep=0)
+{
+ real[] t;
+ int n=length(g);
+ if(relstep <= 0) {
+ for(int i=0; i < n; ++i) {
+ real S=straightness(g,i);
+ if(S < sqrtEpsilon*r)
+ t.push(i);
+ else
+ render(subpath(g,i,i+1),new void(path3, real s) {t.push(i+s);});
+ }
+ t.push(n);
+ } else {
+ int nb=ceil(1/relstep);
+ relstep=n/nb;
+ for(int i=0; i <= nb; ++i)
+ t.push(i*relstep);
+ }
+ return t;
+}
+
+real degrees(rmf a, rmf b)
+{
+ real d=degrees(acos1(dot(a.r,b.r)));
+ real dt=dot(cross(a.r,b.r),a.t);
+ d=dt > 0 ? d : 360-d;
+ return d%360;
+}
+
+restricted int coloredNodes=1;
+restricted int coloredSegments=2;
+
+struct coloredpath
+{
+ path p;
+ pen[] pens(real);
+ bool usepens=false;
+ int colortype=coloredSegments;
+
+ void operator init(path p, pen[] pens=new pen[] {currentpen},
+ int colortype=coloredSegments)
+ {
+ this.p=p;
+ this.pens=new pen[] (real t) {return pens;};
+ this.usepens=true;
+ this.colortype=colortype;
+ }
+
+ void operator init(path p, pen[] pens(real), int colortype=coloredSegments)
+ {
+ this.p=p;
+ this.pens=pens;
+ this.usepens=true;
+ this.colortype=colortype;
+ }
+
+ void operator init(path p, pen pen(real))
+ {
+ this.p=p;
+ this.pens=new pen[] (real t) {return new pen[] {pen(t)};};
+ this.usepens=true;
+ this.colortype=coloredSegments;
+ }
+}
+
+coloredpath operator cast(path p)
+{
+ coloredpath cp=coloredpath(p);
+ cp.usepens=false;
+ return cp;
+}
+
+coloredpath operator cast(guide p)
+{
+ return coloredpath(p);
+}
+
+private surface surface(rmf[] R, real[] t, coloredpath cp, transform T(real),
+ bool cyclic)
+{
+ path g=cp.p;
+ int l=length(g);
+ bool[] planar;
+ for(int i=0; i < l; ++i)
+ planar[i]=straight(g,i);
+
+ surface s;
+ path3 sec=path3(T(t[0]/l)*g);
+ real adjust=0;
+ if(cyclic) adjust=-degrees(R[0],R[R.length-1])/(R.length-1);
+ path3 sec1=shift(R[0].p)*transform3(R[0].r,R[0].s,R[0].t)*sec,
+ sec2;
+
+ for(int i=1; i < R.length; ++i) {
+ sec=path3(T(t[i]/l)*g);
+ sec2=shift(R[i].p)*transform3(R[i].r,cross(R[i].t,R[i].r),R[i].t)*
+ rotate(i*adjust,Z)*sec;
+ for(int j=0; j < l; ++j) {
+ surface st=surface(subpath(sec1,j,j+1)--subpath(sec2,j+1,j)--cycle,
+ planar=planar[j]);
+ if(cp.usepens) {
+ pen[] tp1=cp.pens(t[i-1]/l), tp2=cp.pens(t[i]/l);
+ tp1.cyclic=true; tp2.cyclic=true;
+ if(cp.colortype == coloredSegments) {
+ st.colors(new pen[][] {{tp1[j],tp1[j],tp2[j],tp2[j]}});
+ } else {
+ st.colors(new pen[][] {{tp1[j],tp1[j+1],tp2[j+1],tp2[j]}});
+ }
+ }
+ s.append(st);
+ }
+ sec1=sec2;
+ }
+ return s;
+}
+
+surface tube(path3 g, coloredpath section,
+ transform T(real)=new transform(real t) {return identity();},
+ real corner=1, real relstep=0)
+{
+ pair M=max(section.p), m=min(section.p);
+ real[] t=sample(g,max(M.x-m.x,M.y-m.y)/max(realEpsilon,abs(corner)),
+ min(abs(relstep),1));
+ bool cyclic=cyclic(g);
+ t.cyclic=cyclic;
+ return surface(rmf(g,t),t,section,T,cyclic);
+}
diff --git a/graphics/asymptote/base/unicode.asy b/graphics/asymptote/base/unicode.asy
new file mode 100644
index 0000000000..33571e8341
--- /dev/null
+++ b/graphics/asymptote/base/unicode.asy
@@ -0,0 +1,2 @@
+usepackage("ucs");
+usepackage("inputenc","utf8x");
diff --git a/graphics/asymptote/base/x11colors.asy b/graphics/asymptote/base/x11colors.asy
new file mode 100644
index 0000000000..77e0401f06
--- /dev/null
+++ b/graphics/asymptote/base/x11colors.asy
@@ -0,0 +1,145 @@
+pen rgbint(int r, int g, int b)
+{
+ return rgb(r/255,g/255,b/255);
+}
+
+pen AliceBlue=rgbint(240,248,255);
+pen AntiqueWhite=rgbint(250,235,215);
+pen Aqua=rgbint(0,255,255);
+pen Aquamarine=rgbint(127,255,212);
+pen Azure=rgbint(240,255,255);
+pen Beige=rgbint(245,245,220);
+pen Bisque=rgbint(255,228,196);
+pen Black=rgbint(0,0,0);
+pen BlanchedAlmond=rgbint(255,235,205);
+pen Blue=rgbint(0,0,255);
+pen BlueViolet=rgbint(138,43,226);
+pen Brown=rgbint(165,42,42);
+pen BurlyWood=rgbint(222,184,135);
+pen CadetBlue=rgbint(95,158,160);
+pen Chartreuse=rgbint(127,255,0);
+pen Chocolate=rgbint(210,105,30);
+pen Coral=rgbint(255,127,80);
+pen CornflowerBlue=rgbint(100,149,237);
+pen Cornsilk=rgbint(255,248,220);
+pen Crimson=rgbint(220,20,60);
+pen Cyan=rgbint(0,255,255);
+pen DarkBlue=rgbint(0,0,139);
+pen DarkCyan=rgbint(0,139,139);
+pen DarkGoldenrod=rgbint(184,134,11);
+pen DarkGray=rgbint(169,169,169);
+pen DarkGreen=rgbint(0,100,0);
+pen DarkKhaki=rgbint(189,183,107);
+pen DarkMagenta=rgbint(139,0,139);
+pen DarkOliveGreen=rgbint(85,107,47);
+pen DarkOrange=rgbint(255,140,0);
+pen DarkOrchid=rgbint(153,50,204);
+pen DarkRed=rgbint(139,0,0);
+pen DarkSalmon=rgbint(233,150,122);
+pen DarkSeaGreen=rgbint(143,188,143);
+pen DarkSlateBlue=rgbint(72,61,139);
+pen DarkSlateGray=rgbint(47,79,79);
+pen DarkTurquoise=rgbint(0,206,209);
+pen DarkViolet=rgbint(148,0,211);
+pen DeepPink=rgbint(255,20,147);
+pen DeepSkyBlue=rgbint(0,191,255);
+pen DimGray=rgbint(105,105,105);
+pen DodgerBlue=rgbint(30,144,255);
+pen FireBrick=rgbint(178,34,34);
+pen FloralWhite=rgbint(255,250,240);
+pen ForestGreen=rgbint(34,139,34);
+pen Fuchsia=rgbint(255,0,255);
+pen Gainsboro=rgbint(220,220,220);
+pen GhostWhite=rgbint(248,248,255);
+pen Gold=rgbint(255,215,0);
+pen Goldenrod=rgbint(218,165,32);
+pen Gray=rgbint(128,128,128);
+pen Green=rgbint(0,128,0);
+pen GreenYellow=rgbint(173,255,47);
+pen Honeydew=rgbint(240,255,240);
+pen HotPink=rgbint(255,105,180);
+pen IndianRed=rgbint(205,92,92);
+pen Indigo=rgbint(75,0,130);
+pen Ivory=rgbint(255,255,240);
+pen Khaki=rgbint(240,230,140);
+pen Lavender=rgbint(230,230,250);
+pen LavenderBlush=rgbint(255,240,245);
+pen LawnGreen=rgbint(124,252,0);
+pen LemonChiffon=rgbint(255,250,205);
+pen LightBlue=rgbint(173,216,230);
+pen LightCoral=rgbint(240,128,128);
+pen LightCyan=rgbint(224,255,255);
+pen LightGoldenrodYellow=rgbint(250,250,210);
+pen LightGreen=rgbint(144,238,144);
+pen LightGrey=rgbint(211,211,211);
+pen LightPink=rgbint(255,182,193);
+pen LightSalmon=rgbint(255,160,122);
+pen LightSeaGreen=rgbint(32,178,170);
+pen LightSkyBlue=rgbint(135,206,250);
+pen LightSlateGray=rgbint(119,136,153);
+pen LightSteelBlue=rgbint(176,196,222);
+pen LightYellow=rgbint(255,255,224);
+pen Lime=rgbint(0,255,0);
+pen LimeGreen=rgbint(50,205,50);
+pen Linen=rgbint(250,240,230);
+pen Magenta=rgbint(255,0,255);
+pen Maroon=rgbint(128,0,0);
+pen MediumAquamarine=rgbint(102,205,170);
+pen MediumBlue=rgbint(0,0,205);
+pen MediumOrchid=rgbint(186,85,211);
+pen MediumPurple=rgbint(147,112,219);
+pen MediumSeaGreen=rgbint(60,179,113);
+pen MediumSlateBlue=rgbint(123,104,238);
+pen MediumSpringGreen=rgbint(0,250,154);
+pen MediumTurquoise=rgbint(72,209,204);
+pen MediumVioletRed=rgbint(199,21,133);
+pen MidnightBlue=rgbint(25,25,112);
+pen MintCream=rgbint(245,255,250);
+pen MistyRose=rgbint(255,228,225);
+pen Moccasin=rgbint(255,228,181);
+pen NavajoWhite=rgbint(255,222,173);
+pen Navy=rgbint(0,0,128);
+pen OldLace=rgbint(253,245,230);
+pen Olive=rgbint(128,128,0);
+pen OliveDrab=rgbint(107,142,35);
+pen Orange=rgbint(255,165,0);
+pen OrangeRed=rgbint(255,69,0);
+pen Orchid=rgbint(218,112,214);
+pen PaleGoldenrod=rgbint(238,232,170);
+pen PaleGreen=rgbint(152,251,152);
+pen PaleTurquoise=rgbint(175,238,238);
+pen PaleVioletRed=rgbint(219,112,147);
+pen PapayaWhip=rgbint(255,239,213);
+pen PeachPuff=rgbint(255,218,185);
+pen Peru=rgbint(205,133,63);
+pen Pink=rgbint(255,192,203);
+pen Plum=rgbint(221,160,221);
+pen PowderBlue=rgbint(176,224,230);
+pen Purple=rgbint(128,0,128);
+pen Red=rgbint(255,0,0);
+pen RosyBrown=rgbint(188,143,143);
+pen RoyalBlue=rgbint(65,105,225);
+pen SaddleBrown=rgbint(139,69,19);
+pen Salmon=rgbint(250,128,114);
+pen SandyBrown=rgbint(244,164,96);
+pen SeaGreen=rgbint(46,139,87);
+pen Seashell=rgbint(255,245,238);
+pen Sienna=rgbint(160,82,45);
+pen Silver=rgbint(192,192,192);
+pen SkyBlue=rgbint(135,206,235);
+pen SlateBlue=rgbint(106,90,205);
+pen SlateGray=rgbint(112,128,144);
+pen Snow=rgbint(255,250,250);
+pen SpringGreen=rgbint(0,255,127);
+pen SteelBlue=rgbint(70,130,180);
+pen Tan=rgbint(210,180,140);
+pen Teal=rgbint(0,128,128);
+pen Thistle=rgbint(216,191,216);
+pen Tomato=rgbint(255,99,71);
+pen Turquoise=rgbint(64,224,208);
+pen Violet=rgbint(238,130,238);
+pen Wheat=rgbint(245,222,179);
+pen White=rgbint(255,255,255);
+pen WhiteSmoke=rgbint(245,245,245);
+pen Yellow=rgbint(255,255,0);
+pen YellowGreen=rgbint(154,205,50);