diff options
Diffstat (limited to 'Build/source/utils/asymptote/base')
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); |