summaryrefslogtreecommitdiff
path: root/Build/source/utils/asymptote/base
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2009-05-16 00:19:13 +0000
committerKarl Berry <karl@freefriends.org>2009-05-16 00:19:13 +0000
commitbab45528d65eaafe68a705dbb2a57075c7b7cbd8 (patch)
tree10b4ae2b5195c8dede153ab89359ec00f55f325f /Build/source/utils/asymptote/base
parent8643d90372e9c31e0f461c15c596b60a545bd7d3 (diff)
asymptote 1.72 sources (not integrated into build yet)
git-svn-id: svn://tug.org/texlive/trunk@13110 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/utils/asymptote/base')
-rw-r--r--Build/source/utils/asymptote/base/CAD.asy367
-rw-r--r--Build/source/utils/asymptote/base/animate.asy3
-rw-r--r--Build/source/utils/asymptote/base/animation.asy179
-rw-r--r--Build/source/utils/asymptote/base/annotate.asy16
-rw-r--r--Build/source/utils/asymptote/base/asy-init.el4
l---------Build/source/utils/asymptote/base/asy-keywords.el1
-rw-r--r--Build/source/utils/asymptote/base/asy-mode.el1545
-rw-r--r--Build/source/utils/asymptote/base/asy.vim202
-rwxr-xr-xBuild/source/utils/asymptote/base/asymptote.py48
-rw-r--r--Build/source/utils/asymptote/base/babel.asy4
-rw-r--r--Build/source/utils/asymptote/base/bezulate.asy258
-rw-r--r--Build/source/utils/asymptote/base/binarytree.asy326
-rw-r--r--Build/source/utils/asymptote/base/bsp.asy207
-rw-r--r--Build/source/utils/asymptote/base/contour.asy682
-rw-r--r--Build/source/utils/asymptote/base/contour3.asy481
-rw-r--r--Build/source/utils/asymptote/base/drawtree.asy101
-rw-r--r--Build/source/utils/asymptote/base/embed.asy34
-rw-r--r--Build/source/utils/asymptote/base/external.asy36
-rw-r--r--Build/source/utils/asymptote/base/feynman.asy622
-rw-r--r--Build/source/utils/asymptote/base/flowchart.asy356
-rw-r--r--Build/source/utils/asymptote/base/fontsize.asy1
-rw-r--r--Build/source/utils/asymptote/base/geometry.asy7150
-rw-r--r--Build/source/utils/asymptote/base/graph.asy2203
-rw-r--r--Build/source/utils/asymptote/base/graph3.asy1926
-rw-r--r--Build/source/utils/asymptote/base/graph_settings.asy18
-rw-r--r--Build/source/utils/asymptote/base/grid3.asy399
-rw-r--r--Build/source/utils/asymptote/base/interpolate.asy140
-rw-r--r--Build/source/utils/asymptote/base/labelpath.asy28
-rw-r--r--Build/source/utils/asymptote/base/labelpath3.asy86
-rw-r--r--Build/source/utils/asymptote/base/latin1.asy2
-rw-r--r--Build/source/utils/asymptote/base/markers.asy217
-rw-r--r--Build/source/utils/asymptote/base/math.asy392
-rw-r--r--Build/source/utils/asymptote/base/metapost.asy19
-rw-r--r--Build/source/utils/asymptote/base/nopapersize.ps3
-rw-r--r--Build/source/utils/asymptote/base/obj.asy104
-rw-r--r--Build/source/utils/asymptote/base/ode.asy15
-rw-r--r--Build/source/utils/asymptote/base/palette.asy473
-rw-r--r--Build/source/utils/asymptote/base/patterns.asy102
-rw-r--r--Build/source/utils/asymptote/base/plain.asy270
-rw-r--r--Build/source/utils/asymptote/base/plain_Label.asy601
-rw-r--r--Build/source/utils/asymptote/base/plain_arcs.asy43
-rw-r--r--Build/source/utils/asymptote/base/plain_arrows.asy569
-rw-r--r--Build/source/utils/asymptote/base/plain_boxes.asy136
-rw-r--r--Build/source/utils/asymptote/base/plain_constants.asy157
-rw-r--r--Build/source/utils/asymptote/base/plain_debugger.asy86
-rw-r--r--Build/source/utils/asymptote/base/plain_filldraw.asy234
-rw-r--r--Build/source/utils/asymptote/base/plain_margins.asy99
-rw-r--r--Build/source/utils/asymptote/base/plain_markers.asy377
-rw-r--r--Build/source/utils/asymptote/base/plain_paths.asy380
-rw-r--r--Build/source/utils/asymptote/base/plain_pens.asy341
-rw-r--r--Build/source/utils/asymptote/base/plain_picture.asy1892
-rw-r--r--Build/source/utils/asymptote/base/plain_shipout.asy113
-rw-r--r--Build/source/utils/asymptote/base/plain_strings.asy183
-rw-r--r--Build/source/utils/asymptote/base/plain_xasy.asy128
-rw-r--r--Build/source/utils/asymptote/base/pstoedit.asy18
-rw-r--r--Build/source/utils/asymptote/base/reload.js23
-rw-r--r--Build/source/utils/asymptote/base/roundedpath.asy84
-rw-r--r--Build/source/utils/asymptote/base/simplex.asy272
-rw-r--r--Build/source/utils/asymptote/base/size10.asy12
-rw-r--r--Build/source/utils/asymptote/base/size11.asy12
-rw-r--r--Build/source/utils/asymptote/base/slide.asy574
-rw-r--r--Build/source/utils/asymptote/base/slopefield.asy87
-rw-r--r--Build/source/utils/asymptote/base/solids.asy395
-rw-r--r--Build/source/utils/asymptote/base/splinetype.asy238
-rw-r--r--Build/source/utils/asymptote/base/stats.asy292
-rw-r--r--Build/source/utils/asymptote/base/syzygy.asy936
-rw-r--r--Build/source/utils/asymptote/base/texcolors.asy68
-rw-r--r--Build/source/utils/asymptote/base/three.asy2614
-rw-r--r--Build/source/utils/asymptote/base/three_arrows.asy789
-rw-r--r--Build/source/utils/asymptote/base/three_light.asy167
-rw-r--r--Build/source/utils/asymptote/base/three_margins.asy104
-rw-r--r--Build/source/utils/asymptote/base/three_surface.asy1343
-rw-r--r--Build/source/utils/asymptote/base/tree.asy86
-rw-r--r--Build/source/utils/asymptote/base/trembling.asy369
-rw-r--r--Build/source/utils/asymptote/base/tube.asy210
-rw-r--r--Build/source/utils/asymptote/base/unicode.asy2
-rw-r--r--Build/source/utils/asymptote/base/version.asy1
-rw-r--r--Build/source/utils/asymptote/base/x11colors.asy145
78 files changed, 33200 insertions, 0 deletions
diff --git a/Build/source/utils/asymptote/base/CAD.asy b/Build/source/utils/asymptote/base/CAD.asy
new file mode 100644
index 00000000000..7c333318f5c
--- /dev/null
+++ b/Build/source/utils/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("10 2.5");
+ // F
+ cad.pF =
+ cad.pInvisibleEdge =
+ cad.pInvisibleContour =
+ pHalfWidth + linetype("20 5");
+ // G
+ cad.pG =
+ cad.pMiddleLine =
+ cad.pSymmetryLine =
+ cad.pPartialCircle =
+ cad.pCircularHole =
+ cad.pDivisionPlane =
+ cad.pTransferLine =
+ pHalfWidth + linetype("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("20 2.5 2.5 2.5");
+ // K
+ cad.pK =
+ cad.pContourBeforeDeformation =
+ cad.pAdjacentPartContour =
+ cad.pEndShapeRawMaterial =
+ cad.pContourEligibleType =
+ cad.pPartInFrontOfCuttingPlane =
+ pHalfWidth + linetype("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/Build/source/utils/asymptote/base/animate.asy b/Build/source/utils/asymptote/base/animate.asy
new file mode 100644
index 00000000000..586e71041b6
--- /dev/null
+++ b/Build/source/utils/asymptote/base/animate.asy
@@ -0,0 +1,3 @@
+usepackage("animate");
+import animation;
+
diff --git a/Build/source/utils/asymptote/base/animation.asy b/Build/source/utils/asymptote/base/animation.asy
new file mode 100644
index 00000000000..2b7519c700a
--- /dev/null
+++ b/Build/source/utils/asymptote/base/animation.asy
@@ -0,0 +1,179 @@
+/*****
+ * 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 fit(string prefix="",picture);
+
+frame NoBox(string prefix="", picture pic) {
+ return pic.fit(prefix);
+}
+
+fit BBox(string prefix="", real xmargin=0, real ymargin=xmargin,
+ pen p=currentpen, filltype filltype=NoFill) {
+ return new frame(string prefix, picture pic) {
+ return bbox(prefix,pic,xmargin,ymargin,p,filltype);
+ };
+}
+
+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((prefix == "") ? outprefix() : stripdirectory(prefix),
+ " ","_");
+ this.prefix=prefix;
+ this.global=global;
+ }
+
+ string basename(string prefix=prefix) {
+ return "_"+stripextension(prefix);
+ }
+
+ string name(string prefix, int index) {
+ return stripextension(prefix)+"+"+string(index);
+ }
+
+ private string nextname() {
+ string name=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);
+ shipped=false;
+ }
+
+ void add(picture pic=currentpicture, fit fit=NoBox) {
+ if(global) {
+ pictures.push(pic.copy());
+ } else this.shipout(nextname(),fit(prefix,pic));
+ }
+
+ 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;
+ }
+
+ // Export all frames with the same scaling.
+ void export(string prefix=prefix, fit fit=NoBox,
+ bool multipage=false, bool view=false) {
+ if(pictures.length == 0) return;
+ rescale(pictures);
+ frame multi;
+ bool inlinetex=settings.inlinetex;
+ if(multipage)
+ settings.inlinetex=false;
+ for(int i=0; i < pictures.length; ++i) {
+ if(multipage) {
+ add(multi,fit(prefix,pictures[i]));
+ newpage(multi);
+ } else {
+ if(pictures[i].empty3() || settings.render <= 0) {
+ real render=settings.render;
+ settings.render=0;
+ this.shipout(name(prefix,i),fit(prefix,pictures[i]));
+ settings.render=render;
+ } else { // Render 3D frames
+ files.push(name(prefix,i)+"."+nativeformat());
+ fit(prefix,pictures[i]);
+ }
+ }
+ }
+ if(multipage) {
+ plain.shipout(prefix,multi,view=view);
+ settings.inlinetex=inlinetex;
+ }
+ shipped=true;
+ }
+
+ string load(int frames, real delay=animationdelay, string options="",
+ bool multipage=false) {
+ string s="\animategraphics["+options+"]{"+format("%.18f",1000/delay,"C")+
+ "}{"+basename();
+ if(!multipage) s += "+";
+ s += "}{0}{"+string(frames-1)+"}";
+ return s;
+ }
+
+ string pdf(fit fit=NoBox, real delay=animationdelay, string options="",
+ bool keep=settings.keep, bool multipage=true) {
+ if(settings.inlinetex) multipage=true;
+ if(settings.tex != "pdflatex")
+ abort("inline pdf animations require -tex pdflatex");
+ if(settings.outformat != "") settings.outformat="pdf";
+
+ string filename=basename();
+ string pdfname=filename+".pdf";
+ bool single=global && multipage;
+
+ if(global)
+ export(filename,fit,multipage=multipage);
+
+ shipped=false;
+
+ if(!keep && !settings.inlinetex) {
+ exitfcn currentexitfunction=atexit();
+ void exitfunction() {
+ if(currentexitfunction != null) currentexitfunction();
+ this.purge();
+ if(single)
+ delete(pdfname);
+ }
+ atexit(exitfunction);
+ }
+
+ if(!single)
+ delete(pdfname);
+
+ return load(pictures.length,delay,options,multipage);
+ }
+
+ int movie(fit fit=NoBox, int loops=0, real delay=animationdelay,
+ string format=settings.outformat == "" ? "gif" : settings.outformat,
+ string options="", bool keep=settings.keep) {
+ if(global && format == "pdf") {
+ export(fit,multipage=true,view=true);
+ return 0;
+ }
+
+ if(global)
+ export(fit);
+ return merge(loops,delay,format,options,keep);
+ }
+}
+
+animation operator init() {
+ animation a=animation();
+ return a;
+}
diff --git a/Build/source/utils/asymptote/base/annotate.asy b/Build/source/utils/asymptote/base/annotate.asy
new file mode 100644
index 00000000000..aba9309cb65
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/asy-init.el b/Build/source/utils/asymptote/base/asy-init.el
new file mode 100644
index 00000000000..0e3f178a228
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/asy-keywords.el b/Build/source/utils/asymptote/base/asy-keywords.el
new file mode 120000
index 00000000000..2e932b592e0
--- /dev/null
+++ b/Build/source/utils/asymptote/base/asy-keywords.el
@@ -0,0 +1 @@
+../asy-keywords.el \ No newline at end of file
diff --git a/Build/source/utils/asymptote/base/asy-mode.el b/Build/source/utils/asymptote/base/asy-mode.el
new file mode 100644
index 00000000000..ac31d8555eb
--- /dev/null
+++ b/Build/source/utils/asymptote/base/asy-mode.el
@@ -0,0 +1,1545 @@
+;;; asy-mode.el
+
+;; Copyright (C) 2006-8
+;; Author: Philippe IVALDI 20 August 2006
+;; Modified by: John Bowman
+;; Last modification: 05 December 2008 (P. Ivaldi)
+;;
+;; 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
+
+;; Emacs 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.
+
+(defvar asy-mode-version "1.5")
+
+;;;###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)
+ (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))
+ ))))
+
+ ;; 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))
+ (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} passing by 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-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))
+ (let*
+ ((b-b-n (if Filename Filename (file-name-sans-extension buffer-file-name)))
+ (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 (or (file-newer-than-file-p
+ (concat b-b-n ".tex")
+ (concat b-b-n ".ps"))
+ Force)
+ (progn
+ (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 (file-name-sans-extension buffer-file-name)))
+ (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 (or (file-newer-than-file-p
+ (concat b-b-n ".tex")
+ (concat b-b-n ".pdf"))
+ Force)
+ (progn
+ (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 (file-name-sans-extension buffer-file-name)))
+ (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 (or (file-newer-than-file-p
+ (concat b-b-n ".tex")
+ (concat b-b-n ".pdf"))
+ Force)
+ (progn
+ (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)
diff --git a/Build/source/utils/asymptote/base/asy.vim b/Build/source/utils/asymptote/base/asy.vim
new file mode 100644
index 00000000000..4cb897f1316
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/asymptote.py b/Build/source/utils/asymptote/base/asymptote.py
new file mode 100755
index 00000000000..721c464d36b
--- /dev/null
+++ b/Build/source/utils/asymptote/base/asymptote.py
@@ -0,0 +1,48 @@
+# Python module to feed Asymptote with commands
+# (modified from gnuplot.py)
+from subprocess import *
+class asy:
+ def __init__(self):
+ self.session = Popen(['asy','-quiet','-interactive'],stdin=PIPE)
+ self.help()
+ def send(self, cmd):
+ self.session.stdin.write(cmd+'\n')
+ 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/Build/source/utils/asymptote/base/babel.asy b/Build/source/utils/asymptote/base/babel.asy
new file mode 100644
index 00000000000..e93dec2a5cb
--- /dev/null
+++ b/Build/source/utils/asymptote/base/babel.asy
@@ -0,0 +1,4 @@
+void babel(string s)
+{
+ usepackage("babel",s);
+}
diff --git a/Build/source/utils/asymptote/base/bezulate.asy b/Build/source/utils/asymptote/base/bezulate.asy
new file mode 100644
index 00000000000..c47f3144912
--- /dev/null
+++ b/Build/source/utils/asymptote/base/bezulate.asy
@@ -0,0 +1,258 @@
+// Bezier triangulation routines written by Orest Shardt, 2008.
+
+private real fuzz = sqrtEpsilon;
+
+int countIntersections(path[] p, pair start, pair end)
+{
+ int intersects=0;
+ for(path q : p)
+ intersects += intersections(q,start--end,fuzz).length;
+ return intersects;
+}
+
+path[][] containmentTree(path[] paths)
+{
+ path[][] result;
+ for(int i=0; i < paths.length; ++i) {
+ bool classified=false;
+ // check if current curve contains or is contained in a group of curves
+ for(int j=0; !classified && j < result.length; ++j)
+ {
+ int test = inside(paths[i],result[j][0],zerowinding);
+ if(test == 1) // current curve contains group's toplevel curve
+ {
+ // replace toplevel curve with current curve
+ result[j].insert(0,paths[i]);
+ classified = true;
+ }
+ else if(test == -1) // current curve contained in group's toplevel curve
+ {
+ result[j].push(paths[i]);
+ classified = true;
+ }
+ }
+ // create a new group if this curve does not belong to another group
+ if(!classified)
+ result.push(new path[] {paths[i]});
+ }
+
+ // sort group so that later paths in the array are contained in previous paths
+ bool comparepaths(path i, path j) {return inside(i,j,zerowinding)==1;}
+ for(int i=0; i < result.length; ++i)
+ result[i] = sort(result[i],comparepaths);
+
+ return result;
+}
+
+bool isDuplicate(pair a, pair b, real relSize)
+{
+ return abs(a-b) <= sqrtEpsilon*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;
+ real starttime = 0; // starttime is time on inners[curveIndex]
+ pair direction=I*dir(inners[curveIndex],starttime);
+ pair start=point(inners[curveIndex],starttime);
+
+ // find first intersection of line segment with outer curve
+ path line = start--start+d*direction;
+ real[][] ints=intersections(line,outer,fuzz);
+ assert(ints.length != 0);
+ real endtime=ints[0][1]; // endtime is time on outer
+ pair end = point(outer,endtime);
+ line = start--end;
+ path rline = reverse(line);
+
+ // find first intersection of rline segment with any inner curve
+ real earliestTime=1;
+ for(int j=0; j < inners.length; ++j) {
+ real[][] ints=intersections(rline,inners[j],fuzz);
+ if(ints.length > 0 && ints[0][0] < earliestTime) {
+ earliestTime=ints[0][0]; // time on rline
+ starttime=ints[0][1]; // time on inner curve
+ curveIndex=j;
+ }
+ }
+ start=point(inners[curveIndex],starttime);
+ line = start--end;
+
+ real timeoffset=2;
+ bool found=false;
+ path portion;
+ path[] allCurves = {outer};
+ allCurves.append(inners);
+
+ while(!found && timeoffset > fuzz) {
+ timeoffset /= 2;
+ if(countIntersections(allCurves,start,
+ point(outer,endtime+timeoffset)) == 2)
+ {
+ portion = subpath(outer,endtime,endtime+timeoffset)--start--cycle;
+ found=true;
+ // check if an inner curve is inside the portion
+ for(int k = 0; found && k < inners.length; ++k)
+ {
+ if(k!=curveIndex &&
+ inside(portion,point(inners[k],0),zerowinding))
+ found = false;
+ }
+ }
+ }
+
+ if(!found)timeoffset=-2;
+ while(!found && timeoffset < -fuzz) {
+ timeoffset /= 2;
+ if(countIntersections(allCurves,start,
+ point(outer,endtime+timeoffset))==2)
+ {
+ portion = subpath(outer,endtime+timeoffset,endtime)--start--cycle;
+ found = true;
+ // check if an inner curve is inside the portion
+ for(int k = 0; found && k < inners.length; ++k)
+ {
+ if(k!=curveIndex &&
+ inside(portion,point(inners[k],0),zerowinding))
+ found = false;
+ }
+ }
+ }
+ assert(found);
+ endtime=min(endtime,endtime+timeoffset);
+ 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);
+ }
+}
+
+int countIntersections(path g, pair p, pair q)
+{
+ int ints=0;
+ int l=length(g);
+ for(int i=1; i <= l; ++i)
+ ints += intersections(subpath(g,i-1,i),p--q,fuzz).length;
+ return ints;
+}
+
+bool checkSegment(path g, pair p, pair q)
+{
+ pair mid=0.5*(p+q);
+ return countIntersections(g,p,q) == 4 && 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&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 > mantissaBits) {
+ write("warning: too many subdivisions");
+ } else {
+ p=subdivide(p);
+ i=-1;
+ }
+ }
+ }
+ }
+ }
+ if(length(p) <= 4)
+ patch.append(p);
+ }
+ return patch;
+}
diff --git a/Build/source/utils/asymptote/base/binarytree.asy b/Build/source/utils/asymptote/base/binarytree.asy
new file mode 100644
index 00000000000..7bbd1de2148
--- /dev/null
+++ b/Build/source/utils/asymptote/base/binarytree.asy
@@ -0,0 +1,326 @@
+/* **********************************************************************
+ * binarytree: An Asymptote module to draw binary trees *
+ * *
+ * Copyright (C) 2006 *
+ * Tobias Langner tobias[at]langner[dot]nightlabs[dot]de *
+ * *
+ * Modified by John Bowman *
+ * *
+ ************************************************************************
+ * *
+ * 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;
+
+ /**
+ * sets the left child of this node
+ */
+ void setLeft(binarytreeNode left) {
+ this.left=left;
+ this.left.parent=this;
+ }
+
+ /**
+ * sets the right child of this node
+ */
+ void setRight(binarytreeNode right) {
+ this.right=right;
+ this.right.parent=this;
+ }
+
+ /**
+ * returns a boolean indicating whether this node is the root
+ */
+ bool isRoot() {
+ return parent == null;
+ }
+
+ /**
+ * Returns the level of the subtree rooted at this node.
+ */
+ int getLevel() {
+ if(isRoot())
+ return 1;
+ else
+ return parent.getLevel()+1;
+ }
+
+ /**
+ * sets the children of this binarytreeNode
+ */
+ void setChildren(binarytreeNode left, binarytreeNode right) {
+ setLeft(left);
+ setRight(right);
+ }
+
+ /**
+ * creates 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;
+
+
+/**
+ * draws 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) {
+ Label label=Label(math((string) node.key),pos);
+
+ binarytreeNode left=node.left;
+ binarytreeNode right=node.right;
+
+ /**
+ * returns 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);
+ }
+
+ real dist=getDistance(node.getLevel(),height,minDist)/2;
+
+ /**
+ * draws the connection between the two nodes at the given positions
+ * by calculating the connection points
+ * and then 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,arr,p,Arrow(5));
+ });
+ pic.addPoint(parentPos);
+ pic.addPoint(childPos);
+ }
+
+ if(left != null) {
+ pair childPos=pos-(0,levelDist)-(dist/2,0);
+ draw(pic,left,childPos,height,minDist,levelDist,nodeDiameter,p);
+ deferredDrawNodeConnection(pos,childPos);
+ }
+
+ if(right != null) {
+ pair childPos=pos-(0,levelDist)+(dist/2,0);
+ draw(pic,right,childPos,height,minDist,levelDist,nodeDiameter,p);
+ 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;
+
+ /**
+ * adds 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;
+ }
+ }
+ }
+ }
+
+ /**
+ * returns the height of the tree
+ */
+ int getHeight() {
+ if(root == null)
+ return 0;
+ else
+ return root.getHeight();
+ }
+
+ /**
+ * adds 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;
+ }
+
+
+ /**
+ * returns 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;
+}
+
+/**
+ * draws the given binary tree.
+ */
+void draw(picture pic=currentpicture, binarytree tree,
+ real minDist=minDistDefault, real nodeMargin=nodeMarginDefault,
+ pen p=currentpen)
+{
+ 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);
+}
diff --git a/Build/source/utils/asymptote/base/bsp.asy b/Build/source/utils/asymptote/base/bsp.asy
new file mode 100644
index 00000000000..a744da522cc
--- /dev/null
+++ b/Build/source/utils/asymptote/base/bsp.asy
@@ -0,0 +1,207 @@
+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.nodes.push(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.userBox(F.userMin,F.userMax);
+ pic.append(pic.bounds.point,pic.bounds.min,pic.bounds.max,F.T,F.bounds);
+ }
+}
diff --git a/Build/source/utils/asymptote/base/contour.asy b/Build/source/utils/asymptote/base/contour.asy
new file mode 100644
index 00000000000..fbb4cd1c71a
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/contour3.asy b/Build/source/utils/asymptote/base/contour3.asy
new file mode 100644
index 00000000000..e9d8d928991
--- /dev/null
+++ b/Build/source/utils/asymptote/base/contour3.asy
@@ -0,0 +1,481 @@
+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.vector();
+
+ 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(new triple[] {cur[0].v,cur[0].v,cur[1].v,cur[2].v},
+ normals=new triple[] {cur[0].normal,cur[0].normal,
+ cur[1].normal,cur[2].normal});
+ }
+ return s;
+}
diff --git a/Build/source/utils/asymptote/base/drawtree.asy b/Build/source/utils/asymptote/base/drawtree.asy
new file mode 100644
index 00000000000..832dfc658a1
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/embed.asy b/Build/source/utils/asymptote/base/embed.asy
new file mode 100644
index 00000000000..3fe3d38720c
--- /dev/null
+++ b/Build/source/utils/asymptote/base/embed.asy
@@ -0,0 +1,34 @@
+texpreamble("
+\ifx\pdfhorigin\undefined%
+\usepackage[3D,dvipdfmx]{movie15}
+\else%
+\usepackage[3D]{movie15}
+\fi%
+");
+usepackage("hyperref","setpagesize=false,unicode");
+
+// See http://www.ctan.org/tex-archive/macros/latex/contrib/movie15/README
+// for documentation of the options.
+
+// Embed object in pdf file
+string embed(string name, string options="", real width=0, real height=0)
+{
+ if(options != "") options="["+options+"]{";
+ if(width != 0) options += (string) (width/pt)+"pt";
+ options += "}{";
+ if(height != 0) options += (string) (height/pt)+"pt";
+ return "\includemovie"+options+"}{"+name+"}";
+}
+
+string hyperlink(string url, string text)
+{
+ return "\href{"+url+"}{"+text+"}";
+}
+
+string link(string label, string text, string options="")
+{
+ // Run LaTeX twice to resolve references.
+ settings.twice=true;
+ if(options != "") options="["+options+"]";
+ return "\movieref"+options+"{"+label+"}{"+text+"}";
+}
diff --git a/Build/source/utils/asymptote/base/external.asy b/Build/source/utils/asymptote/base/external.asy
new file mode 100644
index 00000000000..5573561fc36
--- /dev/null
+++ b/Build/source/utils/asymptote/base/external.asy
@@ -0,0 +1,36 @@
+usepackage("hyperref","setpagesize=false,unicode");
+
+// 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 options="", real width=0, real height=0,
+ string image="")
+{
+ string options; // Ignore movie15 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 url, string text, string options="")
+{
+ return hyperlink("run:"+url,text);
+}
+
diff --git a/Build/source/utils/asymptote/base/feynman.asy b/Build/source/utils/asymptote/base/feynman.asy
new file mode 100644
index 00000000000..4360ee9f524
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/flowchart.asy b/Build/source/utils/asymptote/base/flowchart.asy
new file mode 100644
index 00000000000..5fe96ae8508
--- /dev/null
+++ b/Build/source/utils/asymptote/base/flowchart.asy
@@ -0,0 +1,356 @@
+// 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;
+
+struct block {
+ // The absolute center of the block in user coordinates.
+ pair center;
+
+ // The relative center of the block.
+ pair f_center;
+
+ // The size of the block
+ pair size;
+
+ // Returns the relative position along the boundary of the block.
+ pair f_position(real x);
+
+ pair shift(transform t=identity()) {
+ return t*center-f_center;
+ }
+
+ // 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 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;
+
+ // 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);
+};
+
+// 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 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(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));
+}
diff --git a/Build/source/utils/asymptote/base/fontsize.asy b/Build/source/utils/asymptote/base/fontsize.asy
new file mode 100644
index 00000000000..0c0c6a0dcca
--- /dev/null
+++ b/Build/source/utils/asymptote/base/fontsize.asy
@@ -0,0 +1 @@
+usepackage("fix-cm");
diff --git a/Build/source/utils/asymptote/base/geometry.asy b/Build/source/utils/asymptote/base/geometry.asy
new file mode 100644
index 00000000000..a4860557e1f
--- /dev/null
+++ b/Build/source/utils/asymptote/base/geometry.asy
@@ -0,0 +1,7150 @@
+// Copyright (c) 2007, Philippe Ivaldi.
+// Version: : geometry.asy,v 0.1 2007/09/01 Philippe Ivaldi Exp $
+
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or (at
+// your option) any later version.
+
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+// 02110-1301, USA.
+
+// COMMENTARY:
+// An Asymptote geometry module.
+
+// THANKS:
+// Special thanks to Olivier Guibé for his help in mathematical issues.
+
+// BUGS:
+
+// CODE:
+
+import math;
+import markers;
+// *=======================================================*
+// *........................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......................*
+// Copyright (c) 2007, Philippe Ivaldi.
+// Version: $Id: coordinates.asy,v 0.0 2007/02/03 16:06:23 Philippe Ivaldi Exp$
+// Last modified: Wed Aug 15 15:53:01 CEST 2007
+
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// any later version.
+
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+// 02110-1301, USA.
+
+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=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;
+}
+point point(explicit pair p, int m)
+{// Handle ambiguity
+ return point(p,(real)m);
+}
+
+/*<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)
+ write("Warning, the coordinate system of two objects are not the same.
+The operation will be done relatively 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 'maths'.
+ 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, 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.
+ pair z=t*P;
+ pair q=t*Q;
+ pair v=t*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,(z.x,m.y)) < 0) {
+ ptp=(z.x,z.y+cp*(m.y-z.y));
+ ptq=(z.x,q.y+cq*(M.y-q.y));
+ } else {
+ ptp=(z.x,q.y+cq*(m.y-q.y));
+ ptq=(z.x,z.y+cp*(M.y-z.y));
+ }
+ } else if(v.y == 0) {
+ if (dot(v,(m.x,z.y)) < 0) {
+ ptp=(z.x+cp*(m.x-z.x),z.y);
+ ptq=(q.x+cq*(M.x-q.x),z.y);
+ } else {
+ ptp=(q.x+cq*(m.x-q.x),z.y);
+ ptq=(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) == ""){
+ 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, 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*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+p;
+ if(size == 0) size=perpfactor*3mm+sqrt(1+linewidth(p))-1;
+ 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));
+ if (margin == NoMargin)
+ margin=TrueMargin(linewidth(currentpen)/2,linewidth(currentpen)/2);
+ 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,line,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,
+// line l1, 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,line,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,
+// line l1, 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,
+ line l1, 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)
+ write("Warning, 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) write("Warning: variable 'circlenodesnumberfactor' maybe 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("Warning: 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(explicit pair,real,real)"><code></asyxml>*/
+ellipse ellipse(explicit pair C, real a, real b)=
+ new ellipse(explicit pair C, real a, real b)
+{/*<asyxml></code><documentation>Overwrite the default routine.</documentation></function></asyxml>*/
+ return ellipse((point)C,a,b,0);
+};
+
+/*<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;
+}
+circle circle(explicit point C, int r)
+{
+ return circle(C,(real) r);
+}
+
+/*<asyxml><function type="circle" signature="circle(pair,real)"><code></asyxml>*/
+circle circle(pair c, real r)=new circle(pair c, real r)
+{/*<asyxml></code><documentation>Overwrite 'circle(pair,real)'</documentation></function></asyxml>*/
+ return circle(locate(c),r);
+};
+
+/*<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));
+ return parabola(P[0],P[1],P[2],t*p.D);
+}
+
+/*<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>*/
+ 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]) return hyperbola(oe.F2,oe.F1,oe.a);
+ 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.");
+ guide op;
+ coordsys Rp=coordsys(el);
+ if (n < 1) return op;
+ if (angle1 > angle2)
+ return reverse(arcfromcenter(el,angle2,angle1,!direction,n));
+ 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=op..Rp*Rp.polar(r,a+da);
+ }
+ 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, 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;
+ draw(tmp,L,t*(path) parabola,align,p,arrow,bar,NoMargin,legend,marker);
+ add(f,tmp.fit());
+ });
+ if(pic.userMin.x != pic.userMax.x & pic.userMin.y != pic.userMax.y &
+ !finite(abs(pic.userMin)) & !finite(abs(pic.userMax)))
+ 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, 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;
+ picture tmp;
+ draw(tmp,L,t*(path) h,align,p,arrow,bar,NoMargin,legend,marker);
+ hyperbola ht=hyperbola(h.F2,h.F1,h.a);
+ ht.bmin=inverse(t)*m; ht.bmax=inverse(t)*M;
+ draw(tmp,"",t*(path) ht,align,p,arrow,bar,NoMargin,marker);
+ add(f,tmp.fit());
+ });
+ if(pic.userMin.x != pic.userMax.x & pic.userMin.y != pic.userMax.y &
+ !finite(abs(pic.userMin)) & !finite(abs(pic.userMax)))
+ 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(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;
+}
+abscissa operator +(explicit abscissa a, int x)
+{
+ 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;
+}
+abscissa operator -(explicit abscissa a, int x)
+{
+ return a-((real)x);
+}
+
+/*<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 *(int x, explicit abscissa a)
+{
+ return ((real)x)*a;
+}
+abscissa operator *(explicit abscissa a, int x)
+{
+ return ((real)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;
+}
+abscissa operator /(explicit abscissa a, int x)
+{
+ return a/((real)x);
+}
+
+/*<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>*/
+ write("PASS");
+ 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>*/
+ 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=0; i < 3; ++i) {
+ addpoint(intersectionpoint(t.line(i),l));
+ }
+ } else {
+ for (int i=0; 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));
+ draw(pic,La,b--c, align=rotate(dot(a-mBC,aligna) > 0 ? 180 :0)*aligna,p);
+ draw(pic,Lb,a--c, align=rotate(dot(b-mAC,alignb) > 0 ? 180 :0)*alignb,p);
+ draw(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);
+}
+
+/*<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) {
+ write("Warning: the inversion of the line is not a circle.");
+ write("The returned circle has an infinite radius, cirlce.l have been set.");
+ 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) {
+ write("Warning: the inversion of the circle is not a circle.");
+ write("The returned circle has an infinite radius, cirlce.l have been set.");
+ 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......................*
+// *=======================================================*
+
+/*<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=0; i < 3; ++i) {
+ addpoint(intersectionpoints(t.line(i),co));
+ }
+ } else {
+ for (int i=0; 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);
+ arc oa=arc(tc,a,b);
+ 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,b,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=arrowfactor)
+{
+ return arc(A,r,degrees(B-A),degrees(C-A));
+}
+
+// *.......End of compatibility routines........*
+// *=======================================================*
+
+// *........................FOOTER.........................*
+// *=======================================================*
diff --git a/Build/source/utils/asymptote/base/graph.asy b/Build/source/utils/asymptote/base/graph.asy
new file mode 100644
index 00000000000..a778dd89261
--- /dev/null
+++ b/Build/source/utils/asymptote/base/graph.asy
@@ -0,0 +1,2203 @@
+private import math;
+
+import 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) {
+ int i=find(x == v);
+ return i < 0 ? format(s,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-1; ++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-1; ++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;
+
+ real tickmin=finite(locate.S.tickMin) && (Step == 0 || locate.S.automin) ?
+ locate.S.Tinv(locate.S.tickMin) : a;
+ real tickmax=finite(locate.S.tickMax) && (Step == 0 || 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(Step == 0 && N == 0) {
+ N=1;
+ if(divisor.length > 0) {
+ bool autoscale=locate.S.automin && locate.S.automax;
+ real h=0.5*(b-a);
+ 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 == 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 OmitTick(... real[] x) {
+ return new tickvalues(tickvalues v) {
+ if(v.major.length == 0) return v;
+ real norm=max(abs(v.major));
+ for(int i=0; i < x.length; ++i) {
+ int j=find(abs(v.major-x[i]) < zerotickfuzz*norm);
+ if(j >= 0) v.major.delete(j);
+ }
+ return v;
+ };
+}
+
+tickmodifier NoZero=OmitTick(0);
+
+// Tickmodifier that removes all major ticks in the interval [a,b].
+tickmodifier Break(real a, real b) {
+ return new tickvalues(tickvalues v) {
+ real[] V=v.major;
+ real[] major;
+ for(int i=0; i < V.length; ++i)
+ if(V[i] < a-epsilon || V[i] > b+epsilon) major.push(V[i]);
+ v.major=major;
+ return v;
+ };
+}
+
+// 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 == 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;
+ else 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);
+ }
+
+ 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 == 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;
+ else 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);
+ }
+
+ 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(pic.scale.x.T(min));
+
+ if(pic.scale.x.automax) {
+ if(pic.scale.x.automax()) pic.userMaxx(mx.max);
+ } else pic.userMaxx(pic.scale.x.T(max));
+
+ if(crop) {
+ pair userMin=pic.userMin();
+ pair userMax=pic.userMax();
+ pic.bounds.xclip(userMin.x,userMax.x);
+ pic.clip(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(pic.scale.y.T(min));
+
+ if(pic.scale.y.automax) {
+ if(pic.scale.y.automax()) pic.userMaxy(my.max);
+ } else pic.userMaxy(pic.scale.y.T(max));
+
+ if(crop) {
+ pair userMin=pic.userMin();
+ pair userMax=pic.userMax();
+ pic.bounds.yclip(userMin.y,userMax.y);
+ pic.clip(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.userMinx(floor(pic.userMin.x));
+ if(pic.scale.x.automax())
+ pic.userMaxx(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.userMiny(floor(pic.userMin.y));
+ if(pic.scale.y.automax())
+ pic.userMaxy(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)
+{
+ 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(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="",
+ pair min=(-infinity,-infinity), pair max=(infinity,infinity),
+ pen p=currentpen, arrowbar arrow=None, margin margin=NoMargin,
+ bool above=false)
+{
+ xaxis(pic,xlabel,min.x,max.x,p,arrow,margin,above);
+ yaxis(pic,ylabel,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)
+{
+ 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,Scale(pic,z),dir,size,p);
+}
+
+void xtick(picture pic=currentpicture, real x, pair dir=N,
+ real size=Ticksize, pen p=currentpen)
+{
+ xtick(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)
+{
+ xtick(pic,z,dir,size,p);
+}
+
+void ytick(picture pic=currentpicture, real y, pair dir=E,
+ real size=Ticksize, pen p=currentpen)
+{
+ xtick(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(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);
+ xtick(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)
+{
+ ytick(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=nullpen)
+{
+ 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=nullpen)
+{
+ 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=nullpen)
+{
+ 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=nullpen)
+{
+ 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=nullpen)
+{
+ 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;
+ 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;
+ 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(defaultspline);
+
+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 differentlengths="attempt to graph arrays of different lengths";
+string conditionlength="condition array has different length than data";
+
+void checklengths(int x, int y, string text=differentlengths)
+{
+ if(x != y)
+ abort(text+": "+string(x)+" != "+string(y));
+}
+
+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);
+}
+
+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;
+}
+
+picture vectorfield(path vector(pair), pair a, pair b,
+ int nx=nmesh, int ny=nx,
+ bool autoscale=true, bool truesize=false,
+ pen p=currentpen, arrowbar arrow=Arrow,
+ margin margin=PenMargin)
+{
+ picture pic;
+ real dx=1/nx;
+ real dy=1/ny;
+ real scale;
+ if(autoscale) {
+ real size(pair z) {
+ path g=vector(z);
+ return abs(point(g,size(g)-1)-point(g,0));
+ }
+ real max=size((0,0));
+ 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))));
+ }
+ pair lambda=b-a;
+ scale=min(lambda.x/nx,lambda.y/ny)/max;
+ } 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(truesize)
+ draw(z,pic,vector(z),p,arrow);
+ else
+ draw(pic,shift(z)*scale(scale)*vector(z),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(angle1 >= angle2 && direction) angle1 -= 2pi;
+ if(angle2 >= angle1 && !direction) 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/Build/source/utils/asymptote/base/graph3.asy b/Build/source/utils/asymptote/base/graph3.asy
new file mode 100644
index 00000000000..43bf62d26ca
--- /dev/null
+++ b/Build/source/utils/asymptote/base/graph3.asy
@@ -0,0 +1,1926 @@
+// Three-dimensional graphing routines
+
+private import math;
+import graph;
+import three;
+
+triple zero3(real) {return O;}
+
+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);
+}
+
+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.vector());
+ 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));
+ 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);
+ }
+
+ begingroup3(pic);
+ 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);
+ }
+ endgroup3(pic);
+
+ 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(L.s != "" && primary)
+ labelaxis(pic,t,L,G,locate,Sign,ticklabels);
+ };
+}
+
+// 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.vector(),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);
+ triple a2=xmin == -infinity ? tinv*(lb.x-min3(p).x,ytrans(t,y2),
+ ztrans(t,z2)) : (xmin,y2,z2);
+ triple b2=xmax == infinity ? tinv*(rt.x-max3(p).x,ytrans(t,y2),
+ ztrans(t,z2)) : (xmax,y2,z2);
+
+ 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(y2) ? 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);
+ triple a2=ymin == -infinity ? tinv*(xtrans(t,x2),lb.y-min3(p).y,
+ ztrans(t,z2)) : (x2,ymin,z2);
+ triple b2=ymax == infinity ? tinv*(xtrans(t,x2),rt.y-max3(p).y,
+ ztrans(t,z2)) : (x2,ymax,z2);
+
+ 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(x2) ? 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);
+ triple a2=zmin == -infinity ? tinv*(xtrans(t,x2),ytrans(t,y2),
+ lb.z-min3(p).z) : (x2,y2,zmin);
+ triple b2=zmax == infinity ? tinv*(xtrans(t,x2),ytrans(t,y2),
+ rt.z-max3(p).z) : (x2,y2,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(x2) ? 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.userMinz(floor(pic.userMin.z));
+ if(pic.scale.z.automax())
+ pic.userMaxz(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.vector();
+ 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.vector();
+ 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(pic.scale.z.T(min));
+
+ if(pic.scale.z.automax) {
+ if(pic.scale.z.automax()) pic.userMaxz(mz.max);
+ } else pic.userMaxz(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="",
+ triple min=(-infinity,-infinity,-infinity),
+ triple max=(infinity,infinity,infinity),
+ pen p=currentpen, arrowbar3 arrow=None, margin3 margin=NoMargin3)
+{
+ xaxis3(pic,xlabel,min.x,max.x,p,arrow,margin);
+ yaxis3(pic,ylabel,min.y,max.y,p,arrow,margin);
+ zaxis3(pic,zlabel,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)
+{
+ 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,Scale(pic,v),dir,size,p);
+}
+
+void xtick3(picture pic=currentpicture, real x, triple dir=Y,
+ real size=Ticksize, pen p=currentpen)
+{
+ xtick(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)
+{
+ xtick(pic,v,dir,size,p);
+}
+
+void ytick(picture pic=currentpicture, real y, triple dir=X,
+ real size=Ticksize, pen p=currentpen)
+{
+ xtick(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 ztick(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,v);
+ xtick(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)
+{
+ ytick(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)
+{
+ ztick(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=nullpen)
+{
+ 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=nullpen)
+{
+ 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=nullpen)
+{
+ 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=nullpen)
+{
+ 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=nullpen)
+{
+ 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=nullpen)
+{
+ 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);
+}
+
+// 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);
+ 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];
+ 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]});
+ }
+ }
+ return s;
+}
+
+private 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 g=surface(count);
+ int k=-1;
+ for(int i=0; i < n; ++i) {
+ bool[] condi=all ? null : cond[i];
+ real xi=x[i];
+ real[] zi=z[i];
+ real[] zp=z[i+1];
+ real[] ri=r[i];
+ real[] rp=r[i+1];
+ real[] pi=p[i];
+ real[] pp=p[i+1];
+ real[] qi=q[i];
+ real[] qp=q[i+1];
+ real xp=x[i+1];
+ real hx=(xp-xi)/3;
+ for(int j=0; j < m; ++j) {
+ real yj=y[j];
+ real yp=y[j+1];
+ if(all || condi[j]) {
+ triple[][] P={
+ {O,O,O,O},
+ {O,O,O,O},
+ {O,O,O,O},
+ {O,O,O,O}};
+ real hy=(yp-yj)/3;
+ real hxy=hx*hy;
+ // first x and y directions
+ for(int k=0 ; k < 4 ; ++k) {
+ P[k][0] += xi*X;
+ P[0][k] += yj*Y;
+ P[k][1] += (xp+2*xi)/3*X;
+ P[1][k] += (yp+2*yj)/3*Y;
+ P[k][2] += (2*xp+xi)/3*X;
+ P[2][k] += (2*yp+yj)/3*Y;
+ P[k][3] += xp*X;
+ P[3][k] += yp*Y;
+ }
+ // now z, first the value
+ P[0][0] += zi[j]*Z;
+ P[0][3] += zp[j]*Z;
+ P[3][0] += zi[j+1]*Z;
+ P[3][3] += zp[j+1]*Z;
+ // 2nd, first derivative
+ P[0][1] += (P[0][0].z+hx*pi[j])*Z;
+ P[3][1] += (P[3][0].z+hx*pi[j+1])*Z;
+ P[0][2] += (P[0][3].z-hx*pp[j])*Z;
+ P[3][2] += (P[3][3].z-hx*pp[j+1])*Z;
+ P[1][0] += (P[0][0].z+hy*qi[j])*Z;
+ P[1][3] += (P[0][3].z+hy*qp[j])*Z;
+ P[2][0] += (P[3][0].z-hy*qi[j+1])*Z;
+ P[2][3] += (P[3][3].z-hy*qp[j+1])*Z;
+ // 3nd, second derivative
+ P[1][1] += (P[1][0].z+P[0][1].z-P[0][0].z+hxy*ri[j])*Z;
+ P[2][1] += (P[2][0].z+P[3][1].z-P[3][0].z-hxy*ri[j+1])*Z;
+ P[1][2] += (P[0][2].z+P[1][3].z-P[0][3].z-hxy*rp[j])*Z;
+ P[2][2] += (P[3][2].z+P[2][3].z-P[3][3].z+hxy*rp[j+1])*Z;
+ g.s[++k]=patch(P);
+ }
+ }
+ }
+ return g;
+}
+
+// return the surface described by a real matrix f, interpolated with
+// splinetype.
+surface surface(real[][] f, real[] x, real[] y,
+ splinetype splinetype=null, bool[][] cond={})
+{
+ if(splinetype == null)
+ splinetype=(x[0] == x[x.length-1] && y[0] == y[y.length-1]) ?
+ 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]=splinetype(x,ft[j]);
+ real[][] q=new real[n][];
+ for(int i=0; i < n ; ++i)
+ q[i]=splinetype(y,f[i]);
+ real[][] qt=transpose(q);
+ real[] d1=splinetype(x,qt[0]);
+ real[] d2=splinetype(x,qt[m-1]);
+ real[][] r=new real[n][];
+ for(int i=0; i < n ; ++i)
+ r[i]=clamped(d1[i],d2[i])(y,f[i]);
+ return bispline(f,transpose(tp),q,r,x,y,cond);
+}
+
+// return the surface described by a real matrix f, interpolated with
+// splinetype.
+surface surface(real[][] f, pair a, pair b, splinetype splinetype,
+ 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,splinetype,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 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 splinetype.
+surface surface(real f(pair z), pair a, pair b, int nx=nmesh, int ny=nx,
+ splinetype splinetype, 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,splinetype,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)
+{
+ pen thin=is3D() ? thin() : defaultpen;
+ begingroup3(pic);
+ 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);
+ 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);
+ }
+ }
+ }
+ endgroup3(pic);
+}
+
+void draw(picture pic=currentpicture, Label[] L=new Label[],
+ guide3[][] g, pen p=currentpen)
+{
+ draw(pic,L,g,sequence(new pen(int) {return p;},g.length));
+}
+
+picture vectorfield(path3 vector(pair z), triple f(pair z),
+ pair a, pair b, int nx=nmesh, int ny=nx,
+ bool autoscale=true, pen p=currentpen,
+ arrowbar3 arrow=Arrow3, margin3 margin=PenMargin3)
+{
+ picture pic;
+ real dx=1/nx;
+ real dy=1/ny;
+ real scale;
+ if(autoscale) {
+ 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 <= 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))));
+ }
+ pair lambda=(abs(f((b.x,a.y))-f(a)),abs(f((a.x,b.y))-f(a)));
+ scale=min(lambda.x/nx,lambda.y/ny)/max;
+ } 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);
+ draw(pic,shift(f(z))*scale3(scale)*vector(z),p,arrow,margin);
+ }
+ }
+ 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(phi1 >= phi2 && direction) phi1 -= 2pi;
+ if(phi2 >= phi1 && !direction) phi2 -= 2pi;
+
+ 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)
+{
+ return Arc(c,r,90,0,90,360,normal,n)&cycle;
+}
diff --git a/Build/source/utils/asymptote/base/graph_settings.asy b/Build/source/utils/asymptote/base/graph_settings.asy
new file mode 100644
index 00000000000..f5d04d0797b
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/grid3.asy b/Build/source/utils/asymptote/base/grid3.asy
new file mode 100644
index 00000000000..dcb220e0746
--- /dev/null
+++ b/Build/source/utils/asymptote/base/grid3.asy
@@ -0,0 +1,399 @@
+// grid3.asy
+// Author: Philippe Ivaldi (Grids in 3D)
+// 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;
+ og.create(pic,pic.userMin--X(pic),Y(pic)--XY(pic),pic.userMin--Z(pic),
+ pic.userMin.x,pic.userMax.x,pos,pic.scale.x);
+ return og;
+ };
+};
+grid3routine XYgrid=XYgrid();
+
+grid3routine YXgrid(position pos=Relative(0)) {
+ return new grid3(picture pic) {
+ grid3 og;
+ og.create(pic,pic.userMin--Y(pic),X(pic)--XY(pic),pic.userMin--Z(pic),
+ pic.userMin.y,pic.userMax.y,pos,pic.scale.y);
+ return og;
+ };
+};
+grid3routine YXgrid=YXgrid();
+
+
+grid3routine XZgrid(position pos=Relative(0)) {
+ return new grid3(picture pic) {
+ grid3 og;
+ og.create(pic,pic.userMin--X(pic),Z(pic)--ZX(pic),pic.userMin--Y(pic),
+ pic.userMin.x,pic.userMax.x,pos,pic.scale.x);
+ return og;
+ };
+};
+grid3routine XZgrid=XZgrid();
+
+grid3routine ZXgrid(position pos=Relative(0)) {
+ return new grid3(picture pic) {
+ grid3 og;
+ og.create(pic,pic.userMin--Z(pic),X(pic)--ZX(pic),pic.userMin--Y(pic),
+ pic.userMin.z,pic.userMax.z,pos,pic.scale.z);
+ return og;
+ };
+};
+grid3routine ZXgrid=ZXgrid();
+
+grid3routine YZgrid(position pos=Relative(0)) {
+ return new grid3(picture pic) {
+ grid3 og;
+ og.create(pic,pic.userMin--Y(pic),Z(pic)--YZ(pic),pic.userMin--X(pic),
+ pic.userMin.y,pic.userMax.y,pos,pic.scale.y);
+ return og;
+ };
+};
+grid3routine YZgrid=YZgrid();
+
+grid3routine ZYgrid(position pos=Relative(0)) {
+ return new grid3(picture pic) {
+ grid3 og;
+ og.create(pic,pic.userMin--Z(pic),Y(pic)--YZ(pic),pic.userMin--X(pic),
+ pic.userMin.z,pic.userMax.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/Build/source/utils/asymptote/base/interpolate.asy b/Build/source/utils/asymptote/base/interpolate.asy
new file mode 100644
index 00000000000..a6b820bc7d5
--- /dev/null
+++ b/Build/source/utils/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 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/Build/source/utils/asymptote/base/labelpath.asy b/Build/source/utils/asymptote/base/labelpath.asy
new file mode 100644
index 00000000000..fe597a1608d
--- /dev/null
+++ b/Build/source/utils/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;
+ }
+ write("warning: 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/Build/source/utils/asymptote/base/labelpath3.asy b/Build/source/utils/asymptote/base/labelpath3.asy
new file mode 100644
index 00000000000..acfa64d528f
--- /dev/null
+++ b/Build/source/utils/asymptote/base/labelpath3.asy
@@ -0,0 +1,86 @@
+// Fit a label to a path3.
+// Author: Jens Schwaiger
+
+import three;
+size(7.5cm,0);
+real eps=100*realEpsilon;
+currentprojection=perspective(20,18,8);
+currentlight=light(8,10,2);
+//texpreamble("\textwidth=50cm"); // Avoid line breaking in long texts
+
+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;
+ for(int i=1; i < subdiv+1; i=i+1) {
+ bf[i][0]=dir(p,reltime(p,reltimestart+(i/subdiv)*lg));
+ };
+ for(int i=1; i < subdiv+1; i=i+1) {
+ 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 txt, path3 p, real angle=90, triple optional=O)
+{
+ real Cos=Cos(angle);
+ real Sin=Sin(angle);
+ path[] text=shift(-labelmargin(currentpen)*NE)*
+ texpath(Label(txt,(0,0),NE,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/Build/source/utils/asymptote/base/latin1.asy b/Build/source/utils/asymptote/base/latin1.asy
new file mode 100644
index 00000000000..3d76925be24
--- /dev/null
+++ b/Build/source/utils/asymptote/base/latin1.asy
@@ -0,0 +1,2 @@
+usepackage("fontenc","T1");
+usepackage("inputenc","latin1");
diff --git a/Build/source/utils/asymptote/base/markers.asy b/Build/source/utils/asymptote/base/markers.asy
new file mode 100644
index 00000000000..3fef6eb2d6c
--- /dev/null
+++ b/Build/source/utils/asymptote/base/markers.asy
@@ -0,0 +1,217 @@
+// Mark routines and markers written by Philippe Ivaldi.
+
+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/Build/source/utils/asymptote/base/math.asy b/Build/source/utils/asymptote/base/math.asy
new file mode 100644
index 00000000000..f204f746349
--- /dev/null
+++ b/Build/source/utils/asymptote/base/math.asy
@@ -0,0 +1,392 @@
+// 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 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;
+}
+
+// If strict=false, return whether i > j implies x[i] >= x[j]
+// If strict=true, return whether i > j implies x[i] > x[j]
+bool increasing(real[] x, bool strict=false)
+{
+ real[] xp=copy(x);
+ xp.delete(0);
+ xp.push(0);
+ bool[] b=strict ? (xp > x) : (xp >= x);
+ b[x.length-1]=true;
+ return all(b);
+}
+
+// Return the indices of consecutive true-element segments of bool[] b.
+int[][] segment(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=0; i < edge.length-1;) {
+ int start=stop;
+ stop=edge[++i];
+ if(b[start])
+ segment.push(sequence(start,stop-1));
+ }
+ return segment;
+}
+
+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;
+}
+
+real[][] operator + (real[][] a, real[][] b)
+{
+ int n=a.length;
+ real[][] m=new real[n][];
+ for(int i=0; i < n; ++i)
+ m[i]=a[i]+b[i];
+ return m;
+}
+
+real[][] operator - (real[][] a, real[][] b)
+{
+ int n=a.length;
+ real[][] m=new real[n][];
+ for(int i=0; i < n; ++i)
+ m[i]=a[i]-b[i];
+ return m;
+}
+
+private string incommensurate=
+ "Multiplication of incommensurate matrices is undefined";
+
+real[] operator * (real[] b, real[][] a)
+{
+ int nb=b.length;
+ if(nb != a.length)
+ abort(incommensurate);
+ int na0=a[0].length;
+ real[] m=new real[na0];
+ for(int j=0; j < na0; ++j) {
+ real sum;
+ for(int k=0; k < nb; ++k)
+ sum += b[k]*a[k][j];
+ m[j]=sum;
+ }
+ return m;
+}
+
+real[][] operator * (real[][] a, real b)
+{
+ int n=a.length;
+ real[][] m=new real[n][];
+ for(int i=0; i < n; ++i)
+ m[i]=a[i]*b;
+ return m;
+}
+
+real[][] operator * (real b, real[][] a)
+{
+ return a*b;
+}
+
+real[][] operator / (real[][] a, real b)
+{
+ return a*(1/b);
+}
+
+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, 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.
+ 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;
+}
diff --git a/Build/source/utils/asymptote/base/metapost.asy b/Build/source/utils/asymptote/base/metapost.asy
new file mode 100644
index 00000000000..525ffe755e1
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/nopapersize.ps b/Build/source/utils/asymptote/base/nopapersize.ps
new file mode 100644
index 00000000000..67e31404744
--- /dev/null
+++ b/Build/source/utils/asymptote/base/nopapersize.ps
@@ -0,0 +1,3 @@
+@ a4size 0in 0in
+
+@ letterSize 0in 0in
diff --git a/Build/source/utils/asymptote/base/obj.asy b/Build/source/utils/asymptote/base/obj.asy
new file mode 100644
index 00000000000..02f839b594d
--- /dev/null
+++ b/Build/source/utils/asymptote/base/obj.asy
@@ -0,0 +1,104 @@
+// 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=word(line(input(datafile)));
+ 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: ",(string) 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);
+ }
+}
+
+void draw(picture pic=currentpicture, obj o, light light=currentlight)
+{
+ draw(pic,o.s,o.surfacepen,o.meshpen,light);
+}
diff --git a/Build/source/utils/asymptote/base/ode.asy b/Build/source/utils/asymptote/base/ode.asy
new file mode 100644
index 00000000000..5135dd9443f
--- /dev/null
+++ b/Build/source/utils/asymptote/base/ode.asy
@@ -0,0 +1,15 @@
+real euler(real y, real f(real x, real y), real a, real b=a, int n=0,
+ real h=0, bool dynamic=false, real tolmin=0, real tolmax=0)
+{
+ if(h == 0) {
+ if(b == a) return y;
+ if(n == 0) abort("Either n or h must be specified");
+ else h=(b-a)/n;
+ }
+ real x=a;
+ for(int i=0; i < n; ++i) {
+ y += h*f(x,y);
+ x += h;
+ }
+ return y;
+}
diff --git a/Build/source/utils/asymptote/base/palette.asy b/Build/source/utils/asymptote/base/palette.asy
new file mode 100644
index 00000000000..e9d3fb921db
--- /dev/null
+++ b/Build/source/utils/asymptote/base/palette.asy
@@ -0,0 +1,473 @@
+private import graph;
+
+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)
+{
+ _image(f,transpose ? transpose(data) : copy ? copy(data) : data,
+ initial,final,palette,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)
+{
+ _image(f,transpose ? transpose(data) : copy ? copy(data) : data,
+ initial,final,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);
+ int minindex=floor((dmin-rmin)/(rmax-rmin)*palette.length);
+ if(minindex < 0) minindex=0;
+ int maxindex=floor((dmax-rmin)/(rmax-rmin)*palette.length);
+ if(maxindex > palette.length) maxindex=palette.length;
+ if(minindex > 0 || maxindex < palette.length) {
+ pen[] newpalette;
+ for(int i=minindex; i < maxindex; ++i)
+ newpalette.push(palette[i]);
+ return newpalette;
+ }
+ 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(transpose) f=transpose(f);
+ else 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.add(new void(frame F, transform t) {
+ _image(F,f,initial,final,palette,t,copy=false,antialias=antialias);
+ },true);
+ pic.addBox(initial,final);
+ 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)/nx));
+ 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)/ny)),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(transpose) data=transpose(data);
+ else if(copy) data=copy(data);
+
+ initial=Scale(pic,initial);
+ final=Scale(pic,final);
+
+ pic.add(new void(frame F, transform t) {
+ _image(F,data,initial,final,t,copy=false,antialias=antialias);
+ },true);
+ pic.addBox(initial,final);
+}
+
+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);
+
+ // 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;
+ int m=n > 0 ? f[0].length : 0;
+ pen[][] p=new pen[n][m];
+ 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)];},m);
+ }
+ 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();
+
+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)};
+ if(vertical) pdata=transpose(pdata);
+
+ pic.add(new void(frame f, transform t) {
+ _image(f,pdata,initial,final,palette,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/Build/source/utils/asymptote/base/patterns.asy b/Build/source/utils/asymptote/base/patterns.asy
new file mode 100644
index 00000000000..56fa3bf4a24
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/plain.asy b/Build/source/utils/asymptote/base/plain.asy
new file mode 100644
index 00000000000..a918a6bdc09
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain.asy
@@ -0,0 +1,270 @@
+/*****
+ * 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) {
+ write(stdout,"warning: using possibly incompatible version "+
+ version.VERSION+" of plain.asy"+'\n');
+}
+
+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_strings;
+include plain_markers;
+include plain_arrows;
+include plain_debugger;
+
+typedef void exitfcn();
+
+bool needshipout() {
+ return !shipped && !currentpicture.empty();
+}
+
+void updatefunction()
+{
+ if(!currentpicture.uptodate) shipout();
+}
+
+void exitfunction()
+{
+ if(needshipout()) shipout();
+}
+
+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()
+{
+ write("warning: 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()
+{
+ write("warning: 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);
+}
+
+// Conditionally process each file name in array s in a new environment.
+void asy(string format, bool overwrite=false ... string[] s)
+{
+ for(int i=0; i < s.length; ++i) {
+ string f=s[i];
+ int n=rfind(f,".asy");
+ if(n != -1) f=erase(f,n,-1);
+ if(overwrite || error(input(f+"."+format,check=false))) {
+ string outformat=settings.outformat;
+ bool interactiveView=settings.interactiveView;
+ bool batchView=settings.batchView;
+ settings.outformat=format;
+ settings.interactiveView=false;
+ settings.batchView=false;
+ eval("defaultfilename=\""+f+"\"; import \""+f+
+ "\" as dummy; exitfunction()");
+ 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);
+ shipped=false;
+ atupdate(updatefunction);
+ atexit(exitfunction);
+ settings.autoimport=s;
+}
+
+cputime();
diff --git a/Build/source/utils/asymptote/base/plain_Label.asy b/Build/source/utils/asymptote/base/plain_Label.asy
new file mode 100644
index 00000000000..72f924224d4
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_Label.asy
@@ -0,0 +1,601 @@
+// A rotation in the direction dir limited to [-90,90]
+// This is useful for rotating text along a line in the direction dir.
+transform rotate(explicit pair dir)
+{
+ real angle=degrees(dir);
+ if(angle > 90 && angle < 270) angle -= 180;
+ return rotate(angle);
+}
+
+real angle(transform t)
+{
+ pair z=(2t.xx*t.yy,t.yx*t.yy-t.xx*t.xy);
+ if(t.xx < 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;
+ D=abs(A[1][1]);
+ if(D != 0) 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;}
+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));};
+}
+
+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(scaleless(shiftless(t))*align);
+ label(f,s,size,embed(t)*shiftless(T),
+ t*position+align*labelmargin(p0)+shift(T)*0,align,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 ? -Align*dir(t*g,position)*I : Align);
+ },true);
+ }
+
+ 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,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=nullpen, 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=nullpen, filltype filltype=NoFill)
+{
+ label(pic,L,L.position,align,p,filltype);
+}
+
+void label(picture pic=currentpicture, Label L, explicit path g,
+ align align=NoAlign, pen p=nullpen, 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=nullpen, 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)
+{
+ static string[] stringcache;
+ static pen[] pencache;
+ static path[][] pathcache;
+ path[] g;
+
+ string s=L.s;
+ pen p=L.p;
+ int k=0;
+ int i;
+ while((i=find(stringcache == s,++k)) >= 0) {
+ if(pencache[i] == p) {
+ g=pathcache[i];
+ break;
+ }
+ }
+
+ if(i == -1) {
+ g=_texpath(s,p);
+ stringcache.push(s);
+ pencache.push(p);
+ pathcache.push(g);
+ }
+
+ pair a;
+ if(g.length == 0) return g;
+ pair m=min(g);
+ pair M=max(g);
+ pair dir=rectify(inverse(L.T)*-L.align.dir);
+ if(basealign(p) == 1)
+ dir -= (0,(1-dir.y)*m.y/(M.y-m.y));
+ a=m+realmult(dir,M-m);
+
+ return shift(L.position+L.align.dir*labelmargin(p))*L.T*shift(-a)*g;
+}
diff --git a/Build/source/utils/asymptote/base/plain_arcs.asy b/Build/source/utils/asymptote/base/plain_arcs.asy
new file mode 100644
index 00000000000..01e750f42cd
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_arcs.asy
@@ -0,0 +1,43 @@
+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(t1 >= t2 && direction) t1 -= n;
+ if(t2 >= t1 && !direction) 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/Build/source/utils/asymptote/base/plain_arrows.asy b/Build/source/utils/asymptote/base/plain_arrows.asy
new file mode 100644
index 00000000000..0ef8c2b2d5c
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_arrows.asy
@@ -0,0 +1,569 @@
+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[][] Tl=transpose(intersections(left,base));
+ real[][] Tr=transpose(intersections(right,base));
+ return new real[] {Tl.length > 0 ? Tl[0][0] : 1,
+ Tr.length > 0 ? Tr[0][0] : 1};
+}
+
+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,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);
+ 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,x)*r;
+ path right=rotate(angle*factor,x)*r;
+ real[] T=arrowbasepoints(base,left,right);
+ return subpath(left,T[0],0)--subpath(right,T[1],0);
+};
+
+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);
+ pair denom=point(right,T[1])-y;
+ real factor=denom != 0 ? length((point(left,T[0])-y)/denom) : 1;
+ path left=rotate(-angle,x)*r;
+ path right=rotate(angle*factor,x)*r;
+ real[] T=arrowbasepoints(base,left,right);
+ 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="", path g, align align=NoAlign,
+ pen p=currentpen, arrowbar arrow=None, arrowbar bar=None,
+ margin margin=NoMargin, Label legend="", marker marker=nomarker)
+{
+ Label L=L.copy();
+ L.align(align);
+ if(marker != nomarker && !marker.above) marker.mark(pic,g);
+ bool drawpath=arrow(pic,g,p,margin);
+ if(bar(pic,g,p,margin) && drawpath) _draw(pic,g,p,margin);
+ if(L.s != "") {
+ L.p(p);
+ L.out(pic,g);
+ }
+ if(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="", path g,
+ align align=NoAlign, pen p=currentpen, arrowbar arrow=None,
+ arrowbar bar=None, margin margin=NoMargin, Label legend="",
+ 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="", marker marker=nomarker)
+{
+ 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="", 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="", 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="", 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="", pair b, pair dir,
+ real length=arrowlength, align align=NoAlign,
+ pen p=currentpen, arrowbar arrow=Arrow, margin margin=EndMargin)
+{
+ Label 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);
+}
+
+// Force an array of 2D pictures to be as least as large as picture all.
+void rescale2(picture[] pictures, picture all)
+{
+ if(!all.empty2()) {
+ transform t=inverse(all.calculateTransform()*pictures[0].T);
+ pair m=t*min(all);
+ pair M=t*max(all);
+ for(int i=0; i < pictures.length; ++i) {
+ draw(pictures[i],m,nullpen);
+ draw(pictures[i],M,nullpen);
+ }
+ }
+}
+
+// Force an array of pictures to have a uniform scaling.
+void rescale(picture[] pictures)
+{
+ if(pictures.length == 0) return;
+ picture all;
+ size(all,pictures[0]);
+ for(picture pic : pictures)
+ add(all,pic);
+ rescale2(pictures,all);
+}
diff --git a/Build/source/utils/asymptote/base/plain_boxes.asy b/Build/source/utils/asymptote/base/plain_boxes.asy
new file mode 100644
index 00000000000..6ec7f23c985
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_boxes.asy
@@ -0,0 +1,136 @@
+// 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;
+ path g=box(min(src)+0.5*sign*min(p)-z,max(src)+0.5*sign*max(p)+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;
+ path g=ellipse(0.5*(M+m),factor*D.x+0.5*sign*max(p).x+xmargin,
+ factor*D.y+0.5*sign*max(p).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(string prefix="", picture pic=currentpicture,
+ real xmargin=0, real ymargin=xmargin,
+ pen p=currentpen, filltype filltype=NoFill)
+{
+ frame f=pic.fit(prefix,max(pic.xsize-2*xmargin,0),max(pic.ysize-2*ymargin,0));
+ box(f,xmargin,ymargin,p,filltype,false);
+ return f;
+}
diff --git a/Build/source/utils/asymptote/base/plain_constants.asy b/Build/source/utils/asymptote/base/plain_constants.asy
new file mode 100644
index 00000000000..c1d8c086f71
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_constants.asy
@@ -0,0 +1,157 @@
+restricted int undefined=intMax+((intMax % 2)-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 up=(0,1);
+restricted pair down=(0,-1);
+restricted pair right=(1,0);
+restricted pair left=(-1,0);
+
+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;
+
+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 tab(file file) {write(file,'\t');}
+void comma(file file) {write(file,',');}
+typedef void suffix(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);
+ else write("default");
+}
+
+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/Build/source/utils/asymptote/base/plain_debugger.asy b/Build/source/utils/asymptote/base/plain_debugger.asy
new file mode 100644
index 00000000000..8568ee0bd8d
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/plain_filldraw.asy b/Build/source/utils/asymptote/base/plain_filldraw.asy
new file mode 100644
index 00000000000..b9cd191e075
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_filldraw.asy
@@ -0,0 +1,234 @@
+// 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);
+ 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);
+ });
+}
+
+// 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/Build/source/utils/asymptote/base/plain_margins.asy b/Build/source/utils/asymptote/base/plain_margins.asy
new file mode 100644
index 00000000000..c4522754481
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/plain_markers.asy b/Build/source/utils/asymptote/base/plain_markers.asy
new file mode 100644
index 00000000000..67b218f716d
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_markers.asy
@@ -0,0 +1,377 @@
+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);
+}
+
+marker nomarker;
+
+real circlescale=0.85;
+
+marker[] Mark={
+ marker(scale(circlescale)*unitcircle),
+ marker(polygon(3)),marker(polygon(4)),
+ marker(polygon(5)),marker(invert*polygon(3)),
+ marker(cross(4)),marker(cross(6))
+};
+
+marker[] MarkFill={
+ marker(scale(circlescale)*unitcircle,Fill),marker(polygon(3),Fill),
+ marker(polygon(4),Fill),marker(polygon(5),Fill),
+ marker(invert*polygon(3),Fill)
+};
+
+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);
+}
+
+void dot(frame f, pair z, pen p=currentpen, filltype filltype=Fill)
+{
+ if(filltype == Fill)
+ draw(f,z,dotsize(p)+p);
+ else {
+ transform t=shift(z);
+ path g=t*scale(0.5*(dotsize(p)-linewidth(p)))*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=Fill)
+{
+ 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=Fill)
+{
+ 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=Fill)
+{
+ 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=Fill)
+{
+ 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=Fill)
+{
+ 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=Fill)
+{
+ dot(pic,L,L.position,p,filltype);
+}
+
+// A dot in a frame.
+frame dotframe(pen p=currentpen, filltype filltype=Fill)
+{
+ frame f;
+ dot(f,(0,0),p,filltype);
+ return f;
+}
+
+frame dotframe=dotframe();
+
+marker dot(pen p=currentpen, filltype filltype=Fill)
+{
+ return marker(dotframe(p,filltype));
+}
+
+marker dot=dot();
+
diff --git a/Build/source/utils/asymptote/base/plain_paths.asy b/Build/source/utils/asymptote/base/plain_paths.asy
new file mode 100644
index 00000000000..486f3f0cc7b
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_paths.asy
@@ -0,0 +1,380 @@
+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(file file, string s="", explicit guide[] x, suffix suffix=none)
+{
+ write(file,s,(path[]) x,suffix);
+}
+
+void write(string s="", explicit path[] x, suffix suffix=endl)
+{
+ write(stdout,s,x,suffix);
+}
+
+void write(string s="", explicit guide[] x, suffix suffix=endl)
+{
+ write(stdout,s,(path[]) x,suffix);
+}
+
+private string nopoints="nullpath has no points";
+
+pair min(explicit path[] p)
+{
+ if(p.length == 0) abort(nopoints);
+ pair minp=min(p[0]);
+ for(int i=1; i < p.length; ++i)
+ minp=minbound(minp,min(p[i]));
+ return minp;
+}
+
+pair max(explicit path[] p)
+{
+ if(p.length == 0) abort(nopoints);
+ pair maxp=max(p[0]);
+ for(int i=1; i < p.length; ++i)
+ maxp=maxbound(maxp,max(p[i]));
+ return maxp;
+}
+
+guide operator ::(... guide[] a)
+{
+ if(a.length == 0) return nullpath;
+ guide g=a[0];
+ for(int i=1; i < a.length; ++i)
+ g=g..operator tension(1,true)..a[i];
+ return g;
+}
+
+guide operator ---(... guide[] a)
+{
+ if(a.length == 0) return nullpath;
+ guide g=a[0];
+ for(int i=1; i < a.length; ++i)
+ g=g..operator tension(infinity,true)..a[i];
+ return g;
+}
+
+// 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 h)
+{
+ return 0.5*(dir(p)+dir(h));
+}
+
+// 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);
+ real[] T=intersections(p,z,z+I*dir(p,i));
+ // 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(inside(windingnumber(p,m),fillrule)) return m;
+ }
+ }
+ }
+ abort("cannot find an interior point");
+ return 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;
+}
diff --git a/Build/source/utils/asymptote/base/plain_pens.asy b/Build/source/utils/asymptote/base/plain_pens.asy
new file mode 100644
index 00000000000..88fc112f684
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_pens.asy
@@ -0,0 +1,341 @@
+real labelmargin=0.3;
+real dotfactor=6;
+
+pen solid=linetype("");
+pen dotted=linetype("0 4");
+pen dashed=linetype("8 8");
+pen longdashed=linetype("24 8");
+pen dashdotted=linetype("8 8 0 8");
+pen longdashdotted=linetype("24 8 0 8");
+
+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=dotted+1.0;
+pen Dotted(pen p=currentpen) {return dotted+2*linewidth(p);}
+
+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 inside(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 olive=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 darkbrown=darkred;
+pen pink=palemagenta;
+pen palegrey=palegray;
+pen lightgrey=lightgray;
+pen mediumgrey=mediumgray;
+pen grey=gray;
+pen heavygrey=gray;
+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);
+}
+
+pen font(string name)
+{
+ return fontcommand("\font\ASYfont="+name+"\ASYfont");
+}
+
+pen font(string name, real size)
+{
+ return fontsize(size)+font(name+" at "+(string) size+"pt");
+}
+
+pen font(string encoding, string family, string series="m", string shape="n")
+{
+ 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 hexidecimal string.
+pen rgb(string s)
+{
+ real value(string s, int i) {return hex(substr(s,2i,2))/255;}
+ return rgb(value(s,0),value(s,1),value(s,2));
+}
+
+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/Build/source/utils/asymptote/base/plain_picture.asy b/Build/source/utils/asymptote/base/plain_picture.asy
new file mode 100644
index 00000000000..4d894dc6dea
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_picture.asy
@@ -0,0 +1,1892 @@
+real camerafactor=2; // Factor used for camera adjustment.
+pair viewportsize=0; // Horizontal and vertical viewport limits.
+
+restricted bool Aspect=true;
+restricted bool IgnoreAspect=false;
+
+pair size(frame f)
+{
+ return max(f)-min(f);
+}
+
+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;
+}
+
+// A coordinate in "flex space." A linear combination of user and true-size
+// coordinates.
+struct coord {
+ real user,truesize;
+ bool finite=true;
+
+ // 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;
+ }
+}
+
+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)*((finite(cx.user) ? 0 : 1),
+ (finite(cy.user) ? 0 : 1));
+ 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=(tinf.x == 0 ? z.x : infinity);
+ Cy.user=(tinf.y == 0 ? z.y : infinity);
+ 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);
+ }
+}
+
+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)*((finite(cx.user) ? 0 : 1),
+ (finite(cy.user) ? 0 : 1),
+ (finite(cz.user) ? 0 : 1));
+ 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=(tinf.x == 0 ? z.x : infinity);
+ Cy.user=(tinf.y == 0 ? z.y : infinity);
+ Cz.user=(tinf.z == 0 ? z.z : infinity);
+ Cx.truesize=w.x;
+ Cy.truesize=w.y;
+ Cz.truesize=w.z;
+ push(Cx,Cy,Cz);
+ }
+ }
+}
+
+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;
+
+ // Find the first finite restriction.
+ int first=0;
+ for(first=0; first < n; ++first)
+ if(finite(in[first].user)) break;
+
+ if(first == n)
+ return c;
+ else {
+ // 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 || !finite(x.user))
+ 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;
+}
+
+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;
+ }
+}
+
+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)
+{
+ 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;
+}
+
+struct transformation {
+ transform3 modelview; // For orientation and positioning
+ transform3 projection; // For 3D to 2D projection
+ bool infinity;
+ bool oblique;
+ void operator init(transform3 modelview, bool oblique=false) {
+ this.modelview=modelview;
+ this.projection=identity4;
+ infinity=true;
+ this.oblique=oblique;
+ }
+ void operator init(transform3 modelview, transform3 projection) {
+ this.modelview=modelview;
+ this.projection=projection;
+ infinity=false;
+ oblique=false;
+ }
+ transform3 compute() {
+ return projection*modelview;
+ }
+}
+
+struct projection {
+ transform3 t; // projection*modelview (cached)
+ bool infinity;
+ bool oblique;
+ bool absolute=false;
+ triple camera;
+ triple up;
+ triple target;
+ 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?
+ real angle; // Lens angle (currently only used by PRC viewpoint).
+ int ninterpolate; // Used for projecting nurbs to 2D Bezier curves.
+
+ void calculate() {
+ transformation T=projector(camera,up,target);
+ t=T.compute();
+ infinity=T.infinity;
+ oblique=T.oblique;
+ ninterpolate=infinity ? 1 : 16;
+ }
+
+ transformation transformation() {
+ return projector(camera,up,target);
+ }
+
+ transform3 modelview() {return transformation().modelview;}
+
+ triple vector() {
+ return camera-target;
+ }
+
+ void operator init(triple camera, triple up=(0,0,1), triple target=(0,0,0),
+ bool showtarget=true, bool autoadjust=true,
+ bool center=false, projector projector) {
+ this.camera=camera;
+ this.up=up;
+ this.target=target;
+ 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.oblique=oblique;
+ P.camera=camera;
+ P.up=up;
+ P.target=target;
+ P.showtarget=showtarget;
+ P.autoadjust=autoadjust;
+ P.center=center;
+ P.projector=projector;
+ P.angle=angle;
+ P.ninterpolate=ninterpolate;
+ 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));
+ }
+
+ // 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;
+
+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.
+typedef void drawerBound(frame f, transform t, transform T, pair lb, pair rt);
+
+struct picture {
+ // 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);
+
+ // The functions to do the deferred drawing.
+ drawerBound[] nodes;
+ drawerBound3[] nodes3;
+
+ bool uptodate=true;
+
+ // The coordinates in flex space to be used in sizing the picture.
+ struct bounds {
+ coords2 point,min,max;
+ bool exact=true; // An accurate picture bounds is provided by the user.
+ void erase() {
+ point.erase();
+ min.erase();
+ max.erase();
+ }
+ bounds copy() {
+ bounds b=new bounds;
+ b.point=point.copy();
+ b.min=min.copy();
+ b.max=max.copy();
+ b.exact=exact;
+ return b;
+ }
+ void xclip(real Min, real Max) {
+ point.xclip(Min,Max);
+ min.xclip(Min,Max);
+ max.xclip(Min,Max);
+ }
+ void yclip(real Min, real Max) {
+ point.yclip(Min,Max);
+ min.yclip(Min,Max);
+ max.yclip(Min,Max);
+ }
+ void clip(triple Min, triple Max) {
+ xclip(Min.x,Max.x);
+ yclip(Min.y,Max.y);
+ }
+ }
+
+ 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;
+
+ // Transform to be applied to this picture.
+ transform T;
+ transform3 T3;
+
+ // Cached user-space bounding box
+ triple userMin,userMax;
+ bool userSetx,userSety,userSetz;
+
+ 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;
+
+ void init() {
+ userMin=userMax=(0,0,0);
+ userSetx=userSety=userSetz=false;
+ T3=identity(4);
+ }
+ init();
+
+ // Erase the current picture, retaining any size specification.
+ void erase() {
+ nodes.delete();
+ nodes3.delete();
+ bounds.erase();
+ bounds3.erase();
+ T=identity();
+ scale=new ScaleT;
+ legend.delete();
+ init();
+ }
+
+ bool empty2() {
+ return nodes.length == 0;
+ }
+
+ bool empty3() {
+ return nodes3.length == 0;
+ }
+
+ bool empty() {
+ return empty2() && empty3();
+ }
+
+ pair userMin() {return (userMin.x,userMin.y);}
+ pair userMax() {return (userMax.x,userMax.y);}
+
+ void userMinx(real x) {
+ userMin=(x,userMin.y,userMin.z);
+ userSetx=true;
+ }
+
+ void userMiny(real y) {
+ userMin=(userMin.x,y,userMin.z);
+ userSety=true;
+ }
+
+ void userMinz(real z) {
+ userMin=(userMin.x,userMin.y,z);
+ userSetz=true;
+ }
+
+ void userMaxx(real x) {
+ userMax=(x,userMax.y,userMax.z);
+ userSetx=true;
+ }
+
+ void userMaxy(real y) {
+ userMax=(userMax.x,y,userMax.z);
+ userSety=true;
+ }
+
+ void userMaxz(real z) {
+ userMax=(userMax.x,userMax.y,z);
+ userSetz=true;
+ }
+
+ void userCorners(pair c00, pair c01, pair c10, pair c11) {
+ userMin=(min(c00.x,c01.x,c10.x,c11.x),min(c00.y,c01.y,c10.y,c11.y),
+ userMin.z);
+ userMax=(max(c00.x,c01.x,c10.x,c11.x),max(c00.y,c01.y,c10.y,c11.y),
+ userMax.z);
+ }
+
+ void userCorners(triple c000, triple c001, triple c010, triple c011,
+ triple c100, triple c101, triple c110, triple c111) {
+ userMin=(min(c000.x,c001.x,c010.x,c011.x,c100.x,c101.x,c110.x,c111.x),
+ min(c000.y,c001.y,c010.y,c011.y,c100.y,c101.y,c110.y,c111.y),
+ min(c000.z,c001.z,c010.z,c011.z,c100.z,c101.z,c110.z,c111.z));
+ userMax=(max(c000.x,c001.x,c010.x,c011.x,c100.x,c101.x,c110.x,c111.x),
+ max(c000.y,c001.y,c010.y,c011.y,c100.y,c101.y,c110.y,c111.y),
+ max(c000.z,c001.z,c010.z,c011.z,c100.z,c101.z,c110.z,c111.z));
+ }
+
+ void userCopy(picture pic) {
+ userMin=(triple) pic.userMin;
+ userMax=(triple) pic.userMax;
+ userSetx=pic.userSetx;
+ userSety=pic.userSety;
+ userSetz=pic.userSetz;
+ }
+
+ typedef real binop(real, real);
+
+ // Cache the current user-space bounding box x coodinates
+ void userBoxX(real min, real max, binop m=min, binop M=max) {
+ if(userSetx) {
+ userMin=(m(userMin.x,min),userMin.y,userMin.z);
+ userMax=(M(userMax.x,max),userMax.y,userMax.z);
+ } else {
+ userMin=(min,userMin.y,userMin.z);
+ userMax=(max,userMax.y,userMax.z);
+ userSetx=true;
+ }
+ }
+
+ // Cache the current user-space bounding box y coodinates
+ void userBoxY(real min, real max, binop m=min, binop M=max) {
+ if(userSety) {
+ userMin=(userMin.x,m(userMin.y,min),userMin.z);
+ userMax=(userMax.x,M(userMax.y,max),userMax.z);
+ } else {
+ userMin=(userMin.x,min,userMin.z);
+ userMax=(userMax.x,max,userMax.z);
+ userSety=true;
+ }
+ }
+
+ // Cache the current user-space bounding box z coodinates
+ void userBoxZ(real min, real max, binop m=min, binop M=max) {
+ if(userSetz) {
+ userMin=(userMin.x,userMin.y,m(userMin.z,min));
+ userMax=(userMax.x,userMax.y,M(userMax.z,max));
+ } else {
+ userMin=(userMin.x,userMin.y,min);
+ userMax=(userMax.x,userMax.y,max);
+ userSetz=true;
+ }
+ }
+
+ // Cache the current user-space bounding box
+ void userBox(pair min, pair max) {
+ userBoxX(min.x,max.x);
+ userBoxY(min.y,max.y);
+ }
+
+ // Cache the current user-space bounding box
+ void userBox(triple min, triple max) {
+ userBoxX(min.x,max.x);
+ userBoxY(min.y,max.y);
+ userBoxZ(min.z,max.z);
+ }
+
+ // Clip the current user-space bounding box
+ void userClip(pair min, pair max) {
+ userBoxX(min.x,max.x,max,min);
+ userBoxY(min.y,max.y,max,min);
+ }
+
+ void add(drawerBound d, bool exact=false) {
+ uptodate=false;
+ if(!exact) bounds.exact=false;
+ nodes.push(d);
+ }
+
+ void add(drawer d, bool exact=false, bool above=true) {
+ uptodate=false;
+ if(!exact) bounds.exact=false;
+ nodes.push(new void(frame f, transform t, transform T, pair, pair) {
+ d(f,t*T);
+ });
+ }
+
+ void add(drawerBound3 d, bool exact=false, bool above=true) {
+ uptodate=false;
+ if(!exact) bounds.exact=false;
+ if(above)
+ nodes3.push(d);
+ else
+ nodes3.insert(0,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);
+ }
+
+ void clip(drawer d, bool exact=false) {
+ bounds.clip(userMin,userMax);
+ this.add(d,exact);
+ }
+
+ void clip(drawerBound d, bool exact=false) {
+ bounds.clip(userMin,userMax);
+ this.add(d,exact);
+ }
+
+ // Add a point to the sizing.
+ void addPoint(pair user, pair truesize=0) {
+ bounds.point.push(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);
+ userBox(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.min.push(userMin,trueMin);
+ bounds.max.push(userMax,trueMax);
+ userBox(userMin,userMax);
+ }
+
+ 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);
+ userBox(userMin,userMax);
+ }
+
+ // Add a (user space) path to the sizing.
+ void addPath(path g) {
+ if(size(g) > 0)
+ addBox(min(g),max(g));
+ }
+ void addPath(path[] g) {
+ for(int i=0; i < g.length; ++i)
+ addPath(g[i]);
+ }
+
+ // Add a path to the sizing with the additional padding of a pen.
+ void addPath(path g, pen p) {
+ if(size(g) > 0)
+ addBox(min(g),max(g),min(p),max(p));
+ }
+
+ 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;
+ }
+
+ // 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(finite(c[i].user) && 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(finite(c[i].user) && s.scale(c[i]) > M)
+ M=s.scale(c[i]);
+ return M;
+ }
+
+ // Calculate the min for the final frame, given the coordinate transform.
+ pair min(transform t) {
+ if(bounds.min.x.length == 0 && bounds.point.x.length == 0 &&
+ bounds.max.x.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(min(min(infinity,xs,bounds.point.x),xs,bounds.min.x),
+ xs,bounds.max.x),
+ min(min(min(infinity,ys,bounds.point.y),ys,bounds.min.y),
+ ys,bounds.max.y));
+ }
+
+ // Calculate the max for the final frame, given the coordinate transform.
+ pair max(transform t) {
+ if(bounds.min.x.length == 0 && bounds.point.x.length == 0 &&
+ bounds.max.x.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(max(max(-infinity,xs,bounds.point.x),xs,bounds.min.x),
+ xs,bounds.max.x),
+ max(max(max(-infinity,ys,bounds.point.y),ys,bounds.min.y),
+ ys,bounds.max.y));
+ }
+
+ // 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));
+ }
+
+ // Calculate the sizing constants for the given array and maximum size.
+ real calculateScaling(string dir, coord[] coords, real size,
+ bool warn=true) {
+ access simplex;
+ simplex.problem p=new simplex.problem;
+
+ void addMinCoord(coord c) {
+ // (a*user + b) + truesize >= 0:
+ p.addRestriction(c.user,1,c.truesize);
+ }
+ void addMaxCoord(coord c) {
+ // (a*user + b) + truesize <= size:
+ p.addRestriction(-c.user,-1,size-c.truesize);
+ }
+
+ coord[] m=maxcoords(coords,operator >=);
+ coord[] M=maxcoords(coords,operator <=);
+
+ for(int i=0; i < m.length; ++i)
+ addMinCoord(m[i]);
+ for(int i=0; i < M.length; ++i)
+ addMaxCoord(M[i]);
+
+ int status=p.optimize();
+ if(status == simplex.problem.OPTIMAL) {
+ return scaling.build(p.a(),p.b()).a;
+ } else if(status == simplex.problem.UNBOUNDED) {
+ if(warn) write("warning: "+dir+" scaling in picture unbounded");
+ return 0;
+ } else {
+ if(!warn) return 1;
+ bool userzero=true;
+ for(int i=0; i < coords.length; ++i) {
+ if(coords[i].user != 0) userzero=false;
+ if(!finite(coords[i].user) || !finite(coords[i].truesize))
+ abort("unbounded picture");
+ }
+ if(userzero) return 1;
+ write("warning: cannot fit picture to "+dir+"size "+(string) size
+ +"...enlarging...");
+ return calculateScaling(dir,coords,sqrt(2)*size,warn);
+ }
+ }
+
+ void append(coords2 point, coords2 min, coords2 max, transform t,
+ bounds bounds)
+ {
+ // Add the coord info to this picture.
+ if(t == identity()) {
+ point.append(bounds.point);
+ min.append(bounds.min);
+ max.append(bounds.max);
+ } else {
+ point.push(t,bounds.point,bounds.point);
+ // Add in all 4 corner points, to properly size rectangular pictures.
+ point.push(t,bounds.min,bounds.min);
+ point.push(t,bounds.min,bounds.max);
+ point.push(t,bounds.max,bounds.min);
+ point.push(t,bounds.max,bounds.max);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ // Returns the transform for turning user-space pairs into true-space pairs.
+ transform scaling(real xsize, real ysize, bool keepAspect=true,
+ bool warn=true) {
+ if(xsize == 0 && xunitsize == 0 && ysize == 0 && yunitsize == 0)
+ return identity();
+
+ coords2 Coords;
+
+ append(Coords,Coords,Coords,T,bounds);
+
+ 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;
+
+ 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);
+ }
+
+ 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(int i=0; i < nodes.length; ++i)
+ nodes[i](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(int i=0; i < nodes3.length; ++i)
+ nodes3[i](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));
+ }
+
+ 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);
+ }
+
+ frame scaled() {
+ frame f=fit(fixedscaling);
+ pair d=size(f);
+ static real epsilon=100*realEpsilon;
+ if(d.x > xsize*(1+epsilon))
+ write("warning: frame exceeds xlimit: "+(string) d.x+" > "+
+ (string) xsize);
+ if(d.y > ysize*(1+epsilon))
+ write("warning: 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 thisture
+ // 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);
+ }
+
+ // 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;
+ }
+
+ 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));
+ }
+
+ // 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,
+ 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="",
+ projection P=currentprojection) {
+ return fitter == null ? fit2(xsize,ysize,keepAspect) :
+ fitter(prefix,this,format,xsize,ysize,keepAspect,view,options,script,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;
+ }
+
+ // 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;
+ dest.userCopy(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;
+ }
+
+ // 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(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(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.userSetx) userBoxX(src.userMin.x,src.userMax.x);
+ if(src.userSety) userBoxY(src.userMin.y,src.userMax.y);
+ if(src.userSetz) userBoxZ(src.userMin.z,src.userMax.z);
+
+ 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;
+ }
+}
+
+picture operator * (transform t, picture orig)
+{
+ picture pic=orig.copy();
+ pic.T=t*pic.T;
+ pic.userCorners(t*(pic.userMin.x,pic.userMin.y),
+ t*(pic.userMin.x,pic.userMax.y),
+ t*(pic.userMax.x,pic.userMin.y),
+ t*(pic.userMax.x,pic.userMax.y));
+ pic.bounds.exact=false;
+ return pic;
+}
+
+picture operator * (transform3 t, picture orig)
+{
+ picture pic=orig.copy();
+ pic.T3=t*pic.T3;
+ pic.userCorners(t*pic.userMin,
+ t*(pic.userMin.x,pic.userMin.y,pic.userMax.z),
+ t*(pic.userMin.x,pic.userMax.y,pic.userMin.z),
+ t*(pic.userMin.x,pic.userMax.y,pic.userMax.z),
+ t*(pic.userMax.x,pic.userMin.y,pic.userMin.z),
+ t*(pic.userMax.x,pic.userMin.y,pic.userMax.z),
+ t*(pic.userMax.x,pic.userMax.y,pic.userMin.z),
+ t*pic.userMax);
+ 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 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;
+}
+
+pair point(picture pic=currentpicture, pair dir, bool user=true)
+{
+ pair z=pic.userMin()+realmult(rectify(dir),pic.userMax()-pic.userMin());
+ 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.userMin()+realmult(z,pic.userMax()-pic.userMin());
+}
+
+void add(picture pic=currentpicture, drawer 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 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);
+}
+
+void _draw(picture pic=currentpicture, path g, pen p=currentpen,
+ margin margin=NoMargin)
+{
+ 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)
+{
+ 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 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,false);
+ },true);
+ pic.addPath(g);
+}
+
+void axialshade(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen pena, pair a, pen penb, pair b, bool copy=true)
+{
+ if(copy)
+ g=copy(g);
+ pic.add(new void(frame f, transform t) {
+ axialshade(f,t*g,stroke,pena,t*a,penb,t*b,false);
+ },true);
+ pic.addPath(g);
+}
+
+void radialshade(picture pic=currentpicture, path[] g, bool stroke=false,
+ pen pena, pair a, real ra, pen penb, pair b, real rb,
+ 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,penb,B,RB,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[] 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,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.userClip(min(g),max(g));
+ pic.clip(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)
+{
+ if(pic.clipmin.length > 0 && pic.clipmax.length > 0)
+ pic.userClip(pic.clipmin.pop(),pic.clipmax.pop());
+ pic.clip(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 about position to frame dest 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 about position to picture dest 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 attach(picture,frame,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 tex(picture pic=currentpicture, string s)
+{
+ pic.add(new void(frame f, transform) {
+ tex(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, pair min, pair max)
+{
+ 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();
+}
diff --git a/Build/source/utils/asymptote/base/plain_shipout.asy b/Build/source/utils/asymptote/base/plain_shipout.asy
new file mode 100644
index 00000000000..22aeef7121f
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_shipout.asy
@@ -0,0 +1,113 @@
+// Default file prefix used for inline LaTeX mode
+string defaultfilename;
+
+string outprefix(string prefix=defaultfilename) {
+ string s=prefix != "" ? prefix :
+ (settings.outname == "" && interactive()) ? "out" : settings.outname;
+ return stripextension(s);
+}
+
+string outformat(string format="")
+{
+ if(format == "") format=settings.outformat;
+ if(format == "") format=nativeformat();
+ return format;
+}
+
+
+bool shipped; // Was a picture or frame already shipped out?
+
+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, projection);
+string Embed(string name, string options="", real width=0, real height=0);
+string Link(string label, string text, string options="");
+
+bool prc0(string format="")
+{
+ return settings.prc && (outformat(format) == "pdf" || 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;
+}
+
+include plain_xasy;
+
+void shipout(string prefix=defaultfilename, frame f,
+ string format="", bool wait=false, bool view=true,
+ string options="", string script="",
+ projection P=currentprojection)
+{
+ if(is3D(f)) {
+ f=enclose(prefix,embed3(prefix,f,format,options,script,P));
+ if(settings.render != 0 && !prc(format)) {
+ shipped=true;
+ return;
+ }
+ }
+
+ if(inXasyMode) {
+ erase();
+ 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,xformStack.pop0);
+ shipped=true;
+}
+
+void shipout(string prefix=defaultfilename, picture pic=currentpicture,
+ orientation orientation=orientation,
+ string format="", bool wait=false, bool view=true,
+ string options="", string script="",
+ projection P=currentprojection)
+{
+ if(!uptodate()) {
+ bool inlinetex=settings.inlinetex;
+ bool prc=prc(format);
+ if(prc && !pic.empty3())
+ settings.inlinetex=settings.inlineimage;
+ frame f=pic.fit(prefix,format,view=view,options,script,P);
+ if(!pic.empty2() || settings.render == 0 || prc)
+ shipout(prefix,orientation(f),format,wait,view);
+ settings.inlinetex=inlinetex;
+ }
+
+ pic.uptodate=true;
+ shipped=true;
+}
+
+void newpage(picture pic=currentpicture)
+{
+ pic.add(new void(frame f, transform) {
+ newpage(f);
+ },true);
+}
diff --git a/Build/source/utils/asymptote/base/plain_strings.asy b/Build/source/utils/asymptote/base/plain_strings.asy
new file mode 100644
index 00000000000..2df4743171f
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_strings.asy
@@ -0,0 +1,183 @@
+string defaultformat(int n, string trailingzero="", bool fixed=false,
+ bool signed=true)
+{
+ return "$%"+trailingzero+"."+string(n)+(fixed ? "f" : "g")+"$";
+}
+
+string defaultformat=defaultformat(4);
+
+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
+string[] split(string s, string delimiter)
+{
+ string[] S;
+ int last=0;
+ int i;
+ int N=length(delimiter);
+ while((i=find(s,delimiter,last)) >= 0) {
+ if(i >= last)
+ S.push(substr(s,last,i-last));
+ last=i+N;
+ }
+ int n=length(s);
+ if(n >= last)
+ S.push(substr(s,last,n-last));
+ return 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 != "" ? "\vphantom{"+template+"}"+s : s;
+}
+
+string math(string s)
+{
+ return s != "" ? "$"+s+"$" : s;
+}
+
+string graphic(string name, string options="")
+{
+ if(options != "") options="["+options+"]";
+ return "\includegraphics"+options+"{"+name+"}";
+}
+
+string minipage(string s, real width=100bp)
+{
+ if(latex())
+ return "\begin{minipage}{"+(string) (width*pt)+"pt}"+s+"\end{minipage}";
+ write("warning: minipage requires -tex latex or -tex pdflatex");
+ return "";
+}
+
+void usepackage(string s, string options="")
+{
+ if(latex()) {
+ string usepackage="\usepackage";
+ if(options != "") usepackage += "["+options+"]";
+ texpreamble(usepackage+"{"+s+"}");
+ return;
+ }
+ write("warning: usepackage requires -tex latex or -tex pdflatex");
+}
+
+void pause(string w="Hit enter to continue")
+{
+ write(w);
+ w=stdin;
+}
+
+string math(real x)
+{
+ return math((string) x);
+}
+
+string format(real x)
+{
+ return format(defaultformat,x);
+}
+
+string phantom(string s)
+{
+ return "\phantom{"+s+"}";
+}
diff --git a/Build/source/utils/asymptote/base/plain_xasy.asy b/Build/source/utils/asymptote/base/plain_xasy.asy
new file mode 100644
index 00000000000..162cbfb35ad
--- /dev/null
+++ b/Build/source/utils/asymptote/base/plain_xasy.asy
@@ -0,0 +1,128 @@
+restricted bool inXasyMode=false;
+bool diagnostics=false;
+void report(string text)
+{
+ if(diagnostics)
+ write(text);
+}
+void report(transform t)
+{
+ if(diagnostics)
+ write(t);
+}
+void report(int i)
+{
+ if(diagnostics)
+ write(i);
+}
+void initXasyMode()
+{
+ size(0,0);
+ inXasyMode=true;
+}
+
+void exitXasyMode()
+{
+ inXasyMode=false;
+}
+private picture[] tempStore;
+private picture newPic;
+void startScript()
+{
+ tempStore.push(currentpicture.copy());
+ newPic=new picture;
+ currentpicture=newPic;
+}
+
+void endScript()
+{
+ if(tempStore.length < 1) {
+ abort("endScript() without matching beginScript()");
+ } else {
+ currentpicture=tempStore.pop();
+ add(currentpicture,newPic.fit(),group=false);
+ }
+ shipped=false;
+}
+
+struct indexedTransform {
+ int index;
+ transform t;
+ bool active;
+ void operator init(int index, transform t, bool active=true) {
+ this.index=index;
+ this.t=t;
+ this.active=active;
+ }
+}
+
+struct framedTransformStack {
+ struct transact {
+ transform t;
+ bool active;
+ void operator init(transform t, bool active=true) {
+ this.t=t;
+ this.active=active;
+ }
+ void operator init(indexedTransform i){
+ this.t=i.t;
+ this.active=i.active;
+ }
+ void operator init() {
+ this.t=identity();
+ this.active=true;
+ }
+ }
+ private transact[] stack;
+ private int[] frames;
+ private int stackBase=0;
+ transform pop() {
+ if(stack.length == 0)
+ return identity();
+ else {
+ transform popped=stack[0].t;
+ stack.delete(0);
+ report("Popped");
+ report(popped);
+ return popped;
+ }
+ }
+
+ transform pop0() {
+ if(stack.length == 0)
+ return identity();
+ else {
+ static transform zerotransform=(0,0,0,0,0,0);
+ transform popped=stack[0].active ? stack[0].t : zerotransform;
+ stack.delete(0);
+ report("Popped");
+ report(popped);
+ return popped;
+ }
+ }
+
+ void push(transform t, bool Active=true) {
+ report("Pushed");
+ report(t);
+ stack.push(transact(t,Active));
+ }
+
+ void add(... indexedTransform[] tList) {
+ transact[] toPush;
+ for(int a=0; a < tList.length; ++a)
+ toPush[tList[a].index]=transact(tList[a]);
+ for(int a=0; a < toPush.length; ++a)
+ if(!toPush.initialized(a))
+ toPush[a]=transact();
+ report("Added");
+ report(toPush.length);
+ stack.append(toPush);
+ }
+}
+
+framedTransformStack xformStack;
+
+void deconstruct(picture pic=currentpicture, real magnification=1)
+{
+ deconstruct(pic.fit(),currentpatterns,magnification,xformStack.pop);
+}
diff --git a/Build/source/utils/asymptote/base/pstoedit.asy b/Build/source/utils/asymptote/base/pstoedit.asy
new file mode 100644
index 00000000000..7baccd3ed69
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/reload.js b/Build/source/utils/asymptote/base/reload.js
new file mode 100644
index 00000000000..a6526803056
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/roundedpath.asy b/Build/source/utils/asymptote/base/roundedpath.asy
new file mode 100644
index 00000000000..1a426f91dcb
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/simplex.asy b/Build/source/utils/asymptote/base/simplex.asy
new file mode 100644
index 00000000000..bd9f69c9324
--- /dev/null
+++ b/Build/source/utils/asymptote/base/simplex.asy
@@ -0,0 +1,272 @@
+/*****
+ * simplex.asy
+ * Andy Hammerlindl 2004/07/27
+ *
+ * Solves 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.
+ * Correct execution of the algorithm also assumes that the coefficient of "b"
+ * will be +1 or -1 in every added restriction, and that the problem can be
+ * initialized to a valid state by pivoting b with one of the slack
+ * variables. This assumption may in fact be incorrect.
+ *****/
+
+private real infinity=sqrt(0.25*realMax);
+
+struct problem {
+ typedef int var;
+ static var VAR_A = 0;
+ static var VAR_B = 1;
+
+ static int OPTIMAL = -1;
+ static var UNBOUNDED = -2;
+ static int INVALID = -3;
+
+ struct row {
+ real c, t[];
+ }
+
+ // The variables of the rows.
+ // Initialized for the two variable problem.
+ var[] v = {VAR_A, VAR_B};
+
+ // The rows of equalities.
+ row rowA() {
+ row r = new row;
+ r.c = 0;
+ r.t = new real[] {1, 0};
+ return r;
+ }
+ row rowB() {
+ row r = new row;
+ r.c = 0;
+ r.t = new real[] {0, 1};
+ return r;
+ }
+ row[] rows = {rowA(), rowB()};
+
+ // The number of original variables.
+ int n = rows.length;
+
+ // Pivot the variable v[col] with vp.
+ void pivot(int col, var vp)
+ {
+ var vc=v[col];
+
+ // Recalculate rows v[col] and vp for the pivot-swap.
+ row rvc = rows[vc], rvp = rows[vp];
+ real factor=1/rvp.t[col]; // NOTE: Handle rvp.t[col] == 0 case.
+ rvc.c=-rvp.c*factor;
+ rvp.c=0;
+ rvc.t=-rvp.t*factor;
+ rvp.t *= 0;
+ rvc.t[col]=factor;
+ rvp.t[col]=1;
+
+ var a=min(vc,vp);
+ var b=max(vc,vp);
+
+ // Recalculate the rows other than the two used for the above pivot.
+ for (var i = 0; i < a; ++i) {
+ row r=rows[i];
+ real m = r.t[col];
+ r.c += m*rvc.c;
+ r.t += m*rvc.t;
+ r.t[col]=m*factor;
+ }
+ for (var i = a+1; i < b; ++i) {
+ row r=rows[i];
+ real m = r.t[col];
+ r.c += m*rvc.c;
+ r.t += m*rvc.t;
+ r.t[col]=m*factor;
+ }
+ for (var i = b+1; i < rows.length; ++i) {
+ row r=rows[i];
+ real m = r.t[col];
+ r.c += m*rvc.c;
+ r.t += m*rvc.t;
+ r.t[col]=m*factor;
+ }
+
+ // Relabel the vars.
+ v[col] = vp;
+ }
+
+ // As b does not have a non-negativity condition, it must initially be
+ // pivoted out for a variable that does. This selects the initial
+ // variable to pivot with b. It also assumes that there is a valid
+ // solution with a == 0 to the linear programming problem, and if so, it
+ // picks a pivot to get to that state. In our case, a == 0 corresponds to
+ // a picture with the user coordinates shrunk down to zero, and if that
+ // doesn't fit, nothing will.
+ var initVar()
+ {
+ real min=infinity, max=-infinity;
+ var argmin=0, argmax=0;
+
+ for (var i = 2; i < rows.length; ++i) {
+ row r=rows[i];
+ if (r.t[VAR_B] > 0) {
+ real val=r.c/r.t[VAR_B];
+ if (val < min) {
+ min=val;
+ argmin=i;
+ }
+ } else if (r.t[VAR_B] < 0) {
+ real val=r.c/r.t[VAR_B];
+ if (val > max) {
+ max=val;
+ argmax=i;
+ }
+ }
+ }
+
+ // If b has a minimal value, choose a pivot that will give b its minimal
+ // value. Otherwise, if b has maximal value, choose a pivot to give b its
+ // maximal value.
+ return argmin != 0 ? argmin :
+ argmax != 0 ? argmax :
+ UNBOUNDED;
+ }
+
+ // Initialize the linear program problem by moving into an acceptable state
+ // this assumes that b is unrestrained and is the second variable.
+ // NOTE: Works in limited cases, may be bug-ridden.
+ void init()
+ {
+ // Find the lowest constant term in the equations.
+ var lowest = 0;
+ for (var i = 2; i < rows.length; ++i) {
+ if (rows[i].c < rows[lowest].c)
+ lowest = i;
+ }
+
+ // Pivot if necessary.
+ if (lowest != 0)
+ pivot(VAR_B, lowest);
+ }
+
+ // Selects a column to pivot on. Returns OPTIMAL if the current state is
+ // optimal. Assumes we are optimizing the first row.
+ int selectColumn()
+ {
+ int i=find(rows[0].t > 0,1);
+ return (i >= 0) ? i : OPTIMAL;
+ }
+
+ // Select the new variable associated with a pivot on the column given.
+ // Returns UNBOUNDED if the space is unbounded.
+ var selectVar(int col)
+ {
+ // We assume that the first two vars (a and b) once swapped out, won't be
+ // swapped back in. This finds the variable which gives the tightest
+ // non-negativity condition restricting our optimization. This turns
+ // out to be the max of c/t[col]. Note that as c is positive, and
+ // t[col] is negative, all c/t[col] will be negative, so we are finding
+ // the smallest in magnitude.
+ var vp=UNBOUNDED;
+ real max=-infinity;
+ for (int i = 2; i < rows.length; ++i) {
+ row r=rows[i];
+ if(r.c < max*r.t[col]) {
+ max=r.c/r.t[col]; vp=i;
+ }
+ }
+
+ return vp;
+ }
+
+ // Checks that the rows are in a valid state.
+ bool valid()
+ {
+ // Checks that constants are valid.
+ bool validConstants() {
+ for (int i = 0; i < rows.length; ++i)
+ if (rows[i].c < 0)
+ return false;
+ return true;
+ }
+
+ // Check a variable to see if its row is simple.
+ // NOTE: Simple rows could be optimized out, since they are not really
+ // used.
+ bool validVar(int col) {
+
+ var vc = v[col];
+ row rvc = rows[vc];
+
+ if (rvc.c != 0)
+ return false;
+ for (int i = 0; i < n; ++i)
+ if (rvc.t[i] != (i == col ? 1 : 0))
+ return false;
+
+ return true;
+ }
+
+ if (!validConstants()) {
+ return false;
+ }
+ for (int i = 0; i < n; ++i)
+ if (!validVar(i)) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ // Perform the algorithm to find the optimal solution. Returns OPTIMAL,
+ // UNBOUNDED, or INVALID (if no solution is possible).
+ int optimize()
+ {
+ // Put into a valid state to begin and pivot b out.
+ var iv=initVar();
+ if (iv == UNBOUNDED)
+ return iv;
+ pivot(VAR_B, iv);
+
+ if (!valid())
+ return INVALID;
+
+ while(true) {
+ int col = selectColumn();
+
+ if (col == OPTIMAL)
+ return col;
+ var vp = selectVar(col);
+
+ if (vp == UNBOUNDED)
+ return vp;
+
+ pivot(col, vp);
+ }
+
+ // Shouldn't reach here.
+ return INVALID;
+ }
+
+ // Add a restriction to the problem:
+ // t1*a + t2*b + c >= 0
+ void addRestriction(real t1, real t2, real c)
+ {
+ row r = new row;
+ r.c = c;
+ r.t = new real[] {t1, t2};
+ rows.push(r);
+ }
+
+ // Return the value of a computed.
+ real a()
+ {
+ return rows[VAR_A].c;
+ }
+
+ // Return the value of b computed.
+ real b()
+ {
+ return rows[VAR_B].c;
+ }
+}
diff --git a/Build/source/utils/asymptote/base/size10.asy b/Build/source/utils/asymptote/base/size10.asy
new file mode 100644
index 00000000000..3bbd31227a9
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/size11.asy b/Build/source/utils/asymptote/base/size11.asy
new file mode 100644
index 00000000000..93712288152
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/slide.asy b/Build/source/utils/asymptote/base/slide.asy
new file mode 100644
index 00000000000..5b3596d02e6
--- /dev/null
+++ b/Build/source/utils/asymptote/base/slide.asy
@@ -0,0 +1,574 @@
+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;
+ real temp=settings.paperwidth;
+ settings.paperwidth=settings.paperheight;
+ settings.paperheight=temp;
+ pagewidth += settings.paperwidth;
+ pageheight += settings.paperheight;
+} 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;
+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");
+
+// 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 step()
+{
+ if(!stepping || !allowstepping) return;
+ if(!checkposition()) return;
+ lastnode.push(currentpicture.nodes.length-1);
+ 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]);
+ }
+ }
+ firstnode.push(currentpicture.nodes.length-1);
+ tex(bulletcolor(newbulletcolor));
+}
+
+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)
+ write("warning: slide too wide on page "+(string) page+':\n'+(string) s);
+
+ if(abs(offset.y+M.y) > 1) {
+ void toohigh() {
+ write("warning: 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 equation(string s, pen p=itempen)
+{
+ remark(center=true,"\vbox{$$"+s+"$$}",p,minipage=false,skip=0);
+}
+
+void vbox(string s, pen p=itempen)
+{
+ remark(center=true,"\vbox{"+s+"}",p,minipage=false,skip=0);
+}
+
+void equations(string s, pen p=itempen)
+{
+ vbox("\begin{eqnarray*}"+s+"\end{eqnarray*}",p);
+}
+
+void skip(real n=1)
+{
+ incrementposition((0,(tinv*(-n*itemskip*I*lineskip(itempen)*pt)).y));
+}
+
+void display(frame[] f, real margin=0, pair align=S, pen p=itempen,
+ pen figuremattpen=figuremattpen)
+{
+ 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)
+ write("warning: 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);
+ 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)
+{
+ display(new frame[] {f},margin,align,p,figuremattpen);
+}
+
+void display(string[] s, real margin=0, string[] captions=new string[],
+ string caption="", pair align=S, pen p=itempen,
+ pen figuremattpen=figuremattpen)
+{
+ 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);
+ if(caption != "") center(caption,p);
+}
+
+void display(string s, string caption="", pair align=S, pen p=itempen,
+ pen figuremattpen=figuremattpen)
+{
+ display(new string[] {s},caption,align,p,figuremattpen);
+}
+
+void figure(string[] s, string options="", real margin=0,
+ string[] captions=new string[], string caption="",
+ pair align=S, pen p=itempen, pen figuremattpen=figuremattpen)
+{
+ string[] S;
+ for(int i=0; i < s.length; ++i) {
+ S[i]=graphic(s[i],options);
+ }
+
+ display(S,margin,captions,caption,align,itempen,figuremattpen);
+}
+
+void figure(string s, string options="", string caption="", pair align=S,
+ pen p=itempen, pen figuremattpen=figuremattpen)
+{
+ figure(new string[] {s},options,caption,align,p,figuremattpen);
+}
+
+string[] codefile;
+
+void asyinclude(string s, real xsize=0, real ysize=xsize)
+{
+ picture currentpictureSave=currentpicture;
+ currentpicture=new picture;
+ _eval("include \""+s+"\";",true);
+ s=stripdirectory(settings.outname+"_"+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=settings.outname+"_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$\,$"+s,p,
+ -bulletwidth*pt,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;
+ delete(outprefix()+"_.aux");
+ 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/Build/source/utils/asymptote/base/slopefield.asy b/Build/source/utils/asymptote/base/slopefield.asy
new file mode 100644
index 00000000000..bdbb0252670
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/solids.asy b/Build/source/utils/asymptote/base/solids.asy
new file mode 100644
index 00000000000..0b2ffa2a508
--- /dev/null
+++ b/Build/source/utils/asymptote/base/solids.asy
@@ -0,0 +1,395 @@
+import three;
+import graph3;
+
+pen defaultbackpen=linetype("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=currentprojection)
+{
+ 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);
+ }
+
+ // Return the surface of rotation obtain by rotating the path3 (x,0,f(x))
+ // sampled n times between x=a and x=b about an axis lying in the XZ plane.
+ void operator init(triple c=O, real f(real x), real a, real b, int n=ngraph,
+ interpolate3 join=operator --, triple axis=Z,
+ real angle1=0, real angle2=360) {
+ operator init(c,graph(new triple(real x) {return (x,0,f(x));},a,b,n,
+ join),axis,angle1,angle2);
+ }
+
+ 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;
+ // must be recomputed if camera is adjusted
+ void transverse(skeleton s, real t, int n=nslice,
+ projection P=currentprojection) {
+ 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=currentprojection) {
+ 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())
+ write("warning: 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;
+ // must be recomputed if camera is adjusted
+ void longitudinal(skeleton s, int n=nslice, projection P=currentprojection) {
+ 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 &&
+ 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=currentprojection) {
+ skeleton s;
+ transverse(s,m,n,P);
+ longitudinal(s,n,P);
+ return s;
+ }
+}
+
+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, projection P=currentprojection)
+{
+ pen thin=is3D() ? thin() : defaultpen;
+ skeleton s=r.skeleton(m,n,P);
+ begingroup3(pic);
+ if(frontpen != nullpen) {
+ draw(pic,s.transverse.back,thin+defaultbackpen+backpen,light);
+ draw(pic,s.transverse.front,thin+frontpen,light);
+ }
+ if(longitudinalpen != nullpen) {
+ draw(pic,s.longitudinal.back,thin+defaultbackpen+longitudinalbackpen,light);
+ draw(pic,s.longitudinal.front,thin+longitudinalpen,light);
+ }
+ endgroup3(pic);
+}
+
+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);
+}
+
+// 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,0,0,0,Y,n),Z);
+}
diff --git a/Build/source/utils/asymptote/base/splinetype.asy b/Build/source/utils/asymptote/base/splinetype.asy
new file mode 100644
index 00000000000..366f91ac370
--- /dev/null
+++ b/Build/source/utils/asymptote/base/splinetype.asy
@@ -0,0 +1,238 @@
+typedef real[] splinetype(real[], real[]);
+
+restricted real[] defaultspline(real[] x, real[] y);
+restricted real[] Spline(real[] x, real[] y);
+
+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));
+}
+
+// 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);
+ 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);
+ if(y[n-1] != y[0]) 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);
+ 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);
+ 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);
+ 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/Build/source/utils/asymptote/base/stats.asy b/Build/source/utils/asymptote/base/stats.asy
new file mode 100644
index 00000000000..be9efdfa429
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/syzygy.asy b/Build/source/utils/asymptote/base/syzygy.asy
new file mode 100644
index 00000000000..dce87805c9d
--- /dev/null
+++ b/Build/source/utils/asymptote/base/syzygy.asy
@@ -0,0 +1,936 @@
+/***** 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
+frame[] fit(picture[] pics) {
+ frame[] f;
+ for (int i=0; i<pics.length; ++i) {
+ frame ff=pics[i].fit();
+ //label(ff, (string)i, (10,10));
+ //f.push(pics[i].fit());
+ f.push(ff);
+ }
+ return f;
+}
+
+// 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/Build/source/utils/asymptote/base/texcolors.asy b/Build/source/utils/asymptote/base/texcolors.asy
new file mode 100644
index 00000000000..90d9606cb70
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/three.asy b/Build/source/utils/asymptote/base/three.asy
new file mode 100644
index 00000000000..2ba6b73abe1
--- /dev/null
+++ b/Build/source/utils/asymptote/base/three.asy
@@ -0,0 +1,2614 @@
+private import math;
+
+if(inXasyMode) settings.render=0;
+
+if(prc0()) {
+ access embed;
+ Embed=embed.embed;
+ Link=embed.link;
+}
+
+real defaultshininess=0.25;
+real defaultgranularity=0;
+real linegranularity=0.01;
+real tubegranularity=0.003;
+real dotgranularity=0.0001;
+pair viewportmargin=0; // Horizontal and vertical viewport margins.
+real viewportfactor=1.02; // Factor used to expand orthographic viewport.
+real anglefactor=1.02; // Factor used to expand perspective viewport.
+real angleprecision=1e-3; // Precision for centering perspective projections.
+
+string defaultembed3Doptions;
+string defaultembed3Dscript;
+
+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);
+}
+
+bool operator != (real[][] a, real[][] b) {
+ return !(a == b);
+}
+
+// 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);
+ return (abs(u) > sqrtEpsilon) ? unit(u) : unit(cross(v,Z));
+}
+
+// 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;
+ P.target=t*P.target;
+ 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,
+ bool showtarget=true, bool autoadjust=true,
+ bool center=false)
+{
+ if(camera == target)
+ abort("camera cannot be at target");
+ return projection(camera,up,target,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,
+ bool showtarget=true, bool autoadjust=true,
+ bool center=false)
+{
+ return perspective((x,y,z),up,target,showtarget,autoadjust,center);
+}
+
+projection orthographic(triple camera, triple up=Z, triple target=O,
+ bool showtarget=true, bool autoadjust=true,
+ bool center=false)
+{
+ return projection(camera,up,target,showtarget,autoadjust,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, bool showtarget=true,
+ bool autoadjust=true, bool center=false)
+{
+ return orthographic((x,y,z),up,target,showtarget,autoadjust,center);
+}
+
+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;
+ return projection((c2,s2,1),up=Y,
+ new transformation(triple,triple,triple) {
+ return transformation(t,oblique=true);});
+}
+
+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;
+ return projection((1,c2,s2),
+ new transformation(triple,triple,triple) {
+ return transformation(t,oblique=true);});
+}
+
+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;
+ return projection((c2,-1,s2),
+ new transformation(triple,triple,triple) {
+ return transformation(t,oblique=true);});
+}
+
+projection oblique=oblique();
+projection obliqueX=obliqueX(), obliqueY=obliqueY(), obliqueZ=obliqueZ();
+
+currentprojection=perspective(5,4,2);
+
+// 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.vector(),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.vector(),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);
+ };
+}
+
+guide3 operator ::(... guide3[] a)
+{
+ if(a.length == 0) return nullpath3;
+ guide3 g=a[0];
+ for(int i=1; i < a.length; ++i)
+ g=g.. tension atleast 1 ..a[i];
+ return g;
+}
+
+guide3 operator ---(... guide3[] a)
+{
+ if(a.length == 0) return nullpath3;
+ guide3 g=a[0];
+ for(int i=1; i < a.length; ++i)
+ g=g.. tension atleast infinity ..a[i];
+ return g;
+}
+
+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 th 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.cyclicflag;
+ 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;}
+
+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);
+}
+
+// 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.vector());
+ 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);
+ 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;
+ }
+
+ 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.vector());
+ 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;
+ static real[] O={0,0,0,1};
+ real[] tO=t*O;
+ 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)
+{
+ triple v=unit(P.oblique ? P.camera : P.vector());
+ triple u=unit(perp(P.up,v));
+ if(u == O) u=cross(perp(v),v);
+ return transform3(cross(u,v),u);
+}
+
+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 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;
+}
+
+typedef guide3 interpolate3(... guide3[]);
+
+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=scale3(r)*unitcircle3;
+ if(normal != Z)
+ p=align(unit(normal))*p;
+ return shift(c)*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=align(unit(normal));
+ transform3 Tinv=transpose(T);
+ v1=Tinv*v1;
+ v2=Tinv*v2;
+
+ string invalidnormal="invalid normal vector";
+ real fuzz=sqrtEpsilon*max(abs(v1),abs(v2));
+ 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(t1 >= t2 && direction) t1 -= n;
+ if(t2 >= t1 && !direction) t2 -= n;
+
+ return shift(c)*scale3(r)*T*subpath(unitcircle3,t1,t2);
+}
+
+// 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;
+}
+
+triple size3(frame f)
+{
+ return max3(f)-min3(f);
+}
+
+// PRC/OpenGL support
+
+include three_light;
+
+void draw(frame f, path3 g, material p=currentpen, light light=nolight,
+ projection P=currentprojection);
+
+void begingroup3(picture pic=currentpicture)
+{
+ pic.add(new void(frame f, transform3, picture opic, projection) {
+ if(opic != null)
+ begingroup(opic);
+ },true);
+}
+
+void endgroup3(picture pic=currentpicture)
+{
+ pic.add(new void(frame f, transform3, picture opic, projection) {
+ if(opic != null)
+ endgroup(opic);
+ },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;
+
+void draw(picture pic=currentpicture, Label L="", path3 g,
+ align align=NoAlign, material p=currentpen, margin3 margin=NoMargin3,
+ light light=nolight)
+{
+ 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,null);
+ if(pic != null && size(G) > 0) {
+ pic.addPoint(min(G,P.t));
+ pic.addPoint(max(G,P.t));
+ }
+ }
+ 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_arrows;
+
+draw=new void(frame f, path3 g, material p=currentpen,
+ light light=nolight, projection P=currentprojection) {
+ pen q=(pen) p;
+ if(is3D()) {
+ p=material(p,(p.granularity >= 0) ? p.granularity : linegranularity);
+ void drawthick(path3 g) {
+ if(settings.thick) {
+ real width=linewidth(q);
+ if(width > 0) {
+ surface s=tube(g,width);
+ int L=length(g);
+ if(L >= 0) {
+ if(!cyclic(g)) {
+ real r=0.5*width;
+ real linecap=linecap(q);
+ transform3 scale3r=scale3(r);
+ surface cap;
+ triple dirL=dir(g,L);
+ triple dir0=dir(g,0);
+ if(linecap == 0)
+ cap=scale(r,r,1)*unitdisk;
+ else if(linecap == 1)
+ cap=scale3r*((dir0 == O || dirL == O) ?
+ unitsphere : unithemisphere);
+ else if(linecap == 2) {
+ cap=scale3r*unitcylinder;
+ cap.append(scale3r*shift(Z)*unitdisk);
+ }
+ s.append(shift(point(g,0))*align(-dir0)*cap);
+ s.append(shift(point(g,L))*align(dirL)*cap);
+ }
+ if(opacity(q) == 1) _draw(f,g,q);
+ }
+ for(int i=0; i < s.s.length; ++i)
+ draw3D(f,s.s[i],p,light);
+ } else _draw(f,g,q);
+ } else _draw(f,g,q);
+ }
+ string type=linetype(adjust(q,arclength(g),cyclic(g)));
+ if(length(type) == 0) drawthick(g);
+ else {
+ real[] dash=(real[]) split(type," ");
+ 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;
+ }
+ }
+ }
+ } else draw(f,project(g,P),q);
+};
+
+void draw(frame f, explicit path3[] g, material p=currentpen,
+ light light=nolight, projection P=currentprojection)
+{
+ for(int i=0; i < g.length; ++i) draw(f,g[i],p,light,P);
+}
+
+void draw(picture pic=currentpicture, explicit path3[] g,
+ material p=currentpen, margin3 margin=NoMargin3, light light=nolight)
+{
+ for(int i=0; i < g.length; ++i) draw(pic,g[i],p,margin,light);
+}
+
+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)
+{
+ begingroup3(pic);
+ 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);
+ endgroup3(pic);
+ label(pic,L,g,align,(pen) p);
+}
+
+void draw(frame f, path3 g, material p=currentpen, arrowbar3 arrow,
+ light light=nolight, light arrowheadlight=currentlight,
+ projection P=currentprojection)
+{
+ picture pic;
+ if(arrow(pic,g,p,NoMargin3,light,arrowheadlight))
+ draw(f,g,p,light,P);
+ add(f,pic.fit());
+}
+
+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)
+{
+ 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);
+ 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,
+ projection P=currentprojection)
+{
+ arrow(pic,L,b,invert(dir,b,P),length,align,p,arrow,margin,light,
+ arrowheadlight);
+}
+
+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 v=pic.userMin+realmult(rectify(dir),pic.userMax-pic.userMin);
+ 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);
+}
+
+string cameralink(string label, string text="View Parameters")
+{
+ if(!prc() || Link == null) return "";
+ return Link(label,text,"3Dgetview");
+}
+
+private struct viewpoint {
+ triple target,camera,up;
+ real angle;
+ void operator init(string s) {
+ s=replace(s,new string[][] {{" ",","},{"}{",","},{"{",""},{"}",""},});
+ string[] S=split(s,",");
+ target=((real) S[0],(real) S[1],(real) S[2])*cm;
+ camera=target+(real) S[6]*((real) S[3],(real) S[4],(real) S[5])*cm;
+ triple u=unit(target-camera);
+ triple w=unit(Z-u.z*u);
+ up=rotate((real) S[7],O,u)*w;
+ angle=S[8] == "" ? 30 : (real) S[8];
+ }
+}
+
+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;
+}
+
+private string format(real x)
+{
+ // Work around movie15.sty division by zero bug;
+ // e.g. u=unit((1e-10,1e-10,0.9));
+ if(abs(x) < 1e-9) x=0;
+ assert(abs(x) < 1e18,"Number too large: "+string(x));
+ return format("%.18f",x,"C");
+}
+
+private string format(triple v, string sep=" ")
+{
+ return format(v.x)+sep+format(v.y)+sep+format(v.z);
+}
+
+private string format(pen p)
+{
+ real[] c=colors(rgb(p));
+ return format((c[0],c[1],c[2]));
+}
+
+private string[] file3;
+
+private string projection(bool infinity, real viewplanesize)
+{
+ return "activeCamera=scene.cameras.getByIndex(0);
+function asyProjection() {"+
+ (infinity ? "activeCamera.projectionType=activeCamera.TYPE_ORTHOGRAPHIC;" :
+ "activeCamera.projectionType=activeCamera.TYPE_PERSPECTIVE;")+"
+activeCamera.viewPlaneSize="+string(viewplanesize)+";
+activeCamera.binding=activeCamera.BINDING_VERTICAL;
+}
+
+asyProjection();
+
+handler=new CameraEventHandler();
+runtime.addEventHandler(handler);
+handler.onEvent=function(event)
+{
+ asyProjection();
+ scene.update();
+}";
+}
+
+string lightscript(light light) {
+ string script="for(var i=scene.lights.count-1; i >= 0; i--)
+ scene.lights.removeByIndex(i);"+'\n\n';
+ for(int i=0; i < light.position.length; ++i) {
+ string Li="L"+string(i);
+ real[] diffuse=light.diffuse[i];
+ script += Li+"=scene.createLight();"+'\n'+
+ Li+".direction.set("+format(-light.position[i],",")+");"+'\n'+
+ Li+".color.set("+format((diffuse[0],diffuse[1],diffuse[2]),",")+");"+'\n';
+ }
+ // Work around initialization bug in Adobe Reader 8.0:
+ return script +"
+scene.lightScheme=scene.LIGHT_MODE_HEADLAMP;
+scene.lightScheme=scene.LIGHT_MODE_FILE;
+";
+}
+
+void writeJavaScript(string name, string preamble, string script)
+{
+ file out=output(name);
+ write(out,preamble);
+ if(script != "") {
+ 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(projection P, real width, real height)
+{
+ pair viewportmargin=viewportmargin;
+ real xmargin=viewportmargin.x;
+ real ymargin=viewportmargin.y;
+ if(xmargin <= 0) xmargin=max(0.5*(viewportsize.x-width),0);
+ if(ymargin <= 0) ymargin=max(0.5*(viewportsize.y-height),0);
+ viewportmargin=(xmargin,ymargin);
+ if(P.infinity) return viewportmargin;
+ return (max(viewportmargin.x,viewportmargin.y),viewportmargin.y);
+}
+
+string embed3D(string label="", string text=label, string prefix,
+ frame f, string format="",
+ real width=0, real height=0, real angle=30,
+ string options="", string script="",
+ pen background=white, light light=currentlight,
+ projection P=currentprojection)
+{
+ if(!prc(format) || Embed == null) return "";
+
+ if(width == 0) width=settings.paperwidth;
+ if(height == 0) height=settings.paperheight;
+
+ if(script == "") script=defaultembed3Dscript;
+
+ // Adobe Reader doesn't appear to support user-specified viewport lights.
+ string lightscript=light.on() && !light.viewport ? lightscript(light) : "";
+
+ triple lambda=max3(f)-min3(f);
+ pair viewportmargin=viewportmargin(P,lambda.x,lambda.y);
+ real viewplanesize=(viewportfactor*lambda.y+2*viewportmargin.y)/cm;
+ string name=prefix+".js";
+ writeJavaScript(name,lightscript+projection(P.infinity,viewplanesize),script);
+
+ shipout3(prefix,f);
+
+ prefix += ".prc";
+ if(!settings.inlinetex)
+ file3.push(prefix);
+
+ triple v=P.vector()/cm;
+ triple u=unit(v);
+ triple w=Z-u.z*u;
+ real roll;
+ if(abs(w) > sqrtEpsilon) {
+ w=unit(w);
+ triple up=unit(perp(P.up,u));
+ roll=degrees(acos1(dot(up,w)))*sgn(dot(cross(up,w),u));
+ } else roll=0;
+
+ string options3=light.viewport ? "3Dlights=Headlamp" : "3Dlights=File";
+ if(defaultembed3Doptions != "") options3 += ","+defaultembed3Doptions;
+ if((settings.render < 0 || !settings.embed) && settings.auto3D)
+ options3 += ",poster";
+ options3 += ",text="+text+",label="+label+
+ ",toolbar="+(settings.toolbar ? "true" : "false")+
+ ",3Daac="+format(P.absolute ? P.angle : angle)+
+ ",3Dc2c="+format(u)+
+ ",3Dcoo="+format(P.target/cm)+
+ ",3Droll="+format(roll)+
+ ",3Droo="+format(abs(v))+
+ ",3Dbg="+format(background);
+ if(options != "") options3 += ","+options;
+ if(name != "") options3 += ",3Djscript="+stripdirectory(name);
+
+ return Embed(stripdirectory(prefix),options3,width+2*viewportmargin.x,
+ height+2*viewportmargin.y);
+}
+
+object embed(string label="", string text=label,
+ string prefix=defaultfilename,
+ frame f, string format="",
+ real width=0, real height=0, real angle=30,
+ string options="", string script="",
+ pen background=white, light light=currentlight,
+ projection P=currentprojection)
+{
+ object F;
+
+ if(is3D(format))
+ F.L=embed3D(label,text,prefix,f,format,width,height,angle,options,script,
+ background,light,P);
+ else
+ F.f=f;
+ return F;
+}
+
+object embed(string label="", string text=label,
+ string prefix=defaultfilename,
+ picture pic, string format="",
+ real xsize=pic.xsize, real ysize=pic.ysize,
+ bool keepAspect=pic.keepAspect, bool view=true, string options="",
+ string script="", real angle=0, pen background=white,
+ light light=currentlight, projection P=currentprojection)
+{
+ object F;
+ real xsize3=pic.xsize3, ysize3=pic.ysize3, zsize3=pic.zsize3;
+ bool warn=true;
+ transform3 modelview;
+
+ if(xsize3 == 0 && ysize3 == 0 && zsize3 == 0) {
+ xsize3=ysize3=zsize3=max(xsize,ysize);
+ warn=false;
+ }
+
+ projection P=P.copy();
+
+ if(!P.autoadjust && !P.absolute && P.showtarget)
+ draw(pic,P.target,nullpen);
+
+ transform3 t=pic.scaling(xsize3,ysize3,zsize3,keepAspect,warn);
+ bool adjusted=false;
+ transform3 tinv=inverse(t);
+ triple m=pic.min(t);
+ triple M=pic.max(t);
+
+ if(!P.absolute) {
+ P=t*P;
+ if(P.autoadjust || P.center) {
+ bool recalculate=false;
+ if(P.center || P.target.x < m.x ||
+ P.target.y < m.y ||
+ P.target.z < m.z ||
+ P.target.x > M.x ||
+ P.target.y > M.y ||
+ P.target.z > M.z) {
+ P.target=0.5*(m+M);
+ recalculate=true;
+ if(!P.center) write("adjusting target to ",tinv*P.target);
+ }
+ if(recalculate) P.calculate();
+ }
+ if(P.autoadjust || P.infinity)
+ adjusted=adjusted | P.adjust(m,M);
+ }
+
+ picture pic2;
+
+ frame f=pic.fit3(t,pic.bounds3.exact ? pic2 : null,P);
+
+ if(!pic.bounds3.exact) {
+ transform3 s=pic.scale3(f,xsize3,ysize3,zsize3,keepAspect);
+ t=s*t;
+ tinv=inverse(t);
+ P=s*P;
+ f=pic.fit3(t,pic2,P);
+ }
+
+ bool is3D=is3D(format);
+ bool scale=xsize != 0 || ysize != 0;
+
+ 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;
+ real width=lambda.x;
+ real height=lambda.y;
+
+ pair viewportmargin=viewportmargin(P,width,height);
+ projection Q;
+ if(!P.absolute) {
+ if(scale) {
+ pair v=(s.xx,s.yy);
+ transform3 T=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);
+ }
+ pic2.erase();
+ t=xscale3(f(v,x))*yscale3(f(v,y))*zscale3(f(v,z))*t;
+ f=pic.fit3(t,is3D ? null : pic2,P);
+ }
+
+ if(P.autoadjust || P.infinity)
+ adjusted=adjusted | P.adjust(min3(f),max3(f));
+
+ if(adjusted && !P.infinity)
+ write("adjusting camera to ",tinv*P.camera);
+
+ modelview=P.modelview();
+ f=modelview*f;
+ P=modelview*P;
+ Q=P.copy();
+ light=modelview*light;
+
+ if(P.infinity) {
+ triple m=min3(f);
+ triple M=max3(f);
+ triple s=(-0.5(m.x+M.x),-0.5*(m.y+M.y),0);
+ f=shift(s)*f; // Eye will be at (0,0,0).
+ } else {
+ // Choose the angle to be just large enough to view the entire image:
+ int maxiterations=100;
+ if(is3D && angle == 0 && !P.infinity) {
+ 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;
+ s=r+R;
+ transform3 t=shift(h*s.x,h*s.y,0);
+ f=t*f;
+ P=t*P;
+ diff=abs(s-lasts);
+ ++i;
+ } while (diff > angleprecision && i < maxiterations);
+
+ real aspect=width > 0 ? height/width : 1;
+ angle=anglefactor*max(aTan(-r.x*aspect)+aTan(R.x*aspect),
+ aTan(-r.y)+aTan(R.y));
+ if(viewportmargin.y != 0)
+ angle=2*aTan(Tan(0.5*angle)-viewportmargin.y/P.target.z);
+ }
+ }
+ }
+
+ if(prefix == "") prefix=outprefix();
+ bool prc=prc(format);
+ bool preview=settings.render > 0;
+ if(prc) {
+ // The movie15.sty package cannot handle spaces or dots in filenames.
+ prefix=replace(prefix,new string[][]{{" ","_"},{".","_"}});
+ if(settings.embed || nativeformat() == "pdf")
+ prefix += "+"+(string) file3.length;
+ } else
+ preview=false;
+ if(preview || (!prc && settings.render != 0)) {
+ frame f=f;
+ triple m,M;
+ real zcenter;
+ if(P.absolute) {
+ modelview=P.modelview();
+ f=modelview*f;
+ P=modelview*P;
+ angle=P.angle;
+ m=min3(f);
+ M=max3(f);
+ real r=0.5*abs(M-m);
+ zcenter=0.5*(M.z+m.z);
+ M=(M.x,M.y,zcenter+r);
+ m=(m.x,m.y,zcenter-r);
+ } else {
+ m=min3(f);
+ M=max3(f);
+ zcenter=P.target.z;
+ real d=P.distance(m,M);
+ M=(M.x,M.y,zcenter+d);
+ m=(m.x,m.y,zcenter-d);
+ }
+
+ real factor=viewportfactor-1.0;
+ triple margin=(factor*abs(M.x-m.x),factor*abs(M.y-m.y),0);
+ if(P.infinity)
+ margin += (0,viewportmargin.y,0);
+ M += margin;
+ m -= margin;
+ if(!P.infinity && M.z >= 0) abort("camera too close");
+
+ shipout3(prefix,f,preview ? nativeformat() : format,
+ width+2*viewportmargin.x,height+2*viewportmargin.y,
+ P.infinity ? 0 : angle,m,M,
+ tinv*inverse(modelview)*shift(0,0,zcenter),
+ P.absolute ? (modelview*light).position : light.position,
+ light.diffuse,light.ambient,light.specular,
+ light.viewport,view && !preview);
+ if(!preview) return F;
+ }
+
+ string image;
+ if(preview && settings.embed) {
+ image=prefix;
+ if(settings.inlinetex) image += "_0";
+ image += "."+nativeformat();
+ if(!settings.inlinetex) file3.push(image);
+ image=graphic(image);
+ }
+ if(prc) F.L=embed3D(label,text=image,prefix,f,format,
+ width,height,angle,options,script,background,light,
+ P.absolute ? P : Q);
+ }
+
+ if(!is3D) {
+ transform T=pic2.scaling(xsize,ysize,keepAspect);
+ F.f=pic.fit(scale(t[0][0])*T);
+ add(F.f,pic2.fit(T));
+ }
+
+ return F;
+}
+
+embed3=new object(string prefix, frame f, string format, string options,
+ string script, projection P) {
+ return embed(prefix=prefix,f,format,options,script,P);
+};
+
+currentpicture.fitter=new frame(string prefix, picture pic, string format,
+ real xsize, real ysize,
+ bool keepAspect, bool view,
+ string options, string script, projection P) {
+ frame f;
+ bool empty3=pic.empty3();
+ if(is3D(format) || empty3) add(f,pic.fit2(xsize,ysize,keepAspect));
+ if(!empty3) {
+ 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=embed(prefix=prefix,pic,format,xsize,ysize,keepAspect,view,
+ options,script,P);
+ if(prc)
+ label(f,F.L);
+ else {
+ if(settings.render == 0)
+ add(f,F.f);
+ else if(!view)
+ label(f,graphic(prefix));
+ }
+ }
+ return f;
+};
+
+// Force an array of 3D pictures to be as least as large as picture all.
+void rescale3(picture[] pictures, picture all, projection P=currentprojection)
+{
+ if(!all.empty3()) {
+ transform3 t=inverse(all.calculateTransform3(P)*pictures[0].T3);
+ triple m=t*min3(all);
+ triple M=t*max3(all);
+ for(int i=0; i < pictures.length; ++i) {
+ draw(pictures[i],m,nullpen);
+ draw(pictures[i],M,nullpen);
+ }
+ }
+}
+
+// Force an array of pictures to have a uniform scaling using currenprojection.
+rescale=new void(picture[] pictures) {
+ if(pictures.length == 0) return;
+ picture all;
+ size(all,pictures[0]);
+ for(picture pic : pictures)
+ add(all,pic);
+ rescale2(pictures,all);
+ rescale3(pictures,all);
+};
+
+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/Build/source/utils/asymptote/base/three_arrows.asy b/Build/source/utils/asymptote/base/three_arrows.asy
new file mode 100644
index 00000000000..78ee56d9f04
--- /dev/null
+++ b/Build/source/utils/asymptote/base/three_arrows.asy
@@ -0,0 +1,789 @@
+// 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);
+ }
+ }
+ }
+}
+
+void render(path3 s, void f(path3, real))
+{
+ static int maxdepth=ceil(-log(realEpsilon)/log(2))+1;
+ 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);
+ real R=infinity;
+ int nintervals=3;
+ real fuzz=sqrtEpsilon*max(abs(z0),abs(z1));
+ if(S > 0) {
+ --depth;
+ for(int i=0; i <= nintervals; ++i) {
+ R=min(R,radius(z0,c0,c1,z1,i/nintervals));
+ if(S > max(tubegranularity*R,fuzz)) {
+ 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));
+}
+
+surface tube(path3 g, real width)
+{
+ surface tube;
+ real r=0.5*width;
+
+ transform3 t=scale3(r);
+
+ int n=length(g);
+ for(int i=0; i < n; ++i) {
+ real S=straightness(g,i);
+ if(S < sqrtEpsilon*r) {
+ triple v=point(g,i);
+ triple u=point(g,i+1)-v;
+ tube.append(shift(v)*align(unit(u))*scale(r,r,abs(u))*unitcylinder);
+ } else {
+ render(subpath(g,i,i+1),new void(path3 q, real) {
+ real L=arclength(q);
+ surface segment=scale(r,r,L)*unitcylinder;
+ bend(segment,q,L);
+ tube.s.append(segment.s);
+ });
+ }
+
+ if((cyclic(g) || i > 0) && abs(dir(g,i,1)-dir(g,i,-1)) > sqrtEpsilon)
+ tube.append(shift(point(g,i))*t*align(dir(g,i,-1))*unithemisphere);
+ }
+ return tube;
+}
+
+// Refine a noncyclic path3 g so that it approaches its endpoint in
+// geometrically spaced steps.
+path3 approach(path3 g, int n, real radix=3)
+{
+ guide3 G;
+ real L=length(g);
+ real tlast=0;
+ real r=1/radix;
+ for(int i=1; i < n; ++i) {
+ real t=L*(1-r^i);
+ G=G&subpath(g,tlast,t);
+ tlast=t;
+ }
+ return G&subpath(g,tlast,L);
+}
+
+struct arrowhead3
+{
+ arrowhead arrowhead2=DefaultHead;
+ real size(pen p)=arrowsize;
+ real arcsize(pen p)=arcarrowsize;
+ real gap=1;
+ 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.vector() : 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));
+ 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 rotate(-degrees(point(H,t[0][0])-point(H,t[1][0]),warn=false))*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) {
+ 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]=(k*p.P[i][j].x,k*p.P[i][j].y,p.P[i][j].z);
+ p.P[i][j]=bend(p.P[i][j],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);
+
+ angle=min(angle*arrowhookfactor,45);
+ 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;
+ }
+};
+
+real[] arrowbasepoints(path3 base, path3 left, path3 right)
+{
+ real[][] Tl=transpose(intersections(left,base));
+ real[][] Tr=transpose(intersections(right,base));
+ return new real[] {Tl.length > 0 ? Tl[0][0] : 1,
+ Tr.length > 0 ? Tr[0][0] : 1};
+}
+
+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);
+ 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.01;
+ 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);
+ angle=min(angle*arrowhookfactor,45);
+ path h=a.project(g,forwards,P);
+ 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.01;
+ 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);
+ 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.gap=0.83;
+ a.splitpath=false;
+ 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);
+ 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.vector(),perp)) : d;
+ draw(opic,A-v--A+v,p,light);
+ add(f,opic.fit3(identity4,pic2,P));
+ });
+ triple v=d == O ? cross(currentprojection.vector(),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/Build/source/utils/asymptote/base/three_light.asy b/Build/source/utils/asymptote/base/three_light.asy
new file mode 100644
index 00000000000..388c344895a
--- /dev/null
+++ b/Build/source/utils/asymptote/base/three_light.asy
@@ -0,0 +1,167 @@
+struct material {
+ pen[] p; // diffusepen,ambientpen,emissivepen,specularpen
+ real opacity;
+ real shininess;
+ real granularity;
+ void operator init(pen diffusepen=black, pen ambientpen=black,
+ pen emissivepen=black, pen specularpen=mediumgray,
+ real opacity=opacity(diffusepen),
+ real shininess=defaultshininess,
+ real granularity=-1) {
+ p=new pen[] {diffusepen,ambientpen,emissivepen,specularpen};
+ this.opacity=opacity;
+ this.shininess=shininess;
+ this.granularity=granularity;
+ }
+ void operator init(material m, real granularity=m.granularity) {
+ p=copy(m.p);
+ opacity=m.opacity;
+ shininess=m.shininess;
+ this.granularity=granularity;
+ }
+ pen diffuse() {return p[0];}
+ pen ambient() {return p[1];}
+ pen emissive() {return p[2];}
+ pen specular() {return p[3];}
+ void diffuse(pen q) {p[0]=q;}
+ void ambient(pen q) {p[1]=q;}
+ void emissive(pen q) {p[2]=q;}
+ void specular(pen q) {p[3]=q;}
+}
+
+void write(file file, string s="", material x, suffix suffix=none)
+{
+ write(file,s);
+ write(file,"{");
+ write(file,"diffuse=",x.diffuse());
+ write(file,", ambient=",x.ambient());
+ write(file,", emissive=",x.emissive());
+ write(file,", specular=",x.specular());
+ write(file,", opacity=",x.opacity);
+ write(file,", shininess=",x.shininess);
+ write(file,", granularity=",x.granularity);
+ 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.granularity == n.granularity;
+}
+
+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, real granularity=m.granularity)
+{
+ return material(black+opacity(m.opacity),black,m.diffuse(),black,m.opacity,1,
+ granularity);
+}
+
+struct light {
+ real[][] diffuse;
+ real[][] ambient;
+ real[][] specular;
+ real specularfactor;
+ bool viewport; // Are the lights specified (and fixed) in the viewport frame?
+ triple[] position; // Only directional lights are implemented.
+
+ transform3 T=identity(4); // Transform to apply to normal vectors.
+
+ bool on() {return position.length > 0;}
+
+ void operator init(pen[] diffuse=array(position.length,white),
+ pen[] ambient=array(position.length,black),
+ pen[] specular=diffuse, real specularfactor=1,
+ bool viewport=true, triple[] position) {
+ this.position=new triple[position.length];
+ this.diffuse=new real[position.length][];
+ this.ambient=new real[position.length][];
+ this.specular=new real[position.length][];
+ for(int i=0; i < position.length; ++i) {
+ this.position[i]=unit(position[i]);
+ this.diffuse[i]=rgba(diffuse[i]);
+ this.ambient[i]=rgba(ambient[i]);
+ this.specular[i]=rgba(specular[i]);
+ }
+ this.specularfactor=specularfactor;
+ this.viewport=viewport;
+ }
+
+ void operator init(pen diffuse=white, pen ambient=black, pen specular=diffuse,
+ real specularfactor=1,
+ bool viewport=true...triple[] position) {
+ int n=position.length;
+ operator init(array(n,diffuse),array(n,ambient),array(n,specular),
+ specularfactor,viewport,position);
+ }
+
+ void operator init(pen diffuse=white, pen ambient=black, pen specular=diffuse,
+ bool viewport=true, real x, real y, real z) {
+ operator init(diffuse,ambient,specular,viewport,(x,y,z));
+ }
+
+ void operator init(explicit light light) {
+ diffuse=copy(light.diffuse);
+ ambient=copy(light.ambient);
+ specular=copy(light.specular);
+ specularfactor=light.specularfactor;
+ viewport=light.viewport;
+ position=copy(light.position);
+ }
+
+ pen color(triple normal, material m, transform3 T=T) {
+ if(invisible((pen) m)) return invisible;
+ if(position.length == 0) return m.diffuse();
+ normal=unit(T*normal);
+ if(settings.twosided) normal *= sgn(normal.z);
+ real s=m.shininess*128;
+ real[] Diffuse=rgba(m.diffuse());
+ real[] Ambient=rgba(m.ambient());
+ real[] Specular=rgba(m.specular());
+ real[] p=rgba(m.emissive());
+ for(int i=0; i < position.length; ++i) {
+ triple L=viewport ? position[i] : T*position[i];
+ real Ldotn=max(dot(normal,L),0);
+ p += ambient[i]*Ambient+Ldotn*diffuse[i]*Diffuse;
+// Apply specularfactor to partially compensate non-pixel-based rendering.
+ if(Ldotn > 0) // Phong-Blinn model of specular reflection
+ p += dot(normal,unit(L+Z))^s*specularfactor*specular[i]*Specular;
+ }
+ return rgb(p[0],p[1],p[2])+opacity(opacity(m.diffuse()));
+ }
+}
+
+light operator * (transform3 t, light light)
+{
+ light light=light(light);
+ if(!light.viewport) light.position=shiftless(t)*light.position;
+ return light;
+}
+
+light operator cast(triple v) {return light(v);}
+
+light currentlight=light(ambient=rgb(0.1,0.1,0.1),specularfactor=3,
+ (0.25,-0.25,1));
+
+light adobe=light(gray(0.4),specularfactor=3,viewport=false,
+ (0.5,-0.5,-0.25),(0.5,0.5,0.25),
+ (0.5,-0.5,0.2),(-0.5,0.5,-0.2));
+light nolight;
diff --git a/Build/source/utils/asymptote/base/three_margins.asy b/Build/source/utils/asymptote/base/three_margins.asy
new file mode 100644
index 00000000000..b6f286ee4ff
--- /dev/null
+++ b/Build/source/utils/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/Build/source/utils/asymptote/base/three_surface.asy b/Build/source/utils/asymptote/base/three_surface.asy
new file mode 100644
index 00000000000..6e540c416ff
--- /dev/null
+++ b/Build/source/utils/asymptote/base/three_surface.asy
@@ -0,0 +1,1343 @@
+import bezulate;
+
+int nslice=12;
+real camerafactor=1.2;
+
+private real Fuzz=10.0*realEpsilon;
+private real nineth=1/9;
+
+struct patch {
+ triple[][] P=new triple[4][4];
+ triple[] normals; // Optionally specify 4 normal vectors at the corners.
+ pen[] colors; // Optionally specify 4 corner colors.
+ bool straight; // Patch is based on a piecewise straight external path.
+ bool3 planar; // Patch is planar.
+
+ path3 external() {
+ return
+ P[0][0]..controls P[0][1] and P[0][2]..
+ P[0][3]..controls P[1][3] and P[2][3]..
+ P[3][3]..controls P[3][2] and P[3][1]..
+ P[3][0]..controls P[2][0] and P[1][0]..cycle;
+ }
+
+ triple[] internal() {
+ return new triple[] {P[1][1],P[1][2],P[2][2],P[2][1]};
+ }
+
+ triple cornermean() {
+ return 0.25*(P[0][0]+P[0][3]+P[3][3]+P[3][0]);
+ }
+
+ triple[] corners() {return new triple[] {P[0][0],P[0][3],P[3][3],P[3][0]};}
+
+ real[] map(real f(triple)) {
+ return new real[] {f(P[0][0]),f(P[0][3]),f(P[3][3]),f(P[3][0])};
+ }
+
+ triple[] controlpoints() {
+ return new triple[] {
+ P[0][0],P[0][1],P[0][2],P[0][3],
+ P[1][0],P[1][1],P[1][2],P[1][3],
+ P[2][0],P[2][1],P[2][2],P[2][3],
+ P[3][0],P[3][1],P[3][2],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) {
+ return straight ? Bu(0,u)--Bu(3,u) :
+ Bu(0,u)..controls Bu(1,u) and Bu(2,u)..Bu(3,u);
+ }
+
+ 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) {
+ return straight ? Bv(0,v)--Bv(3,v) :
+ Bv(0,v)..controls Bv(1,v) and Bv(2,v)..Bv(3,v);
+ }
+
+ 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(BvPP(0,v),BvPP(1,v),BvPP(2,v),BvPP(3,v),u),
+ bezier(BuP(0,u),BuP(1,u),BuP(2,u),BuP(3,u),v))+
+ cross(bezier(BvP(0,v),BvP(1,v),BvP(2,v),BvP(3,v),u),
+ bezier(BuPP(0,u),BuPP(1,u),BuPP(2,u),BuPP(3,u),v)));
+ return abs(n) > epsilon ? n :
+ 0.25*cross(bezier(BvPP(0,v),BvPP(1,v),BvPP(2,v),BvPP(3,v),u),
+ bezier(BuPP(0,u),BuPP(1,u),BuPP(2,u),BuPP(3,u),v))+
+ 1/6*(cross(bezier(BvPPP(0),BvPPP(1),BvPPP(2),BvPPP(3),u),
+ bezier(BuP(0,u),BuP(1,u),BuP(2,u),BuP(3,u),v))+
+ cross(bezier(BvP(0,v),BvP(1,v),BvP(2,v),BvP(3,v),u),
+ bezier(BuPPP(0),BuPPP(1),BuPPP(2),BuPPP(3),v)))+
+ 1/12*(cross(bezier(BvPPP(0),BvPPP(1),BvPPP(2),BvPPP(3),u),
+ bezier(BuPP(0,u),BuPP(1,u),BuPP(2,u),BuPP(3,u),v))+
+ cross(bezier(BvPP(0,v),BvPP(1,v),BvPP(2,v),BvPP(3,v),u),
+ bezier(BuPPP(0),BuPPP(1),BuPPP(2),BuPPP(3),v)))+
+ 1/36*cross(bezier(BvPPP(0),BvPPP(1),BvPPP(2),BvPPP(3),u),
+ bezier(BuPPP(0),BuPPP(1),BuPPP(2),BuPPP(3),v));
+ }
+
+ static real fuzz=1000*realEpsilon;
+
+ triple normal(real u, real v) {
+ triple n=cross(bezier(BvP(0,v),BvP(1,v),BvP(2,v),BvP(3,v),u),
+ bezier(BuP(0,u),BuP(1,u),BuP(2,u),BuP(3,u),v));
+ real epsilon=fuzz*change2(P);
+ return (abs(n) > epsilon) ? n : normal0(u,v,epsilon);
+ }
+
+ triple normal00() {
+ triple n=9*cross(P[0][1]-P[0][0],P[1][0]-P[0][0]);
+ real epsilon=fuzz*change2(P);
+ return abs(n) > epsilon ? n : normal0(0,0,epsilon);
+ }
+
+ triple normal01() {
+ triple n=9*cross(P[0][3]-P[0][2],P[1][3]-P[0][3]);
+ real epsilon=fuzz*change2(P);
+ return abs(n) > epsilon ? n : normal0(0,1,epsilon);
+ }
+
+ triple normal11() {
+ triple n=9*cross(P[3][3]-P[3][2],P[3][3]-P[2][3]);
+ real epsilon=fuzz*change2(P);
+ return abs(n) > epsilon ? n : normal0(1,1,epsilon);
+ }
+
+ triple normal10() {
+ triple n=9*cross(P[3][1]-P[3][0],P[3][0]-P[2][0]);
+ real epsilon=fuzz*change2(P);
+ return abs(n) > epsilon ? n : normal0(1,0,epsilon);
+ }
+
+ pen[] colors(material m, light light=currentlight) {
+ bool nocolors=colors.length == 0;
+ if(normals.length > 0)
+ return new pen[] {light.color(normals[0],nocolors ? m : colors[0]),
+ light.color(normals[1],nocolors ? m : colors[1]),
+ light.color(normals[2],nocolors ? m : colors[2]),
+ light.color(normals[3],nocolors ? m : colors[3])};
+ if(planar) {
+ triple normal=normal(0.5,0.5);
+ return new pen[] {light.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])};
+ }
+ return new pen[] {light.color(normal00(),nocolors ? m : colors[0]),
+ light.color(normal01(),nocolors ? m : colors[1]),
+ light.color(normal11(),nocolors ? m : colors[2]),
+ light.color(normal10(),nocolors ? m : colors[3])};
+ }
+
+ triple bound(real m(real[], real), triple b) {
+ real x=m(new real[] {P[0][0].x,P[0][1].x,P[0][2].x,P[0][3].x,
+ P[1][0].x,P[1][1].x,P[1][2].x,P[1][3].x,
+ P[2][0].x,P[2][1].x,P[2][2].x,P[2][3].x,
+ P[3][0].x,P[3][1].x,P[3][2].x,P[3][3].x},b.x);
+ real y=m(new real[] {P[0][0].y,P[0][1].y,P[0][2].y,P[0][3].y,
+ P[1][0].y,P[1][1].y,P[1][2].y,P[1][3].y,
+ P[2][0].y,P[2][1].y,P[2][2].y,P[2][3].y,
+ P[3][0].y,P[3][1].y,P[3][2].y,P[3][3].y},b.y);
+ real z=m(new real[] {P[0][0].z,P[0][1].z,P[0][2].z,P[0][3].z,
+ P[1][0].z,P[1][1].z,P[1][2].z,P[1][3].z,
+ P[2][0].z,P[2][1].z,P[2][2].z,P[2][3].z,
+ P[3][0].z,P[3][1].z,P[3][2].z,P[3][3].z},b.z);
+ return (x,y,z);
+ }
+
+ triple min3,max3;
+ bool havemin3,havemax3;
+
+ void init() {
+ havemin3=false;
+ havemax3=false;
+ }
+
+ triple min(triple bound=P[0][0]) {
+ if(havemin3) return minbound(min3,bound);
+ havemin3=true;
+ return min3=bound(minbound,bound);
+ }
+
+ triple max(triple bound=P[0][0]) {
+ if(havemax3) return maxbound(max3,bound);
+ havemax3=true;
+ return max3=bound(maxbound,bound);
+ }
+
+ triple center() {
+ return 0.5*(this.min()+this.max());
+ }
+
+ pair min(projection P, pair bound=project(this.P[0][0],P.t)) {
+ return minbound(controlpoints(),P.t,bound);
+ }
+
+ pair max(projection P, pair bound=project(this.P[0][0],P.t)) {
+ return maxbound(controlpoints(),P.t,bound);
+ }
+
+ void operator init(triple[][] P, triple[] normals=new triple[],
+ pen[] colors=new pen[], bool straight=false,
+ bool3 planar=default) {
+ init();
+ this.P=copy(P);
+ if(normals.length != 0)
+ this.normals=copy(normals);
+ if(colors.length != 0)
+ this.colors=copy(colors);
+ this.planar=planar;
+ this.straight=straight;
+ }
+
+ void operator init(pair[][] P, triple plane(pair)=XYplane,
+ bool straight=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;
+ }
+
+ void operator init(patch s) {
+ operator init(s.P,s.normals,s.colors,s.straight);
+ }
+
+ // A constructor for a convex cyclic path3 of length <= 4 with optional
+ // arrays of 4 internal points, corner normals, and pens.
+ void operator init(path3 external, triple[] internal=new triple[],
+ triple[] normals=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;
+
+ int L=length(external);
+ 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]));
+ if(normals.length > 0) normals.append(array(3,normals[0]));
+ } else if(L == 2) {
+ external=external--cycle--cycle;
+ if(colors.length > 0) colors.append(array(2,colors[0]));
+ if(normals.length > 0) normals.append(array(2,normals[0]));
+ } else if(L == 3) {
+ external=external--cycle;
+ if(colors.length > 0) colors.push(colors[0]);
+ if(normals.length > 0) normals.push(normals[0]);
+ }
+ if(normals.length != 0)
+ this.normals=copy(normals);
+ 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));
+ } else straight=false;
+
+ P=new triple[][] {
+ {point(external,0),postcontrol(external,0),precontrol(external,1),
+ point(external,1)},
+ {precontrol(external,0),internal[0],internal[1],postcontrol(external,1)},
+ {postcontrol(external,3),internal[3],internal[2],precontrol(external,2)},
+ {point(external,3),precontrol(external,3),postcontrol(external,2),
+ point(external,2)}
+ };
+ }
+
+ // A constructor for a convex quadrilateral.
+ void operator init(triple[] external, triple[] internal=new triple[],
+ triple[] normals=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(normals.length != 0)
+ this.normals=copy(normals);
+ 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[0],external[1]-delta[0],external[1]},
+ {external[0]-delta[3],internal[0],internal[1],external[1]+delta[1]},
+ {external[3]+delta[3],internal[3],internal[2],external[2]-delta[1]},
+ {external[3],external[3]-delta[2],external[2]+delta[2],external[2]}
+ };
+ }
+}
+
+patch operator * (transform3 t, patch s)
+{
+ patch S;
+ for(int i=0; i < 4; ++i) {
+ triple[] si=s.P[i];
+ triple[] Si=S.P[i];
+ for(int j=0; j < 4; ++j)
+ Si[j]=t*si[j];
+ }
+
+ transform3 t0=shiftless(t);
+ for(int i=0; i < s.normals.length; ++i)
+ S.normals[i]=t0*s.normals[i];
+
+ S.colors=copy(s.colors);
+ S.planar=s.planar;
+ S.straight=s.straight;
+ return S;
+}
+
+patch reverse(patch s)
+{
+ patch S;
+ S.P=transpose(s.P);
+ if(s.normals.length > 0)
+ S.normals=
+ new triple[] {s.normals[0],s.normals[3],s.normals[2],s.normals[1]};
+ if(s.colors.length > 0)
+ S.colors=new pen[] {s.colors[0],s.colors[3],s.colors[2],s.colors[1]};
+ S.planar=s.planar;
+ S.straight=s.straight;
+ return S;
+}
+
+struct surface {
+ patch[] s;
+
+ 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]);
+ }
+
+ void operator init(triple[][][] P, triple[][] normals=new triple[][],
+ pen[][] colors=new pen[][], bool3 planar=default) {
+ s=sequence(new patch(int i) {
+ return patch(P[i],normals.length == 0 ? new triple[] : normals[i],
+ colors.length == 0 ? new pen[] : colors[i],planar);
+ },P.length);
+ }
+
+ void colors(pen[][] palette) {
+ for(int i=0; i < s.length; ++i) {
+ pen[] palettei=palette[i];
+ s[i].colors=new pen[] {palettei[0],palettei[1],palettei[2],palettei[3]};
+ }
+ }
+
+ 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);
+ }
+
+ // A constructor for a possibly nonconvex cyclic path in a given plane.
+ void operator init (path p, triple plane(pair)=XYplane,
+ bool checkboundary=true) {
+ if(!cyclic(p))
+ abort("cyclic path expected");
+
+ int L=length(p);
+
+ if(L > 4) {
+ for(path g : bezulate(p))
+ s.append(surface(g,plane,checkboundary).s);
+ return;
+ }
+
+ pair[][] P(path 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 new pair[][] {
+ {point(p,0),postcontrol(p,0),precontrol(p,1),point(p,1)},
+ {precontrol(p,0),internal[0],internal[1],postcontrol(p,1)},
+ {postcontrol(p,3),internal[3],internal[2],precontrol(p,2)},
+ {point(p,3),precontrol(p,3),postcontrol(p,2),point(p,2)}
+ };
+ }
+
+ bool straight=piecewisestraight(p);
+ if(L <= 3 && straight) {
+ s=new patch[] {patch(P(p),plane,straight)};
+ return;
+ }
+
+ // 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=surface(subpath(g,0,cut)--cycle,plane,checkboundary).s;
+ s.append(surface(subpath(g,cut,L)--cycle,plane,checkboundary).s);
+ 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;
+ }
+ }
+
+ pair[][] P=P(p);
+
+ if(straight) {
+ s=new patch[] {patch(P,plane,straight)};
+ return;
+ }
+
+ // 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))
+ s=new patch[] {patch(P,plane)};
+ return;
+ }
+
+ 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[0][j]-P[1][j])*P[0][i]).y; // u=0
+ c[1] += w*(conj(P[i][3])*(P[j][3]-P[j][2])).y; // v=1
+ c[2] += w*(conj(P[3][j]-P[2][j])*P[3][i]).y; // u=1
+ c[3] += w*(conj(P[i][0])*(P[j][1]-P[j][0])).y; // v=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(BvP(0,v),BvP(1,v),BvP(2,v),BvP(3,v),u))*
+ bezier(BuP(0,u),BuP(1,u),BuP(2,u),BuP(3,u),v)).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={0,t,1,t};
+ real[] V={t,1,t,0};
+ 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;
+ }
+
+ // 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;
+
+ while(true)
+ for(int i=0; i < L; ++i)
+ if(!straight(p,i) && split(p,i+unitrand())) return;
+ }
+
+ 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[],
+ triple[] normals=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,normals,colors,planar=true));
+ return;
+ }
+
+ // Construct a surface from a possibly nonconvex planar cyclic path3.
+ if(planar != false && internal.length == 0 && normals.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,normals,colors,planar));
+ return;
+ }
+
+ // Path is not planar; split into patches.
+ real factor=1/L;
+ pen[] p;
+ triple[] n;
+ bool nocolors=colors.length == 0;
+ bool nonormals=normals.length == 0;
+ triple center;
+ for(int i=0; i < L; ++i)
+ center += point(external,i);
+ center *= factor;
+ if(!nocolors)
+ p=new pen[] {mean(colors)};
+ if(!nonormals)
+ n=new triple[] {factor*sum(normals)};
+ // 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,
+ nonormals ? n : concat(normals[i:end+1],n),
+ nocolors ? p : concat(colors[i:end+1],p),planar));
+ i=end;
+ }
+ s.push(patch(subpath(external,i,L)--center--cycle,
+ nonormals ? n : concat(normals[i:],normals[0:1],n),
+ nocolors ? p : concat(colors[i:],colors[0:1],p),planar));
+ }
+
+ void operator init(path3 external, triple[] internal=new triple[],
+ triple[] normals=new triple[], pen[] colors=new pen[],
+ bool3 planar=default) {
+ s=new patch[];
+ construct(external,internal,normals,colors,planar);
+ }
+
+ void operator init(explicit path3[] external,
+ triple[][] internal=new triple[][],
+ triple[][] normals=new triple[][],
+ pen[][] colors=new pen[][], bool3 planar=default) {
+ s=new patch[];
+ for(int i=0; i < external.length; ++i)
+ construct(external[i],
+ internal.length == 0 ? new triple[] : internal[i],
+ normals.length == 0 ? new triple[] : normals[i],
+ colors.length == 0 ? new pen[] : colors[i],planar);
+ }
+
+ void push(path3 external, triple[] internal=new triple[],
+ triple[] normals=new triple[] ,pen[] colors=new pen[],
+ bool3 planar=default) {
+ s.push(patch(external,internal,normals,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];
+ 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;
+
+ for(int i=0; i < L; ++i) {
+ path3 h=subpath(g,i,i+1);
+ path3 r=reverse(h);
+ triple max=max(h);
+ triple min=min(h);
+ triple perp=perp(max-c,axis);
+ real fuzz=epsilon*max(abs(max),abs(min));
+ if(abs(perp) < fuzz)
+ perp=perp(min-c,axis);
+ perp=unit(perp);
+ triple normal=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=Tk*h{dirj}..{dirp}Tp*r{-dirp}..{-dirj}cycle;
+ Tk=Tp;
+ dirj=dirp;
+ s[++m]=color == null ? patch(G) :
+ patch(G,new pen[] {color(i,j),color(i+1,j),color(i+1,j+w),
+ color(i,j+w)});
+ }
+ }
+ }
+
+ 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];
+ 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 for a subpatch of P on [u,1] x [v,1].
+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);
+
+ u=1.0-u;
+
+ triple[] c7=split(c3[2],c2[2],c1[2],c0[2],u);
+ triple[] c8=split(c3[3],c2[3],c1[3],c0[3],u);
+ triple[] c9=split(c3[4],c2[4],c1[4],c0[4],u);
+ triple[] c10=split(P3[3],P2[3],P1[3],P0[3],u);
+
+ return new triple[][] {{c7[2],c8[2],c9[2],c10[2]},
+ {c7[1],c8[1],c9[1],c10[1]},
+ {c7[0],c8[0],c9[0],c10[0]},
+ {c3[2],c3[3],c3[4],P3[3]}};
+}
+
+// Return the control points for a subpatch of P on [0,u] x [0,v].
+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);
+
+ u=1.0-u;
+
+ triple[] c4=split(P3[0],P2[0],P1[0],P0[0],u);
+ triple[] c5=split(c3[0],c2[0],c1[0],c0[0],u);
+ triple[] c6=split(c3[1],c2[1],c1[1],c0[1],u);
+ triple[] c7=split(c3[2],c2[2],c1[2],c0[2],u);
+
+ return new triple[][] {
+ {P0[0],c0[0],c0[1],c0[2]},
+ {c4[4],c5[4],c6[4],c7[4]},
+ {c4[3],c5[3],c6[3],c7[3]},
+ {c4[2],c5[2],c6[2],c7[2]}};
+}
+
+patch subpatch(patch s, real ua, real va, real ub, real vb)
+{
+ assert(ua >= 0 && va >= 0 && ub <= 1 && vb <= 1 && ua < ub && va < vb);
+ return patch(subpatchbegin(subpatchend(s.P,ub,vb),ua/ub,va/vb),
+ s.straight,s.planar);
+}
+
+triple point(patch s, real u, real v)
+{
+ return s.point(u,v);
+}
+
+void draw3D(frame f, patch s, material m, light light=currentlight)
+{
+ if(s.colors.length > 0)
+ m=mean(s.colors);
+ bool lighton=light.on();
+ if(!lighton && !invisible((pen) m))
+ m=emissive(m);
+ real granularity=m.granularity >= 0 ? m.granularity : defaultgranularity;
+ draw(f,s.P,s.straight,m.p,m.opacity,m.shininess,granularity,
+ s.planar ? s.normal(0.5,0.5) : O,lighton,s.colors);
+}
+
+void tensorshade(transform t=identity(), frame f, patch s,
+ material m, light light=currentlight, projection P)
+{
+ tensorshade(f,box(t*s.min(P),t*s.max(P)),m.diffuse(),
+ s.colors(m,light),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=light,
+ projection P=currentprojection)
+{
+ if(is3D()) {
+ for(int i=0; i < s.s.length; ++i)
+ draw3D(f,s.s[i],surfacepen[i],light);
+ pen modifiers=thin()+squarecap;
+ for(int k=0; k < s.s.length; ++k) {
+ pen meshpen=meshpen[k];
+ if(!invisible(meshpen)) {
+ meshpen=modifiers+meshpen;
+ real step=nu == 0 ? 0 : 1/nu;
+ for(int i=0; i <= nu; ++i)
+ draw(f,s.s[k].uequals(i*step),meshpen,meshlight);
+ step=nv == 0 ? 0 : 1/nv;
+ for(int j=0; j <= nv; ++j)
+ draw(f,s.s[k].vequals(j*step),meshpen,meshlight);
+ }
+ }
+ } else {
+ 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[] {abs(camera-s.s[i].cornermean()),i};
+
+ depth=sort(depth);
+
+ light.T=shiftless(P.modelview());
+
+ // Draw from farthest to nearest
+ while(depth.length > 0) {
+ real[] a=depth.pop();
+ int i=round(a[1]);
+ tensorshade(t,f,s.s[i],surfacepen[i],light,P);
+ pen meshpen=meshpen[i];
+ if(!invisible(meshpen))
+ draw(f,t*project(s.s[i].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=light,
+ 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,P);
+}
+
+void draw(picture pic=currentpicture, surface s, int nu=1, int nv=1,
+ material[] surfacepen, pen[] meshpen=nullpens,
+ light light=currentlight, light meshlight=light)
+{
+ if(s.empty()) return;
+
+ 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);
+ } else if(pic != null)
+ pic.add(new void(frame f, transform T) {
+ draw(T,f,S,nu,nv,surfacepen,meshpen,light,meshlight,P);
+ },true);
+ if(pic != null) {
+ 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) {
+ pen meshpen=meshpen[k];
+ if(!invisible(meshpen)) {
+ 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=light)
+{
+ material[] surfacepen={surfacepen};
+ pen[] meshpen={meshpen};
+ surfacepen.cyclic(true);
+ meshpen.cyclic(true);
+ draw(pic,s,nu,nv,surfacepen,meshpen,light,meshlight);
+}
+
+void draw(picture pic=currentpicture, surface s, int nu=1, int nv=1,
+ material[] surfacepen, pen meshpen,
+ light light=currentlight, light meshlight=light)
+{
+ pen[] meshpen={meshpen};
+ meshpen.cyclic(true);
+ draw(pic,s,nu,nv,surfacepen,meshpen,light,meshlight);
+}
+
+surface extrude(path p, triple axis=Z)
+{
+ static patch[] allocate;
+ path3 G=path3(p);
+ path3 G2=shift(axis)*G;
+ return surface(...sequence(new patch(int i) {
+ return patch(subpath(G,i,i+1)--subpath(G2,i+1,i)--cycle);
+ },length(G)));
+}
+
+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) 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) 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)
+{
+ surface s=surface(texpath(L));
+ return L.align.is3D ? align(s,L.T3,position,L.align.dir3,L.p) :
+ shift(position)*L.T3*s;
+}
+
+path[] path(Label L, pair z=0, projection P)
+{
+ path[] g=texpath(L);
+ if(L.defaulttransform3) {
+ return L.align.is3D ? align(g,z,project(L.align.dir3,P)-project(O,P),L.p) :
+ shift(z)*g;
+ } else {
+ path3[] G=path3(g);
+ return L.align.is3D ? shift(z)*project(align(G,L.T3,O,L.align.dir3,L.p),P) :
+ shift(z)*project(L.T3*G,P);
+ }
+}
+
+void label(frame f, Label L, triple position, align align=NoAlign,
+ pen p=currentpen, light light=nolight,
+ projection P=currentprojection)
+{
+ Label L=L.copy();
+ L.align(align);
+ L.p(p);
+ if(L.defaulttransform3)
+ L.T3=transform3(P);
+ if(is3D()) {
+ for(patch S : surface(L,position).s)
+ draw3D(f,S,L.p,light);
+ } else {
+ if(L.filltype == NoFill)
+ fill(f,path(L,project(position,P.t),P),
+ light.color(L.T3*Z,L.p,shiftless(P.modelview())));
+ else {
+ frame d;
+ fill(d,path(L,project(position,P.t),P),
+ light.color(L.T3*Z,L.p,shiftless(P.modelview())));
+ add(f,d,L.filltype);
+ }
+ }
+}
+
+void label(picture pic=currentpicture, Label L, triple position,
+ align align=NoAlign, pen p=currentpen, light light=nolight)
+{
+ Label L=L.copy();
+ L.align(align);
+ L.p(p);
+ L.position(0);
+ path[] g=texpath(L);
+ if(g.length == 0 || (g.length == 1 && size(g[0]) == 0)) return;
+ pic.add(new void(frame f, transform3 t, picture pic, projection P) {
+ triple v=t*position;
+ if(L.defaulttransform3)
+ L.T3=transform3(P);
+ if(is3D())
+ for(patch S : surface(L,v).s)
+ draw3D(f,S,L.p,light);
+ if(pic != null) {
+ if(L.filltype == NoFill)
+ fill(project(v,P.t),pic,path(L,P),
+ light.color(L.T3*Z,L.p,shiftless(P.modelview())));
+ else {
+ picture d;
+ fill(project(v,P.t),d,path(L,P),
+ light.color(L.T3*Z,L.p,shiftless(P.modelview())));
+ add(pic,d,L.filltype);
+ }
+ }
+ },!L.defaulttransform3);
+
+ 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)
+{
+ Label L=Label(L,align,p);
+ bool relative=L.position.relative;
+ real position=L.position.position.x;
+ pair Align=L.align.dir;
+ bool alignrelative=L.align.relative;
+ if(L.defaultposition) {relative=true; position=0.5;}
+ if(relative) position=reltime(g,position);
+ if(L.align.default) {
+ alignrelative=true;
+ Align=position <= 0 ? S : position >= length(g) ? N : E;
+ }
+ label(pic,L,point(g,position),
+ alignrelative && determinant(currentprojection.t) != 0 ?
+ -Align*project(dir(g,position),currentprojection.t)*I : L.align);
+}
+
+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;
+
+private real a=4/3*(sqrt(2)-1);
+private transform3 t=rotate(90,O,Z);
+private transform3 t2=t*t;
+private transform3 t3=t2*t;
+private transform3 i=xscale3(-1)*zscale3(-1);
+
+restricted patch octant1=patch(X{Z}..{-X}Z..Z{Y}..{-Z}Y{X}..{-Y}cycle,
+ new triple[] {(1,a,a),(a,a^2,1),(a^2,a,1),
+ (a,1,a)});
+
+restricted surface unithemisphere=surface(octant1,t*octant1,t2*octant1,
+ t3*octant1);
+restricted surface unitsphere=surface(octant1,t*octant1,t2*octant1,t3*octant1,
+ i*octant1,i*t*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)--interp(Z,X,t1){Y}..{-X}interp(Z,Y,t1)--
+ interp(Z,Y,t2){X}..{-Y}cycle,
+ new triple[] {(s2,s2*a,1-s2),(s1,s1*a,1-s1),(s1*a,s1,1-s1),
+ (s2*a,s2,1-s2)});
+}
+
+// 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]=t*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);
+
+private patch unitcylinder1=patch(X--X+Z{Y}..{-X}Y+Z--Y{X}..{-Y}cycle);
+
+restricted surface unitcylinder=surface(unitcylinder1,t*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, projection P=currentprojection)
+{
+ pen q=(pen) p;
+ if(is3D()) {
+ material m=material(p,p.granularity >= 0 ? p.granularity : dotgranularity);
+ for(patch s : unitsphere.s)
+ draw3D(f,shift(v)*scale3(0.5*linewidth(dotsize(q)+q))*s,m,light);
+ } else dot(f,project(v,P.t),q);
+}
+
+void dot(frame f, path3 g, material p=currentpen,
+ projection P=currentprojection)
+{
+ for(int i=0; i <= length(g); ++i) dot(f,point(g,i),p,P);
+}
+
+void dot(frame f, path3[] g, material p=currentpen,
+ projection P=currentprojection)
+{
+ for(int i=0; i < g.length; ++i) dot(f,g[i],p,P);
+}
+
+void dot(picture pic=currentpicture, triple v, material p=currentpen,
+ light light=nolight)
+{
+ pen q=(pen) p;
+ real size=0.5*linewidth(dotsize(q)+q);
+ pic.add(new void(frame f, transform3 t, picture pic, projection P) {
+ if(is3D()) {
+ material m=material(p,p.granularity >= 0 ? p.granularity :
+ dotgranularity);
+ for(patch s : unitsphere.s)
+ draw3D(f,shift(t*v)*scale3(size)*s,m,light);
+ }
+ if(pic != null)
+ dot(pic,project(t*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)
+{
+ for(int i=0; i < v.length; ++i) dot(pic,v[i],p);
+}
+
+void dot(picture pic=currentpicture, explicit path3 g, material p=currentpen)
+{
+ for(int i=0; i <= length(g); ++i) dot(pic,point(g,i),p);
+}
+
+void dot(picture pic=currentpicture, path3[] g, material p=currentpen)
+{
+ for(int i=0; i < g.length; ++i) dot(pic,g[i],p);
+}
+
+void dot(picture pic=currentpicture, Label L, triple v, align align=NoAlign,
+ string format=defaultformat, material p=currentpen)
+{
+ 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);
+ label(pic,L,v);
+}
diff --git a/Build/source/utils/asymptote/base/tree.asy b/Build/source/utils/asymptote/base/tree.asy
new file mode 100644
index 00000000000..0f3559112c3
--- /dev/null
+++ b/Build/source/utils/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 = null;
+ tree right = null;
+ 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/Build/source/utils/asymptote/base/trembling.asy b/Build/source/utils/asymptote/base/trembling.asy
new file mode 100644
index 00000000000..6b94317b1c4
--- /dev/null
+++ b/Build/source/utils/asymptote/base/trembling.asy
@@ -0,0 +1,369 @@
+// Copyright (c) 2008, Philippe Ivaldi.
+// trembling.asy: handwriting package for the software Asymptote.
+// Last modified: Mon Jul 7 23:35:36 CEST 2008
+
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or (at
+// your option) any later version.
+
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+// 02110-1301, USA.
+
+// COMMENTARY:
+
+// THANKS:
+
+// BUGS:
+// magnetic points are experimental...
+
+// CODE:
+
+import geometry;
+import stats;
+
+/*<asyxml><variable type="pair[]" signature="magneticPoints"><code></asyxml>*/
+pair[] magneticPoints;
+/*<asyxml></code><documentation>Array of magnetic points.
+ When a point is magnetized, a trembled path will pass though P if the original path passes through P.
+ When trembling is enabled with the routine 'startTrembling' all the drawn points is added to this array.
+ For convenience, one can magnetize an arbitrary number of points with the routine 'magnetize'.<look href="#magnetize(...point[])"><look href="#startTrembling(real,real,real,real,bool">
+ </documentation></variable></asyxml>*/
+real magneticRadius=1; // unit is bp in postscript coordinates.
+real trembleFuzz(){return min(1e-3,magneticRadius/10);}
+// real trembleFuzz(){return min(1e-1,1);}
+
+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;
+}
+
+/*<asyxml><function type="pair" signature="attract(pair,path,real)"><code></asyxml>*/
+real atime(pair m, path g, real fuzz=trembleFuzz())
+{/*<asyxml></code><documentation>Return the time of the nearest point of 'm' which is on the path g.
+ 'fuzz' is the argument 'fuzz' of 'intersect'.</documentation></function></asyxml>*/
+ 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;
+ // do {
+ // do {// Find a radius for intersection
+ // r += step;
+ // ig=T*scale(r)*unitcircle;
+ // if(ig == g) {
+ // T *= shift(shx,0);
+ // ig=T*scale(r)*unitcircle;
+ // }
+ // t=intersect(ig,g);
+ // } while(t.length <= 0 && r <= maxR && inside(g,ig) != 1);//
+ // if(t.length <= 0) { // degenerated case
+ // r=initr;
+ // T *= shift(shx,0);
+ // write("warning: atime needs numerical adjustment.");
+ // }
+ // } while(t.length <= 0);
+ 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;
+}
+
+/*<asyxml><function type="void" signature="magnetize(...pair[])"><code></asyxml>*/
+void magnetize(...pair[] P)
+{/*<asyxml></code><documentation>Magnetize the points P.
+ When a point is magnetized, a trembled path will pass though P if the original path passes through P.</documentation></function></asyxml>*/
+ if(magneticRadius <= 0) return;
+ currentpicture.uptodate=false;
+ currentpicture.nodes.insert(0,
+ new void(frame f, transform t, transform T, pair lb, pair rt)
+ {
+ for (pair PT:P) {
+ magneticPoints.push(t*PT);
+ }
+ });
+}
+
+/*<asyxml><function type="void" signature="magnetize(...triangle[])"><code></asyxml>*/
+void magnetize(...triangle[] t)
+{/*<asyxml></code><documentation>Magnetize the vertices of the triangles t.<look href="#magnetize(...pair[])"/></documentation></function></asyxml>*/
+ for(int i=0; i < t.length; ++i)
+ magnetize(...new point[] {t[i].A, t[i].B, t[i].C});
+}
+
+void trueMagnetize(pair p)
+{
+ if(magneticRadius <= 0) return;
+ magneticPoints.push(p);
+}
+
+/*<asyxml><function type="path" signature="addnode(path,real)"><code></asyxml>*/
+path addnode(path g, real t)
+{/*<asyxml></code><documentation>Add a node to 'g' at point(g,t).</documentation></function></asyxml>*/
+ 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);
+ og=og..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;
+}
+
+/*<asyxml><function type="path" signature="addnodes(path,int)"><code></asyxml>*/
+path addnodes(path g, int n)
+{/*<asyxml></code><documentation>Add 'n' nodes between each node of 'g'.</documentation></function></asyxml>*/
+ 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;
+}
+
+
+
+/*<asyxml><variable type="real" signature="trembleAngle"><code></asyxml>*/
+real trembleAngle=4, trembleFrequency=0.5, trembleRandom=2;/*<asyxml></code><documentation>Variables used by the routine 'tremble'.</documentation></variable></asyxml>*/
+
+/*<asyxml><function type="path" signature="tremble(path,real,real,real,real)"><code></asyxml>*/
+path tremble(path g,
+ real angle=trembleAngle,
+ real frequency=trembleFrequency,
+ real random=trembleRandom,
+ real fuzz=trembleFuzz())
+{/*<asyxml></code><documentation>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 'angle'.
+ random is 0 means don't use randomized coefficient;
+ More 'random' is hight more the coefficient is hight and the trembling seems randomized.</documentation></function></asyxml>*/
+ if(length(g) == 0) return g;
+ 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(), a=first;
+ real gle;
+ for(int i=1; i <= l; ++i)
+ {
+ pair P=point(tg,i);
+ // gle=(i == l && (cyclic(tg))) ? first : a;
+ // pair post=rotate(gle,point(tg,i-1))*postcontrol(tg,i-1);
+ // pair pre=rotate(gle,P)*precontrol(tg,i);
+ 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;
+}
+
+typedef path pathModifier(path);
+pathModifier NoModifier=new path(path g){return g;};
+pathModifier tremble(real angle=trembleAngle,
+ real frequency=trembleFrequency,
+ real random=trembleRandom,
+ real fuzz=trembleFuzz())
+{
+ return new path(path g){return tremble(g,angle,frequency,random,fuzz);};
+}
+
+void orig_draw(frame f, path g, pen p=currentpen)=draw;
+void tremble_draw(frame f, path g, pen p=currentpen){}
+
+void tremble_marknodes(picture pic=currentpicture, frame f, path g) {}
+void tremble_markuniform(bool centered=false, int n, bool rotated=false) {}
+
+int tremble_circlenodesnumber(real r){return 5;}
+int orig_circlenodesnumber(real r)=circlenodesnumber;
+
+int tremble_circlenodesnumber1(real r, real angle1, real angle2){return 4;}
+int orig_circlenodesnumber1(real r, real angle1, real angle2)=circlenodesnumber;
+
+int tremble_ellipsenodesnumber(real a, real b){return 50;}
+int orig_ellipsenodesnumber(real a, real b)=ellipsenodesnumber;
+
+int tremble_ellipsenodesnumber1(real a, real b, real angle1, real angle2, bool dir){return 20;}
+int orig_ellipsenodesnumber1(real a, real b, real angle1, real angle2, bool dir)=ellipsenodesnumber;
+
+int tremble_parabolanodesnumber(parabola p, real angle1, real angle2){return 20;}
+int orig_parabolanodesnumber(parabola p, real angle1, real angle2)=parabolanodesnumber;
+
+int tremble_hyperbolanodesnumber(hyperbola h, real angle1, real angle2){return 20;}
+int orig_hyperbolanodesnumber(hyperbola h, real angle1, real angle2)=hyperbolanodesnumber;
+
+restricted bool tremblingMode=false;
+/*<asyxml><function type="void" signature="startTrembling(real,real,real,real,bool"><code></asyxml>*/
+void startTrembling(real angle=trembleAngle,
+ real frequency=trembleFrequency,
+ real random=trembleRandom,
+ real fuzz=trembleFuzz(),
+ bool magnetizePoints=true)
+{/*<asyxml></code><documentation>Calling this routine all drawn paths will be trembled with the givens parameters.<look href="#tremble(path,real,real,real,real)"/>
+ If 'magnetizePoints' is true, most dotted points are automatically magnetized.<look href="magnetize(...pair[])"/></documentation></function></asyxml>*/
+ if(!tremblingMode) {
+ tremblingMode=true;
+ tremble_draw=new void(frame f, path g, pen p=currentpen)
+ {
+ if(length(g) == 0 && magnetizePoints) {
+ trueMagnetize(point(g,0));
+ }
+ g=addnodes(g,fuzz*abs(max(g)-min(g)) ...magneticPoints);
+ orig_draw(f,tremble(g,angle,frequency,random,fuzz),p);
+ };
+
+ plain.draw=tremble_draw;
+
+ if(magnetizePoints) {
+ marknodes=new void(picture pic=currentpicture, frame f, path g)
+ {
+ for(int i=0; i <= length(g); ++i) {
+ add(pic,f,point(g,i));
+ magnetize(point(g,i));
+ }
+ };
+ dot=dot();
+
+ markuniform=new markroutine(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));
+ magnetize(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);
+ }
+ }
+ };
+ };
+ }
+
+ circlenodesnumber=tremble_circlenodesnumber;
+ circlenodesnumber=tremble_circlenodesnumber1;
+
+ ellipsenodesnumber=tremble_ellipsenodesnumber;
+ ellipsenodesnumber=tremble_ellipsenodesnumber1;
+
+ parabolanodesnumber=tremble_parabolanodesnumber;
+ hyperbolanodesnumber=tremble_hyperbolanodesnumber;
+ }
+}
+
+// void stopTrembling()
+// {
+// if(tremblingMode) {
+// tremblingMode=false;
+// plain.draw=orig_draw;
+// // draw=orig_draw1;
+// circlenodesnumber=orig_circlenodesnumber;
+// circlenodesnumber=orig_circlenodesnumber1;
+// ellipsenodesnumber=orig_ellipsenodesnumber;
+// ellipsenodesnumber=orig_ellipsenodesnumber1;
+// parabolanodesnumber=orig_parabolanodesnumber;
+// hyperbolanodesnumber=orig_hyperbolanodesnumber;
+// }
+// }
diff --git a/Build/source/utils/asymptote/base/tube.asy b/Build/source/utils/asymptote/base/tube.asy
new file mode 100644
index 00000000000..f62d3897adf
--- /dev/null
+++ b/Build/source/utils/asymptote/base/tube.asy
@@ -0,0 +1,210 @@
+// Author: Philippe Ivaldi
+// 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 guideclosed=cyclic(A);
+ if(len < 2) {return A;};
+ if(guideclosed) {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(guideclosed) {
+ 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;
+}
+
+struct Rmf
+{
+ triple p,r,t; // s=cross(t,r);
+ real reltime;
+ void operator init(triple p, triple r, triple t, real reltime)
+ {
+ this.p=p;
+ this.r=r;
+ this.t=t;
+ this.reltime=reltime;
+ }
+}
+
+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;
+}
+
+private Rmf[] rmf(path3 g, Rmf U0=Rmf(O,O,O,0), real[] t)
+{
+ if(U0.t == O) {
+ triple d=dir(g,0);
+ U0=Rmf(point(g,0),perp(d),d,0);
+ }
+ int l=length(g);
+ Rmf[] R={U0};
+ triple rp,v1,v2,tp,ti,p;
+ real c;
+
+ for(int i=0; i < t.length-1; ++i) {
+ p=point(g,t[i+1]);
+ v1=p-R[i].p;
+ c=dot(v1,v1);
+ if(c != 0) {
+ rp=R[i].r-2*dot(v1,R[i].r)*v1/c;
+ ti=R[i].t;
+ tp=ti-2*dot(v1,ti)*v1/c;
+ ti=dir(g,t[i+1]);
+ v2=ti-tp;
+ rp=rp-2*dot(v2,rp)*v2/dot(v2,v2);
+ R.push(Rmf(p,unit(rp),unit(ti),t[i+1]/l));
+ } else {
+ write("Warning: path3 has duplicated point in Rmf.");
+ R.push(R[R.length-1]);
+ }
+ }
+ return R;
+}
+
+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, coloredpath cp,transform T(real)=
+ new transform(real t) {return identity();},
+ 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(R[0].reltime)*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,cross(R[0].t,R[0].r),R[0].t)*sec,
+ sec2;
+
+ for(int i=1; i < R.length; ++i) {
+ sec=path3(T(R[i].reltime)*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(R[i-1].reltime), tp2=cp.pens(R[i].reltime);
+ 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));
+ t.cyclic(cyclic(g));
+ return surface(rmf(g,t),section,T,cyclic(g));
+}
diff --git a/Build/source/utils/asymptote/base/unicode.asy b/Build/source/utils/asymptote/base/unicode.asy
new file mode 100644
index 00000000000..33571e8341a
--- /dev/null
+++ b/Build/source/utils/asymptote/base/unicode.asy
@@ -0,0 +1,2 @@
+usepackage("ucs");
+usepackage("inputenc","utf8x");
diff --git a/Build/source/utils/asymptote/base/version.asy b/Build/source/utils/asymptote/base/version.asy
new file mode 100644
index 00000000000..e8dcc965de5
--- /dev/null
+++ b/Build/source/utils/asymptote/base/version.asy
@@ -0,0 +1 @@
+string VERSION="1.72";
diff --git a/Build/source/utils/asymptote/base/x11colors.asy b/Build/source/utils/asymptote/base/x11colors.asy
new file mode 100644
index 00000000000..77e0401f06e
--- /dev/null
+++ b/Build/source/utils/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);