diff options
Diffstat (limited to 'src/3rdparty/2geom/doc/manual2')
-rw-r--r-- | src/3rdparty/2geom/doc/manual2/ack | 47 | ||||
-rw-r--r-- | src/3rdparty/2geom/doc/manual2/concepts | 128 | ||||
-rw-r--r-- | src/3rdparty/2geom/doc/manual2/d2 | 106 | ||||
-rw-r--r-- | src/3rdparty/2geom/doc/manual2/geometric primitives | 65 | ||||
-rw-r--r-- | src/3rdparty/2geom/doc/manual2/introduction | 41 | ||||
-rw-r--r-- | src/3rdparty/2geom/doc/manual2/piecewise | 134 | ||||
-rw-r--r-- | src/3rdparty/2geom/doc/manual2/s-basis | 91 |
7 files changed, 612 insertions, 0 deletions
diff --git a/src/3rdparty/2geom/doc/manual2/ack b/src/3rdparty/2geom/doc/manual2/ack new file mode 100644 index 0000000..c6325fd --- /dev/null +++ b/src/3rdparty/2geom/doc/manual2/ack @@ -0,0 +1,47 @@ +h1. Acknowledgements and History +2Geom is a group project, having many authors and contributors. The +original code was sketched out by Nathan Hurst and Peter Moulder for +the Inkscape vector graphics program to provide well typed, correct +and easy to use C++ classes. Since then many people have refined and +debugged the code. One of the earliest C++ification projects for +inkscape was replacing NRPoint with NR::Point. + +A conspicuous absence was a Path datatype, and indeed Inkscape +developed at least 3 different internal path datatypes, plus several +others in related projects. Considering the core importance of path +operations in vector graphics, this led to much re-implementation of +algorithms, numerous bugs, and many round trips converting between +forms. + +Many attempts have been made to try and develop a single path data +structure, but all were fated to sit in random SCMs scattered across +the web. + +Several unrelated projects had copied out various portions of the NR +code from Inkscape and in 2006 MenTaLguY and Nathan felt that it was +time to separate out the geometry portions of inkscape into a +separate library for general use and improvement. The namespace was +changed from NR to Geom and a prototype for paths sketched out. +Nathan studied the state of the art for computational geometry whilst +MenTaLguY focused on the design of Paths. + +Before the re-merging of 2Geom with the inkscape svn HEAD it was felt +that a few smaller projects should be ported to use 2Geom. Michael +Wybrow's libavoid advanced connector routing system was ported first. + +(TODO: did this happen? also, add the rest of history..) + +h2. People who have contributed to 2Geom +* Aaron C. Spike +* Alex Mac +* Fred: livarot +* Javier Sanchez-Reyes +* Jean-Francois Barraud +* Jonathon Wright +* Joshua Blocher +* Kim Marriott +* MenTaLguY +* Michael J. Wybrow +* Michael G. Sloan +* Nathan J. Hurst +* Peter J. R. Moulder diff --git a/src/3rdparty/2geom/doc/manual2/concepts b/src/3rdparty/2geom/doc/manual2/concepts new file mode 100644 index 0000000..e89bf55 --- /dev/null +++ b/src/3rdparty/2geom/doc/manual2/concepts @@ -0,0 +1,128 @@ +h1. Concept Checking + +The C++ Standard Template Library introduces the notion of _concepts_, +which specify families of types related by a common interface. In +template-based programming with the STL, concepts serve a similar +purpose to type classes in Haskell. While, unlike Haskell's language-level +support for type classes, concept-checking is not directly supported by the +C++ compiler or language, C++ libraries have been written which use template +techniques to provide compile-time checking and enforcement of concepts. +We use the Boost Concept Checking library. + +h2. Lib2geom's 'Concepts' + +There are several important lib2geom 'concepts'. + +h3. *FragmentConcept* + +This is perhaps the most important concept within lib2geom, as it defines +the interface for the basic, one-dimensional functions. Fragments are +defined on the interval [0,1], which is referred to as the _intended domain_ +of the function. Functions may be well defined for all values (many are), +but the 0-to-1 domain has significant semantic value. When the functions +are used to represent a *Curve*, 0 is the start and 1 is the end. + +h4. @ T::output_type @ + +Every fragment must typedef an *output_type*. This is usually *Coord*, however, +in order to support considering @D2<T>@ a fragment, this typedef was added. +This information is also used by the compiler to infer the proper bounds and +sbasis types. + +h4. Value Query + +<pre><code> +output_type T::valueAt(double); +output_type T::operator()(double); +output_type T::at0(); +output_type T::at1(); +</code></pre> + +*FragmentConcept* defines several methods for retrieving the value at a point. +One method is to use the *valueAt* function, which returns output_type given +a t-value. Fragments are also functors, which in C++ lingo means they look +like function calls, as they overload the () operator. This is essentially +the same as calling valueAt. The functions *at0* and *at1* are also +provided, and should be used whenever the start or end of the function is +required, as many functions directly store this information. + +h4. @ sbasis_type T::toSBasis() @ + +As *SBasis* will be the main function representation, it is desirable to always +be able to approximate and deal with other functions in this way. Therefore, +the *toSBasis* function is required. When *output_type* is @double@, +@sbasis_type@ is *SBasis*. When *output_type* is *Point*, @sbasis_type@ is +*SBasisCurve*. + +(TODO: in writing this it occurs to me that toSBasis should take a tolerance) + +h4. @ T reverse(T) @ + +As most of the implementors of fragment consider functions in a fairly +symmetric way, the *reverse* function was included in the *FragmentConcept*. +*reverse* flips the function's domain on 0.5, such that f'(t) = f(1-t). + +h4. Bounds + +<code><pre> +bounds_type bounds_fast(T); +bounds_type bounds_exact(T); +bounds_type bounds_local(T, Interval); +</pre></code> + +Finding the bounds of a function is essential for many optimizations and +algorithms. This is why we provide 3 functions to do it. *bounds_fast* +provides a quick bounds which contains the actual bounds of the function. +This form is ideal for optimization, as it hopefully does not require too +much computation. *bounds_exact*, on the other hand, provides the exact +bounds of the function. *bounds_local* only returns the bounds of an +interval on the function - at the moment it is unclear if this is exact. +When *output_type* is @double@, @bounds_type@ is *Interval*. When +*output_type* is @Point@, @bounds_type@ is *Rect*. + +See the linear.h code for an example of an implementation of *FragmentConcept*. + +h3. *OffsetableConcept* + +*OffsetableConcept* defines what it means to be offsetable. Like +*FragmentConcept*, this concept requires an output_type, which is used +as the offset type. This still makes since when the implementor is +also a fragment, as in pretty much all cases you would want to offset +a function using the same type it outputs. + +The following operators are defined by *OffsetableConcept*: + +@T + output_type, T - output_type, T += output_type, T -= output_type@, + +h3. *ScalableConcept* + +*ScalableConcept* defines what it means to be scalable. Like +*OffsetableConcept*, it requires an output_type, which is used as the +scalar-type. This is an assumption that may not pan out in the future, +however, for all function types we've used this always applies. +Technically points should not be multiplicable, however, they provide a +convenient storage mechanism for non-uniform scaling. If this changes +in the future, the implementations will remain the same, while the +concept definitions are loosened. + +The following operators are defined by *ScalableConcept*: +@T * scalar_type, T / scalar_type, T *= scalar_type, T /= scalar_type, -x@, + +h3. *AddableConcept* + +*AddableConcept* defines a concept for classes which are closed under +addition (the classes may be added to themselves, and the result is the +same type). The following operators are included: + +@x + y, x - y, x += y, x -= y@ + +h3. *MultiplicableConcept* + +*MultiplicableConcept* defines a concept for classes which are closed under +multiplication (the classes may be multiplied by themselves, and the result +is the same type). The following operators are included: + +@x * y, x *= y@ + +At some point a DividableConcept may be implemented, however, at the moment +it is not very useful. diff --git a/src/3rdparty/2geom/doc/manual2/d2 b/src/3rdparty/2geom/doc/manual2/d2 new file mode 100644 index 0000000..b4769e0 --- /dev/null +++ b/src/3rdparty/2geom/doc/manual2/d2 @@ -0,0 +1,106 @@ +h1. Dealing with two Dimensions: *D2* + +After writing a few classes for two dimensional objects, we realized +that there is a lot of boilerplate associated with what is essentially +lifting one dimensional concepts into two. Instead of frequently +rewriting this code, we instead created the *D2* template class. + +For example, a point in space might be represented by *D2<double>*. +This may, in fact, become the actual representation for Point. +We have not yet replaced Point with this, as not all of Points +operations have been ported (or are applicable), and we are not +yet sure if there is 0 performance loss. + +(TODO remove previous stuff if D2<double> becomes point repr) + +h2. Component Access + +One might expect such an object to have @.x@ and @.y@ fields, however, +it instead consists of 2 element array. With LibNR, it was found that +the availability of @.x@ and @.y@ encouraged people to attempt to +inline operations rather than using the operators, perhaps in (vain) +pursuit of a performance enhancement. By using an array, we encourage +people to think about points as symmetric objects and discourage +direct use of the components. However, we still provide direct access +for the rare occasion that it is needed. Even in these cases, the array +method reduces bugs by encouraging iteration over the array rather than +explicit element reference. + +The components of a *D2* are accessed through the indexing operator, []. +The input value to the index operator is the @enum@ *Dim2*, which +defines *X* = 0 and *Y* = 1. This is to encourage using the +@for(int d=0; i<2; i++)@ idiom when normal operations do not suffice. + +h2. Arithmetic Operators + +@D2<T>@ implements the *AddableConcept*, *OffsetableConcept*, and +*ScalableConcept* (if @T@ implements them as well) yielding the +following operators: + +<pre><code> +AddableConcept: x + y, x - y, x += y, x -= y +OffsetableConcept: x + p, x - p, x += p, x -= p +ScalableConcept: x * p, x / p, x *= p, x /= p, -x + x * d, x / d, x *= d, x /= d +</code></pre> + +(where @x@ and @y@ are *D2*, d is *Coord*, and @p@ is a *Point* and all +return @D2<T>@) + +These operators all just apply the operation on @T@ to the components. +So, @a + b@ just returns @D2<T>(a[X] + b[X], a[Y] + b[Y])@, though the +actual code uses a loop (which is unrolled) in order to avoid +bugs. + +h2. Geometric Operations + +<pre><code> +T dot(D2<T> const &, D2<T> const &); +T cross(D2<T> const &, D2<T> const &); +</code></pre> + +The *dot*:http://en.wikipedia.org/wiki/Dot@product and +*cross*:http://en.wikipedia.org/wiki/Cross@product products are defined +on D2<T> when T implements *AddableConcept* and *MultiplicableConcept. +The cross function returns the length of the resultant 3d vector +perpendicular to the 2d plane. + +@ D2<T> operator*(D2<T> const &, Matrix const &)@ + +This operation applies an affine transformation to the 2d object. + +h2. Fragment Lifting + +*D2<T>* also implements FragmentConcept if T implements it as well, +allowing *D2* to lift one dimensional functions into two-dimensional +parametric curves. As a fragment, a *D2* will represent a function +from a double to a Point. + +h3. Fragment Operations + +In addition to the normal set of Fragment methods, D2 has the following +functions: + +h4. @ D2<T> compose(D2<T> const &a, T const &b); @ + +The *compose* function is defined when @T@ is a function representation which +supports composition. The only forms in 2geom are *SBasis* and *SBasis2d*. +The *D2* *compose* function composes @b@ on both components of @a@. This +makes sense, as a D2<SBasis> is double -> D2<double> and the function for +composition is double -> double. One way to think of composition is that +the output is equivalent to applying @b@ to the input, and then applying a +to @b@'s output. + +h4. @ D2<T> compose_each(D2<T> const &a, D2<T> &b); @ + +The *compose_each* function is similar to the *compose* function, except that +@b@ is also a *D2*, so instead of composing the same function on each component, +the two functions in @b@ are used. + + +h4. @ Point D2<T>::operator()(double x, double y) const @ + +*D2* wraps this operator for when @T@ is a function taking a 2 component input. +The only case of this currently within 2geom is SBasis2d. + +(TODO: derivative/integral) diff --git a/src/3rdparty/2geom/doc/manual2/geometric primitives b/src/3rdparty/2geom/doc/manual2/geometric primitives new file mode 100644 index 0000000..b78370e --- /dev/null +++ b/src/3rdparty/2geom/doc/manual2/geometric primitives @@ -0,0 +1,65 @@ +h1. Geometric Primitives + +What good is a geometry library without geometric primitives? By this +I mean the very basic stuff, Points/Vectors, Matrices, etc. + +2geom's primitives are descendant from libNR's geometric primitives. +They have been modified quite a bit since that initial import. + +h2. Point + +!media/point.png! + +The mathematical concepts of points and vectors are merged into the +2geom class called *Point*. See Appendix A for a further +discussion of this decision. + +Point may be interpreted as a D2<double> with some additional operations. + +(TODO: document these ops.) + +\section{Transformations} + +Affine transformations are either represented with a canonical 6 +element matrix, or special forms. + +\subsection{Scale} + +\includegraphics[height=50mm]{media/scale.png} + +A \code{Scale} transformation stores a vector representing a scaling +transformation. + +\subsection{Rotate} + +\includegraphics[height=50mm]{media/rotate.png} + +A \code{Rotate} transformation uses a vector(\code{Point}) to store +a rotation about the origin. + +In correspondence with mathematical convention (y increasing upwards), +0 degrees is encoded as a vector pointing along the x axis, and positive +angles indicate anticlockwise rotation. So, for example, a vector along +the y axis would encode a 90 degree anticlockwise rotation of 90 degrees. + +In the case that the computer convention of y increasing downwards, +the \verb}Rotate} transformation works essentially the same, except +that positive angles indicate clockwise rotation. + +\subsection{Translate} + +\includegraphics[height=70mm]{media/translate.png} + +A \code{Translate} transformation is a simple vector(\code{Point}) +which stores an offset. + +\subsection{Matrix} + +\includegraphics[height=70mm]{media/matrix.png} + +A \code{Matrix} is a general affine transform. Code is provided for +various decompositions, constructions, and manipulations. A +\code{Matrix} is composed of 6 coordinates, essentially storing the +x axis, y axis, and offset of the transformation. A detailed +explanation for matrices is given in Appendix B. + diff --git a/src/3rdparty/2geom/doc/manual2/introduction b/src/3rdparty/2geom/doc/manual2/introduction new file mode 100644 index 0000000..f8c71fc --- /dev/null +++ b/src/3rdparty/2geom/doc/manual2/introduction @@ -0,0 +1,41 @@ +h1. Introduction + +This manual focuses on the lib2geom computational geometry framework. +The main goal of this framework is the eventual replacement of +Inkscape's multiple and shoddy geometry frameworks. As with any decent +module or lib, 2geom is designed to achieve the desired functionality +while maintaining a generality encouraging usage within other +applications. The focus on robust, accurate algorithms, as well as +utilization of newer and better representations makes the lib +very attractive for many applications. + +h2. Design Considerations + +2Geom is written with a functional programming style in mind. +Generally data structures are considered immutable and rather than +assignment we use labeling. However, C++ can become unwieldy if +this is taken to extreme and so a certain amount of pragmatism is +used in practice. In particular, usability is not forgotten in the +mires of functional zeal. + +The code relies strongly on the type system and uses some of the more +'tricky' elements of C++ to make the code more elegant and 'correct'. +Despite this, the intended use of 2Geom is a serious vector graphics +application. In such domains, performance is still used as a quality +metric, and as such we consider inefficiency to be a bug, and have +traded elegance for efficiency where it matters. + +In general the data structures used in 2Geom are relatively 'flat' +and require little from the memory management. Currently most data +structures are built on standard STL headers, and new and delete are +used sparingly. It is intended for 2Geom to be fully compatible with +Boehm garbage collector though this has not yet been tested. + +h2. Toy-Based Development + +We have managed to come up with a method of library development +which is perfect for geometry - the development of toys exemplifying +a feature while the feature is perfected. This has somewhat subsumed +the role of tests, and provides immediate motivation/reward for work. + +!media/gear.png! diff --git a/src/3rdparty/2geom/doc/manual2/piecewise b/src/3rdparty/2geom/doc/manual2/piecewise new file mode 100644 index 0000000..9f1bd98 --- /dev/null +++ b/src/3rdparty/2geom/doc/manual2/piecewise @@ -0,0 +1,134 @@ +h1. *Piecewise* + +In order to represent functions with a complex shape, it is necessary +to define functions in a piecewise manner. In the graphics world this +sort of function, when parametric, is often referred to as a 'spline'. +Even beyond the representation of paths, it is also often necessary +for mathematical operations to return piecewise functions, as otherwise +the single-fragment versions would require an inordinate degree to +still be accurate. An example of this is the *inverse* function. + +In the world of lib2geom, this is implemented as the *Piecewise* +template class. It manages a sequence of fragment 'segments' and the +cuts between them. These cuts are the various t-values which separate +the different segments. + +h2. Cuts + +The first and last cuts of a piecewise define it's intended range, and +the intermediary cuts separate the segments. With indices, segment i +is always bordered on the left with cut i and on the right with cut i+1. +In general, c = s+1, where c is the number of cuts and s is the number +of segments. These invariants are checked by the +@bool Piecewise<T>::invariants();@ method. + +The cuts essentially define the position and scale of each segment. +For example, if the left and right cuts are 0.5 apart, the segment is +half its regular size; the derivative will be twice as big. + +h4. Cut Query Functions + +<pre><code> +unsigned Piecewise<T>::segN(double, int low = 0, int high = -1) const; +double Piecewise<T>::segT(double, int = -1) const; +double mapToDomain(double t, unsigned i) const; +</code></pre> + +These functions use the cut information to ascertain which segment a +t-value lies within ( *segN* ), and what the t-value is for that segment +at that particular point ( *segT* ). *segN* takes two optional parameters +which limit the range of the search, and are used internally as it is +defined as a recursive binary search. These may be used if you are sure +that the desired segment index lies within the range. *segT* takes an +optional parameter for the case where you already know the segment number. + +mapToDomain is the inverse of segT, as it takes a t-value for a particular +segment, and returns the global piecewise time for that point. + +h4. @ Interval Piecewise<T>::domain() const; @ + +The *domain* function returns the Interval of the intended domain of the +function, from the first cut to the last cut. + +h4. Cut Modification Functions + +<pre><code> +void Piecewise<T>::offsetDomain(double o) +void Piecewise<T>::scaleDomain(double s) +void Piecewise<T>::setDomain(Interval dom) +</code></pre> + +These functions very simply transform the cuts with linear transformations. + +h3. Technical Details + +As the cuts are simply a public std::vector, they may also be accessed as +@pw.cuts@. + +While the actual segments begin on the first cut and end on the last, +the function is defined throughout all inputs by extending the first +and last segments. The exact switching between segments is arbitrarily +such that beginnings (t=0) have priority over endings (t=1). This only +really matters if it is discontinuous at that location. + +In the context of 2d parametrically defined curves, the usefulness of cuts +becomes less apparrent, as they make no real difference for the display +of the curves. Rather, cuts become more of an agreement between various +functions such that the proper data aligns. + +h2. Construction + +Most of the time there is no need for raw construction of *Piecewise* +functions, as they are usually obtained from operations and other sources. + +The following constructors defined for *Piecewise*: +* The blank constructor +* A constructor which explicitly lifts a fragment to a *Piecewise* on [0,1] +* A constructor which takes the *output_type*, and creates a constant function + +<pre><code> +void Piecewise<T>::push_seg(T); +void Piecewise<T>::push_cut(double); +void Piecewise<T>::push(T, double); +</code></pre> + +The usual method for raw construction is to construct a blank *Piecewise* +function, and use these push methods to load the content. *push_seg* and +*push_cut* simply add to the segment and cut lists, although *push_cut* +also checks that the cut time is larger than the last cut. The current +recommended method for calling these functions is to have one initial +*push_cut*, followed by successive calls to *push*, as this will guarantee +that the cuts and segments properly align. + +h2. Operations + +h3. Arithmetic + +*Piecewise* has many arithmetic operations, and implements +*OffsetableConcept*, *ScalableConcept*, *AddableConcept*, and +*MultiplicableConcept*. The operations which operate on two Piecewise +functions (Addable and Multiplicable) work by interleaving the cuts using +mutual *partition* calls, and iterating the resulting segments. + +h3. Fragment Wrapping + +While *Piecewise* is not a fragment (it does not have the [0,1] domain), +it has many functions reminiscient of *FragmentConcept*, including the +bounds functions, () and valueAt. + +(TODO: reverse function?) + +h3. Concatenation + +<pre><code> +void Piecewise<T>::concat(const Piecewise<T> &other); +void Piecewise<T>::continuousConcat(const Piecewise<T> &other); +</code></pre> + +These functions efficiently append another *Piecewise* to the end of a +*Piecewise*. They offset the _other_ *Piecewise* in time such that it is +flush with the end of this *Piecewise*. *continuousConcat* is basically +the same except that it also offsets in space so the functions also match +in value. + +(TODO: compose/derivative/integral) diff --git a/src/3rdparty/2geom/doc/manual2/s-basis b/src/3rdparty/2geom/doc/manual2/s-basis new file mode 100644 index 0000000..5f2ddfb --- /dev/null +++ b/src/3rdparty/2geom/doc/manual2/s-basis @@ -0,0 +1,91 @@ +h1. S-Power-Basis-Forms + +2Geom provides a very powerful algebra for modifying paths. Although +paths are kept in an extended SVG native form where possible, many +operations require a more mathematical form. Our prefferred form is +a sequence of s-power basis polynomials, henceforth referred to as +s-basis. We may convert to this form, perform the required operations +and convert back, approximating to a requested tolerance as required. + +The precise details of the s-basis form are beyond the scope of this +manual - the interested reader should consult \cite{SanchezReyes1997,SanchezReyes2000,SanchezReyes2001,SanchezReyes2003,SanchezReyes2004}. +An elementary, functional description is given in Appendix C. + +(TODO: work out textile citations, math inclusion) + +Geometrically important properties: +* exact representation of bezier segments +* low condition number on bezier conversion. +* strong convergence guarantees +* $C^0$ continuity guarantee + +The following operations are directly implementable and are very efficient: +* fast conversion from all svg elements +* basic arithmetic - @+@, @-@, $\times$, $\div$ +* algebraic derivative and integral +* elementary trigonometric functions: $\sqrt{\cdot}$, $\sin(\cdot)$, $\cos(\cdot)$, $\exp(\cdot)$ +* efficient degree elevation and reduction +* function inversion +* exact solutions for many non trivial operations +* root finding +* composition + +All of these operations are fast. For example, multiplication of two +beziers by converting to s-basis form, multiplying and converting back +takes roughly the same time as performing the bezier multiplication +directly, and furthermore, subdivision and degree reduction are +straightforward in this form. + +h2. Implementation + +h3. *Linear* + +The *Linear* class represents a linear function, mostly for use as a +building block for *SBasis*. *Linear* fully implements *AddableConcept*, +*OffsetableConcept*, and *ScalableConcept* yielding the following operators: + +<pre><code> + AddableConcept: x + y, x - y, x += y, x -= y + OffsetableConcept: x + d, x - d, x += d, x -= d + ScalableConcept: x * d, x / d, x *= d, x /= d, -x +</code></pre> + +(where @x@ and @y@ are *Linear*, d is *Coord*, and all return *Linear*) + +As *Linear* is a basic function type, it also implements the *FragmentConcept*. + +The main *Linear* constructor accepts two *Coord* values, one for the Linear's +value at 0, and one for its value at 1. These may then later be accessed and +modified with the indexing operator, @[]@, with a value of 0 or 1. + +h3. *SBasis* + +The *SBasis* class provides the most basic function form, +$f(t) \rightarrow y$. *SBasis* are made up of multiple *Linear* elements, +which store to/from values for each polynomial coefficient. + +*SBasis*, like *Linear*, above, fully implements *AddableConcept*, +*OffsetableConcept*, and *ScalableConcept*. + +As *SBasis* is a basic function type, it implements the *FragmentConcept*. + +Usually you do not have to directly construct SBasis, as they are obtained +one way or another, and many of the operations are defined, however, *SBasis* +may be constructed as an implicit *Linear* cast, as a copy, or as a blank. +The class is actually an extension of @std::vector<Linear>@. This provides +the primary method of raw *SBasis* construction -- @push_back(Linear)@, which +adds another coefficient to the *SBasis*. + +*SBasis* also provides the indexing accessor/mutator, and due to its vector +nature, iteration. + +(TODO: wouldn't the indexing be provided by vector any way?) + +h3. *SBasis2D* + +SBasis2D provides a multivariate form - functions of the form +$f(u,v) \rightarrow z$. These can be used for arbitrary distortion +functions (take a path $p(t) \rightarrow (u,v)$ and a pair of surfaces +$f(u,v),g(u,v)$ and compose: $q(t) = (f(p(t)), g(p(t)))$. + +(TODO: flesh out this section) |