)if false
\documentclass{article}
\usepackage{axiom}
\begin{document}
\title{Scenegraph code}
\author{Martin J Baker}
\maketitle
\begin{abstract}
Alternative geometry and graphics framework using scenegraph structure.
\end{abstract}
\eject
\tableofcontents
\eject
\section{Preface}
Its easier to explain a graphics framework with diagrams and
screenshots so you may prefer to read the documentation here:
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/graph/}

This is a graphics framework to support many types of drawing,
plotting, diagrams and various types of output that can't be
represented as text only.

This code is intended to support all types of graphical output that
could be required by mathematical programs, this includes,
\begin{itemize}
\item Plotting functions in various dimensions.
\item Grids and axis for such plots.
\item Graph theory diagrams
\item Geometric Shapes
\item Vector diagrams
\item Action of transforms on structures.
\item Force fields.
\end{itemize}
These are just examples, since the code is intended to be as general
purpose as possible. The code is not intended to edit or render these
graphical structures but rather to output to various file types where
they can be edited and rendered by widely available graphics editors
which are much better at that task.

This is similar to text output from Axiom/FriCAS which uses an
intermediate 'OutputForm' which knows enough about the mathematical
structures to be able to format them in various ways but avoids the
need for each individual structure to know about every type of output
that may be required. In a similar way for graphics, this 'Scene'
code is an intermediate format that knows about mathematical
structures like functions, vectors, matrix, complex numbers,
Clifford algebras, and so on but also knows about graphical stuff
like points, lines, arrows, boundaries, surfaces, colours and so on.

This is all structured as a 'scenegraph' which is a very common form
for graphical programs and provides a very ordered way to structure
our graphical information.

I have called this a 'graphics framework' although there are elements
here of both a 'geometry framework' and a 'drawing framework'. There
is also some XML domains which could be separated out and used with
other XML related stuff.

In some ways it would be good to separate out pure geometry
functions such as:
\begin{itemize}
\item Representing Points, Lines, Oriented Planes, Volumes and higher
dimensional elements.
\item Interactions between these such as intersection, projection,
meet, join and so on.
\item Transformations of these such as translation, rotation,
reflection and so on.
\item Normals to surfaces.
\end{itemize}
I think there is a distinction between the way we represent these
infinite elements such a line that extends infinitely in both
directions and a finite line that only extents between two points.
It seems quite difficult to represent both of these in a common
structure that is completely general. For infinite objects then
Clifford/Grassmann algebra seems to provide a very efficient
representation but for finite objects it is simpler to specify
everything in terms of points.

Most drawing functions tend to be on finite objects so it seems
to make sense to represent shapes in terms of points, for instance,
a curved line would be approximated by a list of points linked by
straight lines.

So why do we need a graphics and drawing framework in a CAS? It
would be good if we could use other software to provide this
capability for us. However we need to express these graphics
functions in terms of various types of algebras such as Clifford
algebra and general purpose graphics packages can't do that. It
would be good if FriCAS could be extended at some stage in the
future to allow the user much better graphical interaction with
the program. This would require even tighter interaction between
the algebra, geometry and graphics aspects of the program.

This code is intended to be an alternative to the current Axiom/FriCAS
graphics framework. It is based on a scenegraph structure which allows
lines, surfaces and higher dimensional structures to be defined and
modified by transforms, clipping boxes, material (line width, colour
and so on) which control individual nodes or whole branches in the
scenegraph. This means that, for example, transforms can be applied
continuously to a whole branch without altering the nodes in that
branch, this means that we can avoid a cumulative build up of floating
point errors when many transforms are applied.

This framework supports all the 2D and 3D draw and plotting that the
existing framework supports. Use of transforms in the scenegraph
means that many plots can be combined together, either overlayed or
next to each other and combined with various show scales, grids text
annotations and so on.

The framework supports:
\begin{itemize}
\item Different algebras : vector, matrix, complex numbers and
Clifford Algebra.
\item Different geometries : Euclidean, projective, conformal, hyperbolic.
\item Different coordinate systems : Cartesian coordinates, Argand Plane,
Spherical, Cylindrical.
\item Different number of dimensions : 2, 3 (4 and more to be added)
\end{itemize}

The aim is not just to draw plots but also to allow experimentation
with arbitrary shapes being transformed, for instance to transform a
given shape by reflecting in a circle. To see the sort of thing I am
trying to do here see first chapter of [1] except this is not just
limited to Clifford algebra and conformal space.

Currently Axiom/FriCAS has domains that represent transforms:
(for example : dhmatrix.spad, moebius.spad and so on) but
they are just standalone domains which don't fit into a wider graphics
framework so it is more difficult to combine them with other things and
to experiment. The aim of this framework is to create a way for geometry
and graphics related entities to interwork.

This scenegraph framework currently exports to file formats like : SVG,
X3D, VRML and Wavefront(obj) but currently there is not the capability
to interact directly in a graphical way, that is, there is no xwindow
support. The existing graphics framework allows user interaction in a
very limited way in that the graphics can be displayed in an xwindow
and there is then the ability to do simple transforms like pan and zoom.
However this is not suitable for the new framework because the existing
framework builds a data structure to represent the graphics
using SPAD and then passes this to 'C' code. The 'C' code then does the
pan, zoom and so on and can then save to a file format.

Apart from being very messy trying to mix different languages, this
would not work for the new framework because:
\begin{itemize}
\item We want to use non-linear as well as linear transforms.
\item I would like to be able to work with discontinuous (fractal)
shapes.
\item I want to use different algebras such as vector, matrix,
complex numbers and Clifford algebras and I would not want to implement
these algebras using 'C'.
\end{itemize}

I can't see a way to implement this as I would like, possible options might
be:
\begin{itemize}
\item Implement an OpenGL canvas in SPAD which allows SPAD to directly
output to OpenGL (a thin wrapper for OpenGL calls) and also to read and
process mouse position.
\item Implement Axiom/FriCAS as library code that can be called from
other languages.
\item Implement some form of two-way API which allows other programs
to use SPAD algebra.
\end{itemize}
\section{The Scenegraph Concept}
A scenegraph consists of a number of nodes in a tree structure, the types of
nodes are:
\begin{itemize}
\item Root Node
\item Group Node
\item Line Node
\item Indexed Face Set (IFS) Node
\item Text Node
\item Clip Node
\item Material Node
\item Transform Node
\item Arrow Node
\item NamedPoints Node
\item Shape Node
\item Def Node
\item Use Node
\end{itemize}
This tree is constructed by starting with the root node and adding child
nodes using the addChild! function as follows:
\begin{verbatim}
addChild! : (n : %, c : %) -> Void
\end{verbatim}
where 'c' is the child node and 'n' node that it is being added to. Any
node type can be a child node or a parent (although it only makes sense
to put the root node only at the root).

In general a given node will affect only those nodes under it in the tree
structure.

The purpose and constructors of these nodes is as follows:
(in the following definitions PT refers to an instance of SPointCategory
such as SCartesian(2), SCartesian(3), SArgand and SConformal).
\subsection{Root Node}
Every scenegraph must have one root node and all other nodes must be
added, in a tree structure, under this node (either directly or
indirectly). So the root node can represent the whole scenegraph
and it is what is passed to export routines, for example, to export
the whole scenegraph.

Use the following code to construct the root node, all other nodes are
contained in a tree structure under this node. The root node can have a
bounding box which indicates the extent where the graphical objects exist.
This allows the export code, for example, to have an area to draw the
graphics in.
\begin{verbatim}
createSceneRoot : () -> %
createSceneRoot : (bb : SBoundary(PT)) -> %
createSceneRoot : (minx : I, miny : I, maxx : I, maxy : I) -> %
\end{verbatim}
The parameters define the boundary. This bounding box is intended to
be the area or volume in which the graphical objects are drawn,
elements of the geometry outside this area will not necessarily
be clipped (to be sure use a clipping node with the same boundary).
The bounding box in the root node has these additional effects:
\begin{itemize}
\item When exported to a drawing or editing application this defines
the default area available for display of the graphics on the screen.
\item It allows some elements such as line widths, font sizes, width of
arrow heads and so on to be scaled so that they are of an
appropriate size for the overall screen area. In mathematical
terms a line has no width but when drawing it we need to give
it a nominal width so that it can be seen.
\end{itemize}
If the first version (without boundary information) is used then
the boundary will be calculated, to include all the nodes, when
the scene is first drawn to a file.
\subsection{Group Node}
Constructs a group node, this node does not do anything itself
but contains other nodes. Any node can contain other nodes - we
do not need to use a group node to do this, but if we just want
to group nodes without any other effects then this is a good
choice.
\begin{verbatim}
createSceneGroup : () -> %
addSceneGroup : (n : %) -> %
\end{verbatim}
addSceneGroup is a convenience function which combines the constructor
createSceneGroup with addChild! This allows a scene graph node to be
constructed and added to the scenegraph in one operation.

Most of the scenegraph constructors have this form as an option.
\subsection{Line Node}
Constructs a line node, this contains a (possibly curved) line, represented
by a list of points in n-dimensional space. The dimension of the space is
implicit in the type of points being used.
A Line node can hold a single line or multiple lines. The reason that a
Line node needs to hold multiple lines is that, when a clip is applied
to a line this might break it into several line segments.

The following form constructs a single line:
\begin{verbatim}
createSceneLine : (line : List PT) -> %
addSceneLine : (n : %, line : List PT) -> %
\end{verbatim}
The following form constructs multiple lines:
\begin{verbatim}
createSceneLines : (line : LINES) -> %
addSceneLines : (n : %, line : LINES) -> %
\end{verbatim}
Where : LINES==> List List PT

The following  form constructs a single line where the endpoints are
specified by nodes, the line is drawn upto the boundary of the nodes.
This way of constructing lines is useful for drawing an 'edge' in
graph theory.
\begin{itemize}
\item st is the node at the start of the line
\item en is the node at the end of the line
\item fontScale should be set to the full width of the diagram (not
just the width of this node). If this is not known then choose
an approximate value. This only affects text nodes, if st or en,
or its subnodes, do not contain a text node then its
value is not significant.

\end{itemize}
\begin{verbatim}
createSceneLine : (st : %, en : %, fontScale : DF) -> %
addSceneLine : (n : %, st : %, en : %, fontScale : DF) -> %
\end{verbatim}
Where : LINES==> List List PT
\subsection{Arrow Node}
Constructs a arrow node, this contains a line or lines with an arrow head
at the end in n-dimensional space. The dimension of the space is
implicit in the type of points being used.
The size of the arrow head is controlled by 'mode' and 'size' parameters.
'mode' can have the following values:
\begin{itemize}
\item "fixed"::Symbol -- fixed size line width given by 'size' parameter
\item "proportional"::Symbol -- size as a proportion of the overall bounds
\item "variable"::Symbol -- size as a proportion of the arrow length
\end{itemize}
So "proportional" would typically be used when drawing a graph
(in graph theory) where it looks better if each arrow head is the
same. "variable" would typically be used when drawing a force field
where a bigger arrow head might indicate a stronger force.
\begin{verbatim}
createSceneArrows : (line : List List PT, mode : Symbol, size : DF) -> %
addSceneArrows : (n : %, line : List List PT, mode : Symbol, size : DF) -> %
\end{verbatim}
The following form constructs an arrow where the endpoints are
specified by names from named points node:
\begin{verbatim}
createSceneArrow : (st : String, en : String, offset : PT, mode : Symbol, size : DF) -> %
addSceneArrow : (n : %, st : String, en : String, offset : PT, mode : Symbol, size : DF) -> %
\end{verbatim}
The following  form constructs a single arrow where the endpoints
are specified by nodes, the arrow is drawn upto the boundary of
the nodes. This way of constructing arrows is useful for drawing
an directed edge in graph theory.
\begin{itemize}
\item st is the node at the start of the arrow
\item en is the node at the end of the arrow
\item fontScale should be set to the full width of the diagram (not
just the width of this node). If this is not known then choose
an approximate value. This only affects text nodes, if st or en,
or its subnodes, do not contain a text node then its
value is not significant.
\end{itemize}
\begin{verbatim}
createSceneArrow : (st : %, en : %, offset : PT, mode : Symbol, _
                                           size : DF, fontScale : DF) -> %
addSceneArrow : (n : %, st : %, en : %, offset : PT, mode : Symbol, _
                                           size : DF, fontScale : DF) -> %
\end{verbatim}
\subsection{Indexed Face Set (IFS) Node}
Constructs an indexed face set node, this defines a surface in n-dimensional
space represented by a set of polygons in n-dimensional space.
\begin{verbatim}
createSceneIFS : (inx : List List NNI, pts : List PT) -> %
addSceneIFS : (n : %, inx : List List NNI, pts : List PT) -> %
createSceneIFS : (in1 : SceneIFS(PT)) -> %
addSceneIFS : (n : %, in1 : SceneIFS(PT)) -> %
\end{verbatim}
A specialised constructor for an IFS node constructs a 3D box.
Constructs an indexed face set node which is a 3D box of a given size
\begin{verbatim}
createSceneBox : (size : DF) -> %
addSceneBox : (n : %, size : DF) -> %
\end{verbatim}
\subsection{Text Node}
Constructs a text node, text can be used for labelling anything
such as graphs, axes and so on.
\begin{verbatim}
createSceneText : (text : TEXT) -> %
addSceneText : (n : %, text : TEXT) -> %
createSceneText : (str : String, sz : NNI, pz : PT) -> %
addSceneText : (n : %, str : String, sz : NNI, pz : PT) -> %
\end{verbatim}
where : TEXT==> Record(txt : String, siz : NNI, pos : PT)
which defines the text to be printed with its size and position.
\subsection{Clip Node}
Constructs a clip node, clips its sub nodes in the coordinate
system in force at the clip node.
\begin{verbatim}
createSceneClip : (bb : SBoundary(PT)) -> %
addSceneClip : (n : %, bb : SBoundary(PT)) -> %
\end{verbatim}
where : bb is the boundary that the node is clipped to.
\subsection{Material Node}
Constructs a material node
This sets the lineWidth, lineCol and fillCol for all nodes under
this, unless overridden by another material node.
That is the material parameters that apply to a given node are
those of the closest material node above it in the hierarchy
\begin{verbatim}
createSceneMaterial : (mat : MATERIAL) -> %
addSceneMaterial : (n : %, mat : MATERIAL) -> %
createSceneMaterial : (lineW : DF, lineC : String, fillC : String) -> %
addSceneMaterial : (n : %, lineW : DF, lineC : String, fillC : String) -> %
\end{verbatim}
Where : MATERIAL==> Record(lineWidth : DF, lineCol : String, fillCol : String)
\subsection{Transform Node}
Constructs a transform node
This transforms the points and vectors below this node
If a given node has more than one transform node above
it in the hierarchy then the transforms are concatenated (combined).
\begin{verbatim}
createSceneTransform : (tran : TR) -> %
addSceneTransform : (n : %, tran : TR) -> %
\end{verbatim}
\subsection{NamedPoints Node}
The aim of the 'named points' node and associated domain is to
provide better support for drawing graphs (that is 'graphs' as in
graph theory) and diagrams of trees, latices and category theory
arrow diagrams. In other words, diagrams with named nodes and
arrows or lines between these nodes.
\begin{verbatim}
createSceneNamedPoints : (np : SceneNamedPoints PT) -> %
    ++ createSceneNamedPoints(np) constructs a named points node, this
    ++ allows us to define a set of points which can be used multiple
    ++ times in the scenegraph.
addSceneNamedPoints : (n : %, np : SceneNamedPoints PT) -> %
    ++ addSceneNamedPoints(n, np) is a convenience function which
    ++ combines createSceneNamedPoints with addChild!
\end{verbatim}
A tutorial showing examples of how to use the named point node
is here.
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/graph/tutorial/named/index.htm}
\subsection{Shape Node}
A shape is something like a rectangle or an ellipse in 2 dimensions
or a sphere or box in 3 dimensions.

Its main purpose here, especially in 2 dimensions, is to provide a
way to graphically show sets or visually associate things together.
That is, the shape could be drawn around other elements to show that
they are a set. For example we could draw Venn diagrams or show how
one set of elements maps to another set of elements.

Of course we could create a shape by plotting the equation for that
shape, for example, by using 'ParametricPlaneCurve'. However that
would be unnecessarily complicated when we just want a shape to
visually show a grouping of elements, the mathematical properties
of the shape are not the issue here.

The shape is stored as follows:
\begin{verbatim}
SHAPE ==> Record(shptype : Symbol, centre : PT, size : PT, fill : Boolean)
\end{verbatim}
where:

'shptype' may have the following values:
\begin{itemize}
\item "rect"::Symbol
\item "ellipse"::Symbol
\item "box"::Symbol
\item "sphere"::Symbol
\end{itemize}

centre determines where the shape is drawn.

size determines the extent of the shape (which may be different in each
dimension).

fill, if set to true, will fill the shape with the current fill colour.
otherwise, if set to false, the inside of the shape is transparent.
\begin{verbatim}
addSceneLines : (n : %, line : LINES) -> %
  ++ a convenience function which combines createSceneLines with addChild!
createSceneShape : (shape : SHAPE) -> %
  ++ This just creates the shape without adding it.
\end{verbatim}

A tutorial showing examples of how to use the shape node is here.
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/graph/tutorial/shapes/}
\subsection{Def Node}
The 'def' and 'use' constructs are used together to allow us to include
a branch of the scenegraph multiple times in the scene.
\begin{verbatim}
createSceneDef : (nam : String, nde : %) -> %
    ++ createSceneDef(nam, nde) defines a point in the scenegraph
    ++ so that it can be used elsewhere.
addSceneDef : (n : %, nam : String, nde : %) -> %
    ++ addSceneDef(n, nam, nde) is a convenience function which
    ++ combines createSceneDef with addChild!
\end{verbatim}
A tutorial showing examples of how to use the def and use nodes is here.
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/graph/tutorial/defuse/}

\subsection{Use Node}
The 'def' and 'use' constructs are used together to allow us to include
a branch of the scenegraph multiple times in the scene.
\begin{verbatim}
createSceneUse : (nam : String) -> %
    ++ createSceneUse(nam) uses another point in the scenegraph.
addSceneUse : (n : %, nam : String) -> %
    ++ addSceneUse(n, nam) is a convenience function which
    ++ combines createSceneUse with addChild!
\end{verbatim}
A tutorial showing examples of how to use the def and use nodes is here.
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/graph/tutorial/defuse/}

\section{Drawing Plots and grids}
The following constructors create 'compound' nodes in that the node
returned has subnodes under it.
\subsection{Grids and Patterns}
Grids are useful as a background to plots, they consist of various
straight, horizontal and vertical lines.

The form without the step size constructs a grid with:
\begin{itemize}
\item narrow blue lines every 20 units
\item wide blue lines every 100 units
\item wide red lines every 200 units
\end{itemize}
The form with step uses the prevailing colour and thickness and the
step parameter defines the spacing between lines.

Since the lines in grids are defined by the endpoints of the lines, it
does not make sense to apply a non-linear transform to them as the
lines will remain straight and won't be transformed as they should.

Patterns are used to show the effect of transforms. Although they may
be approximated by straight lines the points are intended to be close
enough together to transform reasonably accurately.
\subsection{Grids}
This form with step uses the prevailing colour and thickness and
the stepSize parameter defines the spacing between lines in local
coordinates.
\begin{verbatim}
createSceneGrid : (stepSize : DF, bb : SBoundary(PT)) -> %
addSceneGrid : (n : %, stepSize : DF, bb : SBoundary(PT)) -> %
\end{verbatim}
The next form, without the step size, constructs a grid with:
\begin{itemize}
\item narrow blue lines every 20 units
\item blue lines every 100 units
\item red lines every 200 units
\end{itemize}
This is constructed as follows:
\begin{verbatim}
createSceneGrid : (bb : SBoundary(PT)) -> %
addSceneGrid : (n : %, bb : SBoundary(PT)) -> %
\end{verbatim}
\subsection{Patterns}
The pattern depends on ptype parameter as follows:
\begin{itemize}
\item "GRID"::Symbol: construct a set of horizontal and vertical lines in the current
clip boundary and current material with a spacing between
lines given by the step parameter.
\item "SIERPINSKI"::Symbol: constructs a Sierpinski fractal. step parameter gives the
level of subdivision.
\item "HOUSE"::Symbol: constructs a house shape.
\end{itemize}
This is constructed as follows:
\begin{verbatim}
createScenePattern(ptype : Symbol, step : NNI, bb : SBoundary(PT)) : %
addScenePattern(n : %, ptype : Symbol, step : NNI, bb : SBoundary(PT)) : %
\end{verbatim}
\subsection{One Dimensional subspace in Two Dimensions}
This represents 1 dimension (line - possibly curved) in 2 dimensions (plane)
The line is approximated as end-to-end straight lines defined by a list of
points. In theory a line has no width but in that case we would not see it
so we give it a width given by the material node that is
applicable in this part of the scene graph.

The plot is defined by a function and a range of values. There are various
ways to define this function:
\begin{itemize}
\item DF -> DF a mapping from float to float
\item DF -> PT a mapping from float to point
\end{itemize}
Where:
DF ==> DoubleFloat
PT ==> SPointCategory -- an instance of SPointCategory represents a point.
We can also create the plot using an indirect parameter. (parametric)
PPC is ParametricPlaneCurve(DF -> DF) which is created with curve(f1, f2)
where f1 and f2 are functions of type ComponentFunction, in this case
DF -> DF
\begin{verbatim}
createPlot1Din2D : (f : DF -> PT, tRange : SEG, numPts : NNI) -> %
addPlot1Din2D : (n : %, f : DF -> PT, tRange : SEG, numPts : NNI) -> %
createPlot1Din2D : (DF -> DF, SEG, numPts : NNI) -> %
addPlot1Din2D : (n : %, DF -> DF, SEG, numPts : NNI) -> %
createPlot1Din2Dparametric : (PPC, SEG, numPts : NNI) -> %
addPlot1Din2Dparametric : (n : %, PPC, SEG, numPts : NNI) -> %
\end{verbatim}
\subsection{One Dimensional subspace in Three Dimensions}
create a line (1D subspace) in 3D space
This represents 1 dimension (line - possibly curved) in 3 dimensions.
In theory a line has no width but in that case we would not see it
so we give it a width given by the material node that is
applicable in this part of the scene graph.

Again there are various ways to define this function:
PCFUN is a function from float to point : DF -> PT
PSC ParametricSpaceCurve(DF -> DF) is created with curve(f1, f2, f3)
where f1, f2 and f3 are functions of type ComponentFunction, in this case
DF -> DF
create a line (1D subspace) in 3D space
\begin{verbatim}
createPlot1Din3Dparametric : (PSC, SEG, numPts : NNI) -> %
addPlot1Din3Dparametric : (n : %, PSC, SEG, numPts : NNI) -> %
createPlot1Din3Dparametric : (PCFUN, SEG, numPts : NNI) -> %
addPlot1Din3Dparametric : (n : %, PCFUN, SEG, numPts : NNI) -> %
\end{verbatim}
\subsection{Two Dimensional subspace in Three Dimensions}
create a surface (2D subspace) in 3D space
The surface is approximated by polygons which are
represented by in indexed face set (IFS) node
create a surface (2D subspace) in 3D space
\begin{verbatim}
createPlot2Din3D : (ptFun : PSFUN, uSeg : SEG, vSeg : SEG, numPts : NNI) -> %
createPlot2Din3D : ((DF, DF) -> DF, SEG, SEG, numPts : NNI) -> %
addPlot2Din3D : (n : %, (DF, DF) -> DF, SEG, SEG, numPts : NNI) -> %
createPlot2Din3Dparametric : (PSFUN, SEG, SEG, numPts : NNI) -> %
addPlot2Din3Dparametric : (n : %, PSFUN, SEG, SEG, numPts : NNI) -> %
createPlot2Din3Dparametric : (PSF, SEG, SEG, numPts : NNI) -> %
addPlot2Din3Dparametric : (n : %, PSF, SEG, SEG, numPts : NNI) -> %
\end{verbatim}
\section{Exporting the graphics}
This scenegraph framework currently exports to the following file formats:
\begin{itemize}
\item SVG - For 2 dimensional graphics this is the most standards based and also
        supported by graphical editors such as Inkscape.
\item X3D - For 3 dimensional graphics I think this is the most standards
        compliant but it is not supported on many 3D editors.
\item VRML - VRML2 and VRML97 are supported but not VRML1. This flavour of
         VRML holds the same information as X3D but using a different
         non-XML syntax.
\item Wavefront(obj) - This is a very simple 3D format that will hold meshes
                   but not text or colour information. It is supported on
                   most 3D editors but only use it as a fallback if the
                   other 3D formats don't work.
\end{itemize}
To export a scenegraph to one of these file formats use the following
function calls:

Write an 'SVG' representation of node 'n' (usually the root node) to
the filename supplied:
\begin{verbatim}
writeSvg : (n : %, filename : String) -> Void
\end{verbatim}
Write an 'X3D' representation of node 'n' (usually the root node)
to the filename supplied.
\begin{verbatim}
writeX3d : (n : %, filename : String) -> Void
\end{verbatim}
Write a 'VRML' representation of node 'n' (usually the root node)
to the filename supplied.
\begin{verbatim}
writeVRML : (n : %, filename : String) -> Void
\end{verbatim}
Write an 'OBJ' (Wavefront) representation of node 'n' (usually the
root node) to the filename supplied.
\begin{verbatim}
writeObj : (n : %, filename : String) -> Void
\end{verbatim}
If we only want to create an XML structure without writing to a file
we can use these functions:

create an XmlElement containing a 'SVG' representation of node 'n' and
the nodes below it:
\begin{verbatim}
toSVG : (n : %, mat : MATERIAL, tran : TR, bb : SBoundary(PT)) -> XmlElement
\end{verbatim}
Create an XmlElement containing a 'X3D' representation of node 'n' and
the nodes below it:
\begin{verbatim}
toX3D : (n : %, mat : MATERIAL, tran : TR, bb : SBoundary(PT)) -> XmlElement
\end{verbatim}
Creates .OBJ (Wavefront) structures from scenegraph tree structure called
recursively for each node, so when called on root node in scenegraph
all other nodes in the scenegraph will get called. When called the
reference values should be empty or zero and when the function returns
they will be set.
\begin{verbatim}
toObj : (n : %, ptLst : Reference List PT, _
      indexLst : Reference List List NNI, _
      indexNxt : Reference NNI, tran : TR, bb : SBoundary(PT)) -> Void
\end{verbatim}
\section{Using the code}
\subsection{Example 1 - Two dimensional plots with scale.}
First we will create a couple of functions to plot:
\begin{verbatim}
DF ==> DoubleFloat
fnsin(x : DF) : DF == sin(x/100::DF)*400::DF
fntan(x : DF) : DF == tan(x/100::DF)*400::DF
\end{verbatim}
Then we create a bounding box, this is the box within which we do the drawing.
This contains two points representing the maximum and minimum extent of the
drawing area or volume. That is 'mins' contains the minimum values of x, y...
and 'maxs' contains the maximum values of x, y...
\begin{verbatim}
view := boxBoundary(sipnt(0, -500)$SCartesian(2), sipnt(1200, 500)$SCartesian(2))
\end{verbatim}
\begin{verbatim}
sc := createSceneRoot(view)$Scene(SCartesian(2))
\end{verbatim}
\begin{verbatim}
gd := addSceneGrid(sc, view)$Scene(SCartesian(2))
\end{verbatim}
we can now export this to a SVG file so that we can confirm that
the scene contains a grid
\begin{verbatim}
writeSvg(sc,"test1.svg")
\end{verbatim}
Now lets add a plot to this. First we add a 'material' node to the root to
define the colour and thickness of the plot.
\begin{verbatim}
mt1 := addSceneMaterial(sc,3::DF,"blue","green")$Scene(SCartesian(2))
\end{verbatim}
Then we add the plot to the material node using the sine function that we
defined earlier.
\begin{verbatim}
ln1 := addPlot1Din2D(mt1, fnsin, 0..1000::DF, 49)$Scene(SCartesian(2))
\end{verbatim}
we can now export this to a SVG file again to see that
the plot has been added to the grid
\begin{verbatim}
writeSvg(sc,"test2.svg")
\end{verbatim}
Lets now add second plot to the scene so that we can see how to modify
it using material, transform and clip.
\begin{verbatim}
mt2 := addSceneMaterial(sc,3::DF,"green","green")$Scene(SCartesian(2))
\end{verbatim}
\begin{verbatim}
tr2 := addSceneTransform(mt2, stransform([_
             [1::DF, 0::DF, 0::DF], _
             [0::DF, 1::DF, 0::DF], _
             [0::DF, 0::DF, 1::DF]]))$Scene(SCartesian(2))
\end{verbatim}
\begin{verbatim}
bb := boxBoundary(sipnt(100, -400)$SCartesian(2), sipnt(1100, 400)$SCartesian(2))
bb2 := addSceneClip(tr2, bb)$Scene(SCartesian(2))
\end{verbatim}
\begin{verbatim}
ln2 := addPlot1Din2D(bb2, fntan, 0..1000::DF, 49)$Scene(SCartesian(2))
\end{verbatim}
\begin{verbatim}
tx := addSceneText(sc,"sin(theta)",32::NNI,_
          sipnt(200, 400)$SCartesian(2))$Scene(SCartesian(2))
\end{verbatim}
\begin{verbatim}
writeSvg(sc,"test3.svg")
\end{verbatim}
\section{Capabilities of this Framework}
\begin{itemize}
\item draw plots
\item define shapes.
\item show scales and grids
\item combine many graphs/shapes in one view
\item transform graphs/shapes of groups of shapes.
\item apply properties like colour, line thickness to these nodes or groups.
\item add text annotations.
\item export to SVG, X3D, VRML and Wavefront(obj) files.
\end{itemize}
\section{Programming Issues}
Usually, when programming using SPAD, I try to use a functional programming
style as much as possible, for instance making objects (representations)
immutable. However I don't think it would be practical to implement the
scenegraph in this way. A design requirement is that the scenegraph needs
to hold a large amount of information, A 3D scene might be made from
thousands (perhaps millions?) of polygons. For the sake of efficiency we
need to be able to modify it without copying the whole thing into a new
object for each change.

The object oriented style of programming would seem to be ideal for a
scenegraph. Each type of node in the scenegraph could be a different object
all inheriting from a common object. Unfortunately is is not easy to do
this in SPAD because it does not support polymorphism.

So what I have ended up with is perhaps the worst of both styles of
programming?
\section{Transforms in scene graph STransform}
Existing transforms in FriCAS (such as dhmatrix.spad and
moebius.spad) are stand alone chunks of algebra which don't
interwork with each other and it would be a lot of work to incorporate them
into other code especially graphics and geometry.

The aim of STransform is to make a completely general template for
transforms so that specific transforms like a Moebius transform could
be implemented so that the slot directly into the geometry/graphics
framework.

We have not achieved that aim yet. STransform does work with the various
implementations of the SPointCategory such as SCartesian(2), SCartesian(3),
SArgand and SConformal. What I would like to do is to have
a transform category which could be implemented by domains like
dhmatrix.spad and moebius.spad, then they could be
used in the geometry/graphics framework anywhere transforms are appropriate.

The most general form of transform would be represented by PT->PT where
PT is any implementation of SPointCategory. So ideally all transforms
should be defined in this way. The usual way to transform a vector is to
use a matrix like this:
transform(in : Vector, def : Matrix) : Vector
So what I need is a way to curry it into something like this:
transform(def : Matrix) : (Vector->Vector)
I don't know how to this? that is, how do I create an anonymous function
where a variable (in this case def : Matrix) is a constant in the function?
\section{Future Enhancements}
\begin{itemize}
\item show union and intersection (join and meet)
\item slice through shapes and show sections.
\item represent 4 and higher dimensions using various methods like colour or
  time animation.
\item Support interaction with the graphics, possibly by using a OpenGL
  canvas which could be called directly from SPAD
  Bill Page mentioned that there is an OpenGL binding for lisp.
  \url{http://common-lisp.net/project/cl-opengl/}
  \url{http://repo.or.cz/w/cl-glfw.git}
\end{itemize}
\section{SPointCategory}
SPointCategory defines a type that can represent either a point or a vector.
It is important to make the distinction between a point and a vector (and
not just treat a point as an offset from the origin) because a point and a
vector will transform differently. Pure rotations will act on points and
vectors identically but pure translations act only on points.

SPointCategory has various implementations depending on the algebra and
type of space that we are dealing with:
\begin{itemize}
\item SCartesian - n dimensional space defined by x, y... and 0 for vectors and 1
            for points. Translations are defined by (n+1)x(n+1) matrix.
\item SArgand - two dimensional space defined by real and imaginary parts.
           Translations are defined by general function of a complex
           variable C->C.
\item SConformal - Two extra dimensions added to form a null space.
\end{itemize}
What I really want to do is define implementations of conformal space
in various number of dimensions using Clifford algebra say 'SConformal2d'.
This would need to join SPointCategory with a particular implementation
of CliffordAlgebra like:
CliffordAlgebra(4, DF, [[1::DF, 0::DF, 0::DF, 0::DF], _
                      [0::DF, 1::DF, 0::DF, 0::DF], _
                      [0::DF, 0::DF, 1::DF, 0::DF], _
                      [0::DF, 0::DF, 0::DF, -1::DF]])
(DF ==> DoubleFloat)
but I don't know how to do this.

This category is implicitly defined over DoubleFloat, the user is
not given given the option of specifying any other type, I did consider
this but decided not to because:
\begin{itemize}
\item We require an ordered number system (that is we require '<') to support
  features like clipping.
\item In most cases the functions and transforms would be continuous.
\end{itemize}
)endif

)abbrev category SPTCAT SPointCategory
++ Author: Martin Baker
++ Date Created: September 2010
++ Basic Operations:
++ convert
++ Related Constructors:
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ http://www.euclideanspace.com/prog/scratchpad/mycode/graph/
++ Description: PointCategory is the category of points and vectors in space.
++ Points may be used to represent shapes in a scenegraph such as: the
++ vertices of a cube or points along a line.
++ Shapes such as these may be defined explicitly or may be plotted.
++ We implement SetCategory to allow us to curry transforms from one
++ point to another.

SPointCategory() : Category == SetCategory with
  spnt : (a : DoubleFloat, b : DoubleFloat) -> %
    ++ spnt(a, b) constructs a 2D point defined by a and b
    ++ of type R
  sipnt : (a : Integer, b : Integer) -> %
    ++ sipnt(a, b) constructs a 2D point defined by a and b
    ++ of type Integer
  spnt : (a : DoubleFloat, b : DoubleFloat, c : DoubleFloat) -> %
    ++ spnt(a, b, c) constructs a 3D point defined by a, b and c
    ++ of type R
  sipnt : (a : Integer, b : Integer, c : Integer) -> %
    ++ sipnt(a, b, c) constructs a 3D point defined by a, b and c
    ++ of type Integer
  svec : (a : DoubleFloat, b : DoubleFloat) -> %
    ++ svec(a, b) constructs a 2D vector defined by a and b
    ++ of type R
  sivec : (a : Integer, b : Integer) -> %
    ++ sivec(a, b) constructs a 2D vector defined by a and b
    ++ of type Integer
  svec : (a : DoubleFloat, b : DoubleFloat, c : DoubleFloat) -> %
    ++ svec(a, b, c) constructs a 3D vector defined by a, b and c
    ++ of type R
  sivec : (a : Integer, b : Integer, c : Integer) -> %
    ++ sivec(a, b, c) constructs a 3D vector defined by a, b and c
    ++ of type Integer
  min : (a : %, b : %) -> %
    ++ min(a, b) returns a point whose value in each dimension is
    ++ the minimum. So if a has the smallest x value and b has the
    ++ smallest y value then the result will combine these. This is
    ++ useful for constructing boundary boxes around sets of points.
  max : (a : %, b : %) -> %
    ++ max(a, b) returns a point whose value in each dimension is
    ++ the maximum. So if a has the largest x value and b has the
    ++ largest y value then the result will combine these. This is
    ++ useful for constructing boundary boxes around sets of points.
  dimension : (p : %) -> PositiveInteger
    ++ dimension(p) returns the number of dimensions that make up the
    ++ point category p.
  Pnan? : (p : %) -> Boolean
    ++ Pnan?(p) returns true if any of its elements are not valid
    ++ DoubleFloat values.
    ++ Examples of invalid floating point numbers are when we
    ++ have divided a given element by zero or taken
    ++ sqrt of negative number at some stage?
    ++ Note: we can still represent a point at infinity and we can
    ++ represent points using complex numbers, but we do this using
    ++ projective or conformal space or by using the appropriate
    ++ instance of SPointCategory, not by using invalid floating
    ++ point numbers.
  unitVector : (p : %) -> %
    ++ unitVector(p) returns a vector with the same direction as p
    ++ but with unit length. We scale until length is 1
  distanceSquared : (p1 : %, p2 : %) -> DoubleFloat
    ++ distanceSquared(p1, p2) returns the distance between the
    ++ points p1 and p2.
    ++ There are 2 metrics associated with any SPointCategory
    ++ the underlying space and the space that it is embedded in
    ++ this is the distance in the underlying space.
  distance : (p1 : %, p2 : %) -> DoubleFloat
    ++ distance(p1, p2) returns the distance between the points
    ++ p1 is the first point.
    ++ p2 is the second point.
    ++ There are 2 metrics associated with any SPointCategory
    ++ the underlying space and the space that it is embedded in
    ++ this is the distance in the underlying space.
  parallel : (x : %, y : %) -> DoubleFloat
    ++ parallel(x, y) returns the length of the parallel component of
    ++ two vectors.
    ++ In the case of Euclidean space this is the dot product of two
    ++ vectors. We use this function to test the colinearity
  colinearity : (x : %, y : %) -> DoubleFloat
    ++ colinearity(x, y) returns a measure of how parallel two vectors
    ++ are independent of the length of the vectors:
    ++ 1.0 is completely parallel
    ++ 0.0 is completely perpendicular
    ++ returned value will be between these
  perpendicular : (x : %, y : %) -> %
    ++ perpendicular(x, y) returns a vector perpendicular to the two
    ++ vectors in the case of 3D Euclidean space this is the cross the
    ++ product of two vectors. We use this function to compute orthogonal
  _+: (x : %, y : %) -> %
    ++ +(x, y) gives addition
    ++ add the corresponding elements of the vector or point.
    ++ The type (vector or point) depends on the following table:
    ++ vector + vector = vector
    ++ point + vector = point
    ++ point + point = invalid (but interpret as vector)
    ++ Although, adding two points is not strictly a valid operation,
    ++ we allow it here by interpreting the point as a vector relative
    ++ to the origin. We do that because there are some useful short
    ++ cuts, for instance, to find the central point between two points
    ++ we can add them together and scalar multiply by 0.5.
  _-: (x : %, y : %) -> %
    ++ -(x, y) gives subtraction
    ++ subtract the corresponding elements of the vector or point.
    ++ The type (vector or point) depends on the following table:
    ++ vector - vector = vector
    ++ point - vector = point
    ++ vector - point = invalid (but interpret as point)
    ++ point - point = invalid (but interpret as vector)
    ++ Although, subtracting two points is not strictly a valid operation,
    ++ we allow it here by interpreting the result as the vector distance
    ++ between the two points.
  _*: (s : DoubleFloat, x : %) -> %
    ++ *(s, x) gives scalar multiplication
    ++ Although, scalar multiplication is strictly only a valid operation
    ++ for vectors and not points, we allow it here because there are some
    ++ useful short cuts, for instance, to find the central point between
    ++ two points we can add them together and scalar multiply by 0.5.
  toPoint : (p : %) -> %
    ++ toPoint(p) returns a Point with the same coordinates as p
    ++ If p was originally a vector then that will be treated as the
    ++ distance from the origin.
  toVector : (p : %) -> %
    ++ toVector returns a Vector with the same coordinates as p
    ++ If p was originally a point then the vector will be the
    ++ distance from the origin.
  isPoint? : (p : %) -> Boolean
    ++ isPoint?(p) returns true if this represents a point as
    ++ opposed to a vector.
  isVector? : (p : %) -> Boolean
    ++ isVector? returns true if this represents a vector as
    ++ opposed to a point.
  inBounds? : (pt : %, mns : %, mxs : %) -> Boolean
    ++ inBounds?(pt, mns, mxs) returns true if pt is inside bounding box
    ++ where bounding box is specified by min and max.
  screenCoordX : (pt : %) -> DoubleFloat
    ++ screenCoordX(pt) returns the 'x'(left to right) screen coordinate.
  screenCoordY : (pt : %) -> DoubleFloat
    ++ screenCoordY(pt) returns the 'y'(bottom to top) screen coordinate.
  screenCoordZ : (pt : %) -> DoubleFloat
    ++ screenCoordZ(pt) returns the 'z'(out from screen) screen coordinate.
  screenCoords : (pt : %) -> List DoubleFloat
    ++ screenCoords(pt) returns screen coordinates as a list.
  extendedCoords : (pt : %) -> List DoubleFloat
    ++ extendedCoords(pt) returns extended coordinates as a list.
  coerce : (pt : %) -> OutputForm
    ++ output

 add

  -- Measure of how parallel two vectors are independent of the length
  -- of the vectors:
  -- 1.0 is completely parallel
  -- 0.0 is completely perpendicular
  -- returned value will be these or between these
  colinearity(x : %, y : %) : DoubleFloat == parallel(x, y)^2/_
            (parallel(x, x) * parallel(y, y))


  -- convenience constructors which allow us to construct using
  -- integer values
  sipnt(a : Integer, b : Integer) : % == spnt(a::DoubleFloat, b::DoubleFloat)
  sipnt(a : Integer, b : Integer, c : Integer) : % ==
      spnt(a::DoubleFloat, b::DoubleFloat, c::DoubleFloat)
  sivec(a : Integer, b : Integer) : % == svec(a::DoubleFloat, b::DoubleFloat)
  sivec(a : Integer, b : Integer, c : Integer) : % ==
      svec(a::DoubleFloat, b::DoubleFloat, c::DoubleFloat)

  -- returns the distance between the points p1 and p2
  -- there are 2 metrics associated with any SPointCategory
  -- the underlying space and the space that it is embedded in
  -- this is the distance in the underlying space.
  distance(p1 : %, p2 : %) : DoubleFloat ==
      sqrt(distanceSquared(p1, p2))

)if false

The first coding of points and vectors is Cartesian, that is n dimensions
are represented by n floating point numbers. Each number represents the
projection onto mutually perpendicular lines. An additional number is added
distinguish between points and vectors.
\section{SCartesian domain}
)endif

)abbrev domain SCRT SCartesian
++ Author: Martin Baker
++ Date Created: September 2010
++ Basic Operations: point, elt, setelt, copy, dimension, minIndex, maxIndex,
++ convert
++ Related Constructors:
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ http://www.euclideanspace.com/prog/scratchpad/mycode/graph/
++ Description: an implementation of PointCategory to represent points and
++ vectors in three dimensional coordinate space

SCartesian(n) : Exports == Implementation where
  -- Domains for points, subspaces and properties of components in
  -- a subspace
  n : PositiveInteger
  PI ==> PositiveInteger
  DF ==> DoubleFloat

  Exports ==> SPointCategory()

  Implementation ==> add

    -- a 2D point contains 2 floating point numbers, a 3D point
    -- contains 3 and so on.
    -- then an additional number is added
    -- 1@DF if % represents a point
    -- 0@DF if % represents a vector
    Rep := PrimitiveArray DF

    -- spnt(a, b) constructs a 2D point defined by a and b
    -- of type DF
    spnt(a : DF, b : DF) : % ==
      pt := new(n+1, 0$DF)$Rep
      pt.0 := a
      pt.1 := b
      pt.n := 1@DF
      pt

    -- spnt(a, b, c) constructs a 3D point defined by a, b and c
    -- of type DF
    spnt(a : DF, b : DF, c : DF) : % ==
      pt := new(n+1, 0$DF)$Rep
      pt.0 := a
      pt.1 := b
      if n>2 then pt.2 := c
      pt.n := 1@DF
      pt

    -- svec(a, b) constructs a 2D vector defined by a and b
    -- of type DF
    svec(a : DF, b : DF) : % ==
      pt := new(n+1, 0$DF)$Rep
      pt.0 := a
      pt.1 := b
      pt.n := 0@DF
      pt

    -- svec(a, b, c) constructs a 3D vector defined by a, b and c
    -- of type DF
    svec(a : DF, b : DF, c : DF) : % ==
      pt := new(n+1, 0$DF)$Rep
      pt.0 := a
      pt.1 := b
      if n>2 then pt.2 := c
      pt.n := 0@DF
      pt

    -- min(a, b) returns a point whose value in each dimension is
    -- the minimum. So if a has the smallest x value and b has the
    -- smallest y value then the result will combine these. This is
    -- useful for constructing boundary boxes around sets of points.
    min(a : %, b : %) : % ==
      pt := new(n+1, 0$DF)$Rep
      for i in 0..(n-1) repeat
        pt.i := min(a.i, b.i)
      pt.n := 1$DF
      pt

    -- max(a, b) returns a point whose value in each dimension is
    -- the maximum. So if a has the largest x value and b has the
    -- largest y value then the result will combine these. This is
    -- useful for constructing boundary boxes around sets of points.
    max(a : %, b : %) : % ==
      pt := new(n+1, 0$DF)$Rep
      for i in 0..(n-1) repeat
        pt.i := max(a.i, b.i)
      pt.n := 1$DF
      pt

    dimension(p) == n+1 -- dimension of base space

    -- Test for NaN (Not a Number)
    -- That is have we divided by zero or taken sqrt of negative number at
    -- some stage?
    -- Common Lisp does not support NaN ? So we need some sort of test for
    -- invalid numbers
    -- If not equal to itself then we assume that its not a valid number
    nan?(x : DF) : Boolean == x ~= x

    -- Is this a valid point? if any of its elements are not valid
    -- then return true
    -- Examples of invalid floating point numbers are when have we
    -- have divided a given element by zero or taken
    -- sqrt of negative number at some stage?
    -- Note: we can still represent a point at infinity and we can
    -- represent points using complex numbers, but we do this using
    -- projective or conformal space or by using the appropriate
    -- instance of SPointCategory, not by using invalid floating
    -- point numbers.
    Pnan?(p : %) : Boolean ==
      for i in 0..(n-1) repeat
        if nan?(p.i) then return true
      false

    -- This returns the length of the parallel component of two vectors
    -- in the case of Euclidean space this is the dot product of two
    -- vectors. We use this function to test the colinearity
    parallel(x : %, y : %) : DF == x.0 * y.0 + x.1 * y.1 + x.2 * y.2

    -- scale until length is 1
    unitVector(p : %) : % ==
      factor := 0::DF
      -- take sum of squares
      for i in 0..(n-1) repeat
        factor := factor + (p.i * p.i)
      factor := sqrt(factor)
      pt := new(n+1, 0$DF)$Rep
      for i in 0..(n-1) repeat
        pt.i := p.i/factor
      pt.n := 0@DF -- set to vector
      pt

    -- returns the distance between the points p1 and p2
    -- there are 2 metrics associated with any SPointCategory
    -- the underlying space and the space that it is embedded in
    -- this is the distance in the underlying space.
    distanceSquared(p1 : %, p2 : %) : DF ==
      factor := 0::DF
      for i in 0..(n-1) repeat
        factor := factor + (p1.i - p2.i)^2
      factor

    -- This returns a vector perpendicular to the two vectors
    -- in the case of 3D Euclidean space this is the cross product of the
    -- two vectors. We use this function to compute orthogonal
    perpendicular(x : %, y : %) : % ==
      pt := new(n+1, 0$DF)$Rep
      if n<3 then return sipnt(0, 0)
      pt.0 := x.1 * y.2 - x.2 * y.1
      pt.1 := x.2 * y.0 - x.0 * y.2
      pt.2 := x.0 * y.1 - x.1 * y.0
      pt.n := 0@DF -- set to vector
      pt

    -- scalar multiplication
    -- Although, scalar multiplication is strictly only a valid operation
    -- for vectors and not points, we allow it here because there are some
    -- useful short cuts, for instance, to find the central point between
    -- two points we can add them together and scalar multiply by 0.5.
    _*(s : DF, x : %) : % ==
      pt := new(n+1, 0$DF)$Rep
      for i in 0..(n-1) repeat
        pt.i := x.i * s
      pt.n := x.n
      pt

    -- addition
    -- add the corresponding elements of the vector or point.
    -- The type (vector or point) depends on the following table:
    -- vector + vector = vector
    -- point + vector = point
    -- point + point = invalid (but interpret as point)
    -- Although, adding two points is not strictly a valid operation,
    -- we allow it here by interpreting the point as a vector relative
    -- to the origin. We do that because there are some useful short
    -- cuts, for instance, to find the central point between two points
    -- we can add them together and scalar multiply by 0.5.
    _+(x : %, y : %) : % ==
      pt := new(n+1, 0$DF)$Rep
      for i in 0..(n-1) repeat
        pt.i := x.i + y.i
      -- set the type according to the table above
      pt.n := x.n + y.n
      if pt.n > 1::DF then pt.n := 1::DF
      pt

    -- subtraction
    -- subtract the corresponding elements of the vector or point.
    -- The type (vector or point) depends on the following table:
    -- vector - vector = vector
    -- point - vector = point
    -- vector - point = invalid (but interpret as point)
    -- point - point = invalid (but interpret as vector)
    -- Although, subtracting two points is not strictly a valid operation,
    -- we allow it here by interpreting the result as the vector distance
    -- between the two points.
    _-(x : %, y : %) : % ==
      pt := new(n+1, 0$DF)$Rep
      for i in 0..(n-1) repeat
        pt.i := x.i - y.i
      pt.n := x.n + y.n
      if pt.n > 1::DF then pt.n := 0::DF
      pt

    -- return true if param is inside bounding box
    -- where bounding box is specified by min and max
    inBounds?(pt : %, mns : %, mxs : %) : Boolean ==
      for i in 0..(n-1) repeat
        -- if pt less than minimum (left or top) then false
        if qelt(pt, i) < qelt(mns, i) then return false
        -- if pt greater than maximum (right or bottom) then false
        if qelt(pt, i) > qelt(mxs, i) then return false
      -- inside box so return true
      true

    -- returns the 'x'(left to right) screen coordinate
    screenCoordX(pt : %) : DF == pt.0

    -- returns the 'y'(bottom to top) screen coordinate
    screenCoordY(pt : %) : DF == pt.1

    -- returns the 'z'(out from screen) screen coordinate
    screenCoordZ(pt : %) : DF == if n>2 then pt.2 else 0@DF

    -- returns screen coordinates as a list
    screenCoords(pt : %) : List DF ==
      res : List DF := []
      for i in 0..(n-1) repeat
        res := concat(res, pt.i)
      res

    -- returns extended coordinates as a list
    extendedCoords(pt : %) : List DF == entries(pt)

    -- toPoint(p) returns a Point with the same coordinates as p
    -- If p was originally a vector then that will be treated as the
    -- distance from the origin.
    toPoint(p : %) ==
      pt := new(n+1, 0$DF)$Rep
      for i in 0..(n-1) repeat pt.i := p.i
      pt.n := 1$DF
      pt

    -- toVector returns a Vector with the same coordinates as p
    -- If p was originally a point then the vector will be the
    -- distance from the origin.
    toVector(p : %) ==
      pt := new(n+1, 0$DF)$Rep
      for i in 0..(n-1) repeat pt.i := p.i
      pt.n := 0$DF
      pt

    -- isPoint?(p) returns true if this represents a point as
    -- opposed to a vector.
    isPoint?(p : %) == p.n > 0.9::DF

    -- isVector? returns true if this represents a vector as
    -- opposed to a point.
    isVector?(p : %) == p.n < 0.1::DF

    -- start of SetCategory definitions --

    hash(s : %) : SingleInteger == 0$SingleInteger

    -- returns true if x equals y
    _=(x : %, y : %) : Boolean ==
      for i in 0..n+1 repeat
        if x.i ~= y.i then return false
      true

    -- returns true if x is not equal to y
    _~_=(x : %, y : %) : Boolean == not(x = y)

    -- output
    coerce(pt : %) : OutputForm ==
      s:Symbol := "proj"::Symbol
      if pt.n = 0@DF then s := "vec"::Symbol
      if pt.n = 1@DF then s := "pt"::Symbol
      eles : List OutputForm := [(pt.i)::OutputForm for i in 0..(n-1)]
      hconcat(s::OutputForm, paren(eles))$OutputForm

)if false

Another coding of points and vectors is Argand. This represents two
dimensional points and vectors on Argand plane.
\section{SArgand domain}
)endif

)abbrev domain SARGND SArgand
++ Author: Martin Baker
++ Date Created: September 2010
++ Basic Operations: point, elt, setelt, copy, dimension, minIndex, maxIndex,
++ convert
++ Related Constructors:
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ http://www.euclideanspace.com/prog/scratchpad/mycode/graph/
++ Description: an implementation of PointCategory to represent points and
++ vectors in an Argand plane

SArgand() : Exports == Implementation where
  ++ Domain for representing points, vectors, subspaces and properties
  ++ of components in a subspace as complex numbers
  DF ==> DoubleFloat
  C ==> Complex DF
  PI ==> PositiveInteger

  Exports == SPointCategory() with
    coerce : % -> C
    coerce : C -> %

  Implementation ==> add

    -- represented by real and imaginary parts of complex number
    Rep := Record(r : DF, i : DF)

    -- spnt(a, b) constructs a 2D point defined by a and b
    -- of type DF
    spnt(a : DF, b : DF) : % ==
      [a, b]

    -- spnt(a, b, c) constructs a 3D point defined by a, b and c
    -- of type DF
    spnt(a : DF, b : DF, c : DF) : % ==
      [a, b]

    -- svec(a, b) constructs a 2D vector defined by a and b
    -- of type DF
    svec(a : DF, b : DF) : % ==
      [a, b]

    -- svec(a, b, c) constructs a 3D vector defined by a, b and c
    -- of type DF
    svec(a : DF, b : DF, c : DF) : % ==
      [a, b]

    -- min(a, b) returns a point whose value in each dimension is
    -- the minimum. So if a has the smallest x value and b has the
    -- smallest y value then the result will combine these. This is
    -- useful for constructing boundary boxes around sets of points.
    min(a : %, b : %) : % ==
      re : DF := min(a.r, b.r)
      im : DF := min(a.i, b.i)
      [re, im]

    -- max(a, b) returns a point whose value in each dimension is
    -- the maximum. So if a has the largest x value and b has the
    -- largest y value then the result will combine these. This is
    -- useful for constructing boundary boxes around sets of points.
    max(a : %, b : %) : % ==
      re : DF := max(a.r, b.r)
      im : DF := max(a.i, b.i)
      [re, im]

    dimension p == 2 -- dimension of base space

    -- Test for NaN (Not a Number)
    -- That is have we divided by zero or taken sqrt of negative number at
    -- some stage? Common Lisp does not support NaN ? So we need some sort
    -- of test for invalid numbers
    -- If not equal to itself then we assume that its not a valid number
    nan?(x : DF) : Boolean == x ~= x

    -- Is this a valid point? if any of its elements are not valid
    -- then return true
    -- Examples of invalid floating point numbers are when have we
    -- have divided a given element by zero or taken
    -- sqrt of negative number at some stage?
    -- Note: we can still represent a point at infinity and we can
    -- represent points using complex numbers, but we do this using
    -- projective or conformal space or by using the appropriate
    -- instance of SPointCategory, not by using invalid floating
    -- point numbers.
    Pnan?(p : %) : Boolean ==
      nan?(p.r) => true
      nan?(p.i) => true
      false

    -- scale until length is 1
    unitVector(p : %) : % ==
      factor := sqrt(p.r * p.r + p.i * p.i)
      [p.r/factor, p.i/factor]

    -- returns the distance between the points p1 and p2
    -- there are 2 metrics associated with any SPointCategory
    -- the underlying space and the space that it is embedded in
    -- this is the distance in the underlying space.
    distanceSquared(p1 : %, p2 : %) : DF ==
      (p1.r - p2.r)^2 + (p1.i - p2.i)^2

    -- This returns the length of the parallel component of two vectors
    -- in the case of Euclidean space this is the dot product of two
    -- vectors. We use this function to test the colinearity
    parallel(x : %, y : %) : DF == x.r * y.r + x.i * y.i

    -- This returns a vector perpendicular to the two vectors
    -- in the case of 3D Euclidean space this is the cross product of the
    -- two vectors. We use this function to compute orthogonal
    -- In this case set to arbitrary value
    perpendicular(x : %, y : %) : % ==
      [0@DF, 1@DF]

    -- scalar multiplication
    _*(s : DF, x : %) : % ==
      [x.r * s, x.i * s]

    -- addition
    _+(x : %, y : %) : % ==
      [x.r + y.r, x.i + y.i]

    -- addition
    _-(x : %, y : %) : % ==
      [x.r - y.r, x.i - y.i]

    -- return true if param is inside bounding box
    -- where bounding box is specified by min and max
    inBounds?(pt : %, mns : %, mxs : %) : Boolean ==
      -- left of box - check minimum x
      pt.r < mns.r => false
      -- right of box - check maximum x
      pt.r > mxs.r => false
      -- below box - check minimum y
      pt.i < mns.i => false
      -- above box - check maximum y
      pt.i > mxs.i => false
      -- inside box so return true
      true

    -- returns the 'x'(left to right) screen coordinate
    screenCoordX(pt : %) : DF == pt.r

    -- returns the 'y'(bottom to top) screen coordinate
    screenCoordY(pt : %) : DF == pt.i

    -- returns the 'z'(out from screen) screen coordinate
    screenCoordZ(pt : %) : DF == 0@DF

    -- returns screen coordinates as a list
    screenCoords(pt : %) : List DF == [pt.r, pt.i]

    -- returns extended coordinates as a list
    extendedCoords(pt : %) : List DF == [pt.r, pt.i]

    -- toPoint(p) returns a Point with the same coordinates as p
    -- If p was originally a vector then that will be treated as the
    -- distance from the origin.
    toPoint(p : %) ==
      [p.r, p.i]

    -- toVector returns a Vector with the same coordinates as p
    -- If p was originally a point then the vector will be the
    -- distance from the origin.
    toVector(p : %) ==
      [p.r, p.i]

    -- isPoint?(p) returns true if this represents a point as
    -- opposed to a vector. Since there is no distinction in
    -- Argand plane always return true.
    isPoint?(p : %) == true

    -- isVector? returns true if this represents a vector as
    -- opposed to a point. Since there is no distinction in
    -- Argand plane always return true.
    isVector?(p : %) == true

    coerce(me : %) : C ==
      complex(me.r, me.i)

    coerce(cmpx : C) : % ==
      [real(cmpx), imag(cmpx)]

    -- start of SetCategory definitions --

    hash(s : %) : SingleInteger == 0$SingleInteger

    -- returns true if x equals y
    _=(x : %, y : %) : Boolean ==
      if x.r ~= y.r then return false
      if x.i ~= y.i then return false
      true

    -- returns true if x is not equal to y
    _~_=(x : %, y : %) : Boolean == not(x = y)

    -- output
    coerce(pt : %) : OutputForm ==
      pt.i < 0::DF =>
        ((pt.r)::OutputForm) - _
             (hconcat('%i::OutputForm, (- pt.i)::OutputForm))
      ((pt.r)::OutputForm) + _
           (hconcat('%i::OutputForm, (pt.i)::OutputForm))

)if false

A coding of points and vectors in conformal space.
\section{SConformal domain}
)endif

)abbrev domain SCONF SConformal
++ Author: Martin Baker
++ Date Created: September 2010
++ Basic Operations: point, elt, setelt, copy, dimension, minIndex, maxIndex,
++ convert
++ Related Constructors:
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ http://www.euclideanspace.com/prog/scratchpad/mycode/graph/
++ Description: an implementation of PointCategory to represent points and
++ vectors in n+2 dimensional conformal space based on n dimensional
++ Euclidean space.

SConformal(n) : Exports == Implementation where
  ++ Domain for representing points, vectors, subspaces and properties
  ++ of conformal space
  n : PositiveInteger
    ++ number of base dimensions (excluding point at infinity and point
    ++ at origin)
  PI ==> PositiveInteger
  DF ==> DoubleFloat
  --
  -- What I would like to do is be able to coerce to a CliffordAlgebra
  -- with a specific signature representing conformal space
  -- however FriCAS does not allow a specific matrix to be
  -- created here.
  --bLin ==> squareMatrix([[1@DF, 0@DF, 0@DF, 0@DF], _
  --                     [0@DF, 1@DF, 0@DF, 0@DF], _
  --                     [0@DF, 0@DF, 1@DF, 0@DF], _
  --                     [0@DF, 0@DF, 0@DF, 1@DF]])
  --CA ==> CliffordAlgebra(4, DF, bLin)

  Exports == SPointCategory() with
    normalisePoint : (pt : %) -> %
      ++ normalised point has scalar value of one, if this is not the
      ++ case then scale values

    --coerce: % -> CA
    --coerce: CA -> %

  Implementation ==> add

    import from OutputForm

    dim := 2^(n+2)
      ++ the total dimension of all the grades from scalar up to
      ++ pseudoscalar

    z : %

    -- count of floating point numbers to represent a Clifford algebra
    -- number given by dim
    Rep := PrimitiveArray DF

    New ==> new(dim, 0$DF)$Rep

    -- spnt(a, b) constructs a 2D point defined by a and b
    -- of type DF
    -- z.1 is point at zero which is set to -1
    -- z.2 is point at infinity which is set to a^2 + b^2
    -- z.4 is a
    -- z.8 is b
    spnt(a : DF, b : DF) : % ==
      inf : DF := (a*a + b*b)*0.5::DF
      (z := New; z.1 := -1@DF; z.2 := inf; z.4 := a; z.8 := b; z)

    -- spnt(a, b, c) constructs a 3D point defined by a, b and c
    -- of type DF
    -- z.1 is point at zero which is set to -1
    -- z.2 is point at infinity which is set to a^2 + b^2
    -- z.4 is a
    -- z.8 is b
    -- z.16 is c
    spnt(a : DF, b : DF, c : DF) : % ==
      inf : DF := (a*a + b*b + c*c)*0.5::DF
      z := New; z.1 := -1@DF; z.2 := inf
      z.4 := a; z.8 := b
      if n>2 then z.16 := c
      z

    -- svec(a, b) constructs a 2D vector defined by a and b
    -- of type DF
    svec(a : DF, b : DF) : % ==
      (z := New; z.1 := 0@DF; z.2 := 0@DF; z.4 := a; z.8 := b; z)

    -- svec(a, b, c) constructs a 3D vector defined by a, b and c
    -- of type DF
    svec(a : DF, b : DF, c : DF) : % ==
      z := New; z.1 := 0@DF; z.2 := 0@DF
      z.4 := a; z.8 := b
      if n>2 then z.16 := c
      z

    -- min(a, b) returns a point whose value in each dimension is
    -- the minimum. So if a has the smallest x value and b has the
    -- smallest y value then the result will combine these. This is
    -- useful for constructing boundary boxes around sets of points.
    min(a : %, b : %) : % ==
      pt := New
      pt.8 := min(a.8, b.8)
      pt.4 := min(a.4, b.4)
      pt.2 := (pt.4*pt.4 + pt.8*pt.8)*0.5::DF
      pt.1 := -1@DF
      pt

    -- max(a, b) returns a point whose value in each dimension is
    -- the maximum. So if a has the largest x value and b has the
    -- largest y value then the result will combine these. This is
    -- useful for constructing boundary boxes around sets of points.
    max(a : %, b : %) : % ==
      pt := New
      pt.8 := max(a.8, b.8)
      pt.4 := max(a.4, b.4)
      pt.2 := (pt.4*pt.4 + pt.8*pt.8)*0.5::DF
      pt.1 := -1@DF
      pt

    dimension(p)== 2 -- dimension of base space

    -- Test for NaN (Not a Number)
    -- That is have we divided by zero or taken sqrt of negative number at
    -- some stage?
    -- Common Lisp does not support NaN ? So we need some sort of test for
    -- invalid numbers
    -- If not equal to itself then we assume that its not a valid number
    nan?(x : DF) : Boolean == x ~= x

    -- Is this a valid point? if any of its elements are not valid
    -- then return true
    -- Examples of invalid floating point numbers are when have we
    -- have divided a given element by zero or taken
    -- sqrt of negative number at some stage?
    -- Note: we can still represent a point at infinity and we can
    -- represent points using complex numbers, but we do this using
    -- projective or conformal space or by using the appropriate
    -- instance of SPointCategory, not by using invalid floating
    -- point numbers.
    Pnan?(p : %) : Boolean ==
      for i in 0..(dim-1) repeat
        if nan?(p.i) then return true
      false

    -- scale until length is 1
    unitVector(p : %) : % ==
      factor := sqrt(p.4*p.4 + p.8*p.8)
      pt := New
      pt.4 := p.4/factor
      pt.8 := p.8/factor
      pt.2 := (pt.4*pt.4 + pt.8*pt.8)*0.5::DF
      pt.1 := -1@DF
      pt

    -- returns the distance between the points p1 and p2
    -- there are 2 metrics associated with any SPointCategory
    -- the underlying space and the space that it is embedded in
    -- this is the distance in the underlying space.
    distanceSquared(p1 : %, p2 : %) : DF ==
      1@DF

    -- This returns the length of the parallel component of two vectors
    -- in the case of Euclidean space this is the dot product of two
    -- vectors. We use this function to test the colinearity
    parallel(x : %, y : %) : DF == x.6 * y.6 + x.10 * y.10

    -- This returns a vector perpendicular to the two vectors
    -- in the case of 3D Euclidean space this is the cross product of the
    -- two vectors. We use this function to compute orthogonal
    perpendicular(x : %, y : %) : % ==
      pt := New
      pt.4 := 0@DF
      pt.8 := 1@DF
      pt.2 := (pt.4*pt.4 + pt.8*pt.8)*0.5::DF
      pt.1 := -1@DF
      pt

    -- scalar multiplication
    _*(s : DF, x : %) : % ==
      pt := New
      pt.4 := x.4 * s
      pt.8 := x.8 * s
      pt.2 := (pt.4*pt.4 + pt.8*pt.8)*0.5::DF
      pt.1 := -1@DF
      pt

    -- addition
    _+(x : %, y : %) : % ==
      pt := New
      pt.4 := x.4 + y.4
      pt.8 := x.8 + y.8
      pt.2 := (pt.4*pt.4 + pt.8*pt.8)*0.5::DF
      pt.1 := -1@DF
      pt

    -- subtraction
    _-(x : %, y : %) : % ==
      pt := New
      pt.4 := x.4 - y.4
      pt.8 := x.8 - y.8
      pt.2 := (pt.4*pt.4 + pt.8*pt.8)*0.5::DF
      pt.1 := -1@DF
      pt

    -- return true if param is inside bounding box
    -- where bounding box is specified by min and max
    inBounds?(pt : %, mns : %, mxs : %) : Boolean ==
     -- left of box - check minimum x
      qelt(pt, 4) < qelt(mns, 4) => false
      -- right of box - check maximum x
      qelt(pt, 4) > qelt(mxs, 4) => false
      -- below box - check minimum y
      qelt(pt, 8) < qelt(mns, 8) => false
      -- above box - check maximum y
      qelt(pt, 8) > qelt(mxs, 8) => false
      -- inside box so return true
      true

   -- returns the 'x'(left to right) screen coordinate
    screenCoordX(pt : %) : DF == pt.4

    -- returns the 'y'(bottom to top) screen coordinate
    screenCoordY(pt : %) : DF == pt.8

    -- returns the 'z'(out from screen) screen coordinate
    screenCoordZ(pt : %) : DF == if n>2 then pt.16 else 0@DF

    -- returns screen coordinates as a list
    screenCoords(pt : %) : List DF ==
      if n>2 then return [pt.4, pt.8, pt.16]
      [pt.4, pt.8]

    -- returns extended coordinates as a list
    extendedCoords(pt : %) : List DF == entries(pt)

    -- normalalised point has scalar value of one, if this is not the
    -- case then scale values
    normalisePoint(pt : %) : % ==
      scaleFactor : DF := pt.1
      if scaleFactor < 0.01::DF and scaleFactor > -0.01::DF then
        print message("can't apply normalisePoint to a vector")
        return pt
      if scaleFactor < -0.99::DF and scaleFactor > -1.01::DF then
        return pt
      pt2 := New
      infin := 0@DF
      for x in 1..n repeat
        index := 2^(x+1)
        pt2.index := pt.index / (-scaleFactor)
        infin := infin + ((pt2.index)*(pt2.index))*0.5::DF
      pt2.1 := -1@DF
      pt2.2 := infin
      pt2


    -- toPoint(p) returns a Point with the same coordinates as p
    -- If p was originally a vector then that will be treated as the
    -- distance from the origin.
    toPoint(p : %) ==
      pt := New
      for i in 0..(dim-1) repeat pt.i := p.i
      pt.1 := -1$DF
      pt

    -- toVector returns a Vector with the same coordinates as p
    -- If p was originally a point then the vector will be the
    -- distance from the origin.
    toVector(p : %) ==
      pt := New
      for i in 0..(dim-1) repeat pt.i := p.i
      pt.1 := 0$DF
      pt

    -- isPoint?(p) returns true if this represents a point as
    -- opposed to a vector.
    isPoint?(p : %) == p.1 < -0.1::DF

    -- isVector? returns true if this represents a vector as
    -- opposed to a point.
    isVector?(p : %) == p.1 > -0.9::DF

--    coerce(me: %): C ==
--      multivector(extendedCoords(me))$C

--    coerce(cmpx: C): % ==
--      [real(cmpx), imag(cmpx)]

    -- start of SetCategory definitions --

    hash(s : %) : SingleInteger == 0$SingleInteger

    -- returns true if x equals y
    _=(x : %, y : %) : Boolean ==
      for i in 0..(dim-1) repeat
        if x.i ~= y.i then return false
      true

    -- returns true if x is not equal to y
    _~_=(x : %, y : %) : Boolean == not(x = y)

    -- output
    coerce(pt : %) : OutputForm ==
      eles : List OutputForm := [(pt.i)::OutputForm for i in 0..(dim-1)]
      paren(eles)

)if false

\section{SceneIFS domain}
Many of the functions here are from NumericTubePlot and related
packages. However I needed them to be defined over SPointCategory
so I adapted them here.
)endif

)abbrev domain SCIFS SceneIFS
++ Author: Martin Baker (some parts adapted from NumericTubePlot by
++         Clifton J. Williamson)
++ Date Created: September 2010
++ Basic Operations:
++ Related Constructors:
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ Description: Generates various surfaces (2 dimensional subsets
++ of 3D space). When 1 dimensional subspace (a line) needs to be
++ represented in a 3D editor then we convert the line into a tube.
++ Many of the functions here are from NumericTubePlot and related
++ packages. However I needed them to be defined over SPointCategory
++ so I adapted them here.
SceneIFS(PT) : Exports == Implementation where
 PT : SPointCategory

 NNI==> NonNegativeInteger
 DF==> DoubleFloat
 SEG ==> Segment DF
 Triad ==> Record(tang : PT, norm : PT, bin : PT)
 Clipd ==> Record(source : DF -> PT, _
                   ranges : List SEG, _
                   knots : List DF, _
                   points : List PT)

 Exports== with
  smesh : (ptin : List List PT, clos : Boolean) -> %
    ++ smesh(ptin, clos) constructs a ifs representation of a mesh from
    ++ a 2 dimensional array of points
  singleFace : (ptin : List PT) -> %
    ++ singleFace(ptin) constructs a ifs representation of a face from
    ++ an array of points
  subdivide : (me : %) -> %
    ++ subdivide(me) for each face calculate a new point in the centre
    ++ of the face, then build a new set of faces which include the new
    ++ point.
  sierpinskiDivide : (me : %, level : NNI) -> %
    ++ sierpinskiDivide(me, level) for each edge of each face calculate
    ++ a new point in the centre of the edge, then build a new set of
    ++ faces which include the new points.
  stube : (functions : List Clipd, r : DF, n : Integer) -> %
    ++ stube(functions: List Clipd, r, n) creates a tube of radius r around
    ++ the curve functions.
  curveLoops : (pts1 : List PT, r : DF, nn : Integer) ->List List PT
    ++ curveLoops(pts1, r, nn) generates ifs from line given by pts1
  indexes : (me : %) -> List List NNI
    ++ indexes(me) returns indexes into pointList(me)
  pointList : (me : %) -> List PT
    ++ pointList(me) returns points at vertices of this mesh.
  coerce : (me : %) -> OutputForm
    ++ coerce(me) list this instance to the command line

 Implementation == add
  Rep := Record(inx : List List NNI, pts : List PT)

  -- returns indexes
  indexes(me : %) : List List NNI == me.inx

  -- returns points
  pointList(me : %) : List PT == me.pts

  meshIndex(i : Integer, j : Integer, c : Integer) : NNI == ((j*(c)) + i)::NNI

  -- construct a ifs representation of a mesh from a 2 dimensional
  -- array of points
  smesh(ptin : List List PT, clos : Boolean) : % ==
   pt2 : List PT := []
   in2 : List List NNI := []
   colLength := #(first ptin)
   j := 0
   for lpt in ptin repeat
     i := 0
     for p in lpt repeat
       pt2 := concat(pt2, p)
       if i > 0 and j > 0 then
         thisLine : List NNI := [meshIndex(i-1, j-1, colLength), _
                               meshIndex(i, j-1, colLength), _
                               meshIndex(i, j, colLength), _
                               meshIndex(i-1, j, colLength)]
         in2 := concat(in2, thisLine)
       i := i+ 1
     if clos and j > 0 then
       thisLine : List NNI := [meshIndex(i-1, j-1, colLength), _
                               meshIndex(0, j-1, colLength), _
                               meshIndex(0, j, colLength), _
                               meshIndex(i-1, j, colLength)]
       in2 := concat(in2, thisLine)
     j := j+1
   [in2, pt2]

  -- construct a ifs representation of a face from an array of points
  singleFace(ptin : List PT) : % ==
   pt2 : List PT := ptin
   --in2: List List NNI := [concat([i for i in 0..(#ptin - 1)], 0)]
   in2 : List List NNI := [[i for i in 0..(#ptin - 1)]]
   [in2, pt2]

  -- for each face calculate a new point in the centre of the face, then
  -- build a new set of faces which include the new point.
  subdivide(me : %) : % ==
    indexSize : NNI := #(me.pts)
    pt2 : List PT := [(me.pts).i for i in 1..indexSize]
    in2 : List List NNI := []
    for faceIx in me.inx repeat
      -- create new point as the average value of all the points in the face
      newPt := sipnt(0, 0, 0)
      for ptIx in faceIx repeat
        newPt := newPt + pt2.(ptIx+1)
      newPt := (1 / #faceIx)::DF * newPt
      -- add new point to list of points
      pt2 := concat(pt2, newPt)
      indexPos := 1
      -- build face indexes
      for ptIx in faceIx repeat
        newFace : List NNI := [faceIx.j for j in 1..(#(faceIx))]
        newFace.indexPos := indexSize
        indexPos := indexPos + 1
        in2 := concat(in2, newFace)
      indexSize := indexSize + 1
    [in2, pt2]

  -- for each edge of each face calculate a new point in the centre of
  -- the edge, then build a new set of faces which include the new points.
  sierpinskiDivide(me : %, level : NNI) : % ==
    indexSize : NNI := #(me.pts)
    pt2 : List PT := [(me.pts).i for i in 1..indexSize]
    in2 : List List NNI := []
    for faceIx in me.inx repeat
      -- create new point as the average value of edge ends
      for offset in 1..#faceIx repeat
        ix1 : NNI := offset
        ix2 : NNI := offset+1
        if ix2 > #faceIx then ix2 := 1
        newPt := pt2.((faceIx.ix1) + 1) + pt2.((faceIx.ix2) + 1)
        newPt := 0.5::DF * newPt
        pt2 := concat(pt2, newPt)
      index1 : NNI := ((#faceIx) - 1)::NNI
      index2 : NNI := 0
      -- build face indexes
      for offset in 1..#faceIx repeat
        ix3 := indexSize+index1
        ix4 := indexSize+index2
        newFace : List NNI := [faceIx.offset, ix3, ix4]
        index1 := index1 + 1
        if index1 = #faceIx then index1 := 0
        index2 := index2 + 1
        if index2 = #faceIx then index2 := 0
        in2 := concat(in2, newFace)
      indexSize := indexSize + #faceIx
    if level < 1 then return [in2, pt2]
    sierpinskiDivide([in2, pt2], (level-1)::NNI)

  -- listBranches(c) returns a list of lists of points, representing the
  -- branches of the curve c.
  listBranches(functions : List Clipd) : List List PT ==
    outList : List List PT := []
    for curve in functions repeat
      outList := concat(curve.points, outList)
    outList

  LINMAX := convert(0.995)@DF
  XHAT := sipnt(1, 0, 0)
  YHAT := sipnt(0, 1, 0)
  PREV0 := sipnt(1, 1, 0)
  PREV := PREV0

  orthog(x : PT, y : PT) : PT ==
      if colinearity(x, y) > LINMAX then y := PREV
      if colinearity(x, y) > LINMAX then
        y := (colinearity(x, XHAT) < LINMAX => XHAT; YHAT)
      a : DF := -parallel(x, y)/parallel(x, x)
      PREV := a*x + y

  poTriad(pl : PT, po : PT, pr : PT) : Triad ==
      -- use divided difference for t.
      t := unitVector(pr - pl)
      -- compute n as orthogonal to t in plane containing po.
      pol := pl - po
      n := unitVector orthog(t, pol)
      [t, n, perpendicular(t, n)]

  curveTriads(l : List PT) : List Triad ==
      (k := #l) < 2 => error "Need at least 2 points to specify a curve"
      PREV := PREV0
      k = 2 =>
        t := unitVector(second l - first l)
        n := unitVector(t - XHAT)
        b := perpendicular(t, n)
        triad : Triad := [t, n, b]
        [triad, triad]
      -- compute interior triads using divided differences
      midtriads : List Triad :=
        [poTriad(pl, po, pr) for pl in l for po in rest l _
               for pr in rest rest l]
      -- compute first triad using a forward difference
      x := first midtriads
      t := unitVector(second l - first l)
      n := unitVector orthog(t, x.norm)
      begtriad : Triad := [t, n, perpendicular(t, n)]
      -- compute last triad using a backward difference
      x := last midtriads
      -- efficiency!!
      t := unitVector(l.k - l.(k-1))
      n := unitVector orthog(t, x.norm)
      endtriad : Triad := [t, n, perpendicular(t, n)]
      concat(begtriad, concat(midtriads, endtriad))

  -- cosSinInfo(n) returns the list of lists of values for n, in the
  -- form: [[cos(n - 1) a, sin(n - 1) a], ..., [cos 2 a, sin 2 a], [cos a, sin a]]
  -- where a = 2 pi/n. Note: n should be greater than 2.
  cosSinInfo(n : Integer) : List List DF ==
      ans : List List DF := []
      theta : DF := 2 * pi()/n
      for i in 1..(n-1) repeat --!! make more efficient
        angle := i * theta
        ans := concat([cos angle, sin angle], ans)
      ans

  -- loopPoints(p, n, b, r, lls) creates and returns a list of points
  -- which form the loop with radius r, around the centre point
  -- indicated by the point p, with the principal normal vector of
  -- the space curve at point p given by the point(vector) n, and the
  -- binormal vector given by the point(vector) b, and a list of lists,
  -- lls, which is the \spadfun{cosSinInfo} of the number of points
  -- defining the loop.
  loopPoints(ctr : PT, pNorm : PT, bNorm : PT, rad : DF, cosSin : List List DF) : List PT ==
      ans : List PT := []
      while not(empty?(cosSin)) repeat
        cossin := first cosSin; cos := first cossin; sin := second cossin
        ans := cons(ctr + rad * (cos * pNorm + sin * bNorm), ans)
        cosSin := rest cosSin
      pt := ctr + rad * pNorm
      concat(pt, concat(ans, pt))

  curveLoops(pts1 : List PT, r : DF, nn : Integer) : List List PT ==
      triads := curveTriads pts1
      cosSin := cosSinInfo nn
      loops : List List PT := []
      for pt in pts1 for triad in triads repeat
        n := triad.norm; b := triad.bin
        loops := concat(loopPoints(pt, n, b, r, cosSin), loops)
      reverse! loops

  -- stube(functions, r, n) creates a tube of radius r around the
  -- curve functions.
  stube(functions : List Clipd, r : DF, n : Integer) : % ==
    n < 3 => error "tube: n should be at least 3"
    brans := listBranches functions
    loops : List List PT := []
    for bran in brans repeat
      loops := concat(loops, curveLoops(bran, r, n))
    smesh(loops, true)

  -- list this instance to the command line
  coerce(me : %) : OutputForm ==
    r : OutputForm := empty()
    for p in me.pts repeat
      r := hconcat(r, p::OutputForm)
      r := hconcat(r, message(" "))
    res : List OutputForm := [r] -- string to hold result
    for faceIx in me.inx repeat
      r := empty()
      for pIx in faceIx repeat
        r := hconcat(r, pIx::OutputForm)
        r := hconcat(r, message(" "))
      res := concat(res, r)
    vconcat(res)

)if false

\section{SceneNamedPoints}
The aim of the 'named points' node and associated domain is to provide
better support for drawing graphs (that is 'graphs' as in graph theory)
and diagrams of trees, latices and category theory arrow diagrams.
In other words, diagrams with named nodes and arrows or lines between
these nodes.

These diagrams can already be drawn, using this graphics framework
without this feature, however this requires that the coordinates
are supplied for each end of each line or arrow. If we want to stop
the arrow short of the nodes, to avoid overwriting the node name, then
the new coordinates would have to be recalculated using boilerplate
code.

So the aim is to separate the mathematical structure of the diagram
from the actual coordinates and issues of making the diagram more human
readable. Eventually we might also have some intelligent code that
places the node positions to minimise crossing the arrows and so on.

For example, imagine we wanted to add support for graph theory in
FriCAS (seems like it would be an interesting project) then we
could implement a GraphTheory domain and it might contain a
'draw' function to draw the graph to a SVG file. This code will have
a set of node names, say "a","b"... and the 'draw' function will
draw the appropriate arrows between them, however we don't want the
code to be cluttered up with specific coordinates so we put all that
in a 'named points' node (here called np). We can then then draw the
arrows thus:
\begin{verbatim}
addSceneArrow(np,"a","b"....
\end{verbatim}
\section{SceneNamedPoints Tutorial}
We can create a 'named points' domain to hold a list of names and
the points they map to:
\begin{verbatim}
(1) -> PT ==> SCartesian(2)
                                                             Type : Void

(2) -> ptin : List PT := [sipnt(100, 100)$PT, _
                                   sipnt(100, 200)$PT, sipnt(200, 200)$PT]
   (2)  [pt(100.0, 100.0), pt(100.0, 200.0), pt(200.0, 200.0)]
                                              Type : List(SCartesian(2))

(3) -> nmin: List String := ["a","b","c"]
   (3)  ["a","b","c"]
                                                     Type : List(String)

(4) -> namedPts : SceneNamedPoints PT := namedPoints(ptin, nmin)
   (4)  pt:"a":pt(100.0,100.0),"b":pt(100.0,200.0),"c":pt(200.0,200.0)
                                  Type : SceneNamedPoints(SCartesian(2))
\end{verbatim}
We can then use this 'named points' domain in the scene like this:
\begin{verbatim}
(5) -> view := boxBoundary(sipnt(0, 0)$PT, sipnt(1000, 1000)$PT)
   (5)  bound box : pt(0.0, 0.0)->pt(1000.0, 1000.0)
                                         Type : SBoundary(SCartesian(2))

(6) -> sc := createSceneRoot(view)$Scene(PT)
   (6)  scene root bound box : pt(0.0, 0.0)->pt(1000.0, 1000.0) #ch = 0
                                             Type : Scene(SCartesian(2))

(7) -> np := addSceneNamedPoints(sc, namedPts)$Scene(PT)
   (7)
   scene namedpoints n=
     pt:"a":pt(100.0,100.0),"b":pt(100.0,200.0),"c":pt(200.0,200.0)
      #ch = 0
                                             Type : Scene(SCartesian(2))

(8) -> nt := addSceneText(np,["a","b","c"],12::NNI,sipnt(50,50)$PT)
   (8)  scene text="a" sz = 12 p = pt(50.0,50.0) npt=["a","b","c"] #ch = 0
                                             Type : Scene(SCartesian(2))

(9) -> a1 := addSceneArrow(np,"a","b",_
                             sipnt(50,50)$PT,"fixed"::Symbol,2.0::DoubleFloat)
   (9)  scene arrows str="a" ena="b" ofs = pt(50.0,50.0) md = fixed sz = 2.0 #ch = 0
                                             Type : Scene(SCartesian(2))

(10) -> a2 := addSceneArrow(np,"b","c",_
                             sipnt(50,50)$PT,"fixed"::Symbol,2.0::DoubleFloat)
   (10)  scene arrows str="b" ena="c" ofs = pt(50.0,50.0) md = fixed sz = 2.0 #ch = 0
                                             Type : Scene(SCartesian(2))

(11) -> writeSvg(sc,"testGraph/examplenamed.svg")
                                                                   Type : Void
\end{verbatim}
Further information here:
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/graph/tutorial/named/}
\section{domain SceneNamedPoints}
)endif

)abbrev domain SCNP SceneNamedPoints
++ Author: Martin Baker
++ Date Created: July 2011
++ Basic Operations:
++ Related Constructors:
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ Description: Associates points or nodes in Scene structure with names
++ (or a hierarchy of names).
++ The aim is, where a point or node is used in several different
++ constructions, it only needs to be specified once.
SceneNamedPoints(PT) : Exports == Implementation where
 PT : SPointCategory

 NNI==> NonNegativeInteger

 Exports== SetCategory with
  namedPoints : (ptin : List PT, nmin : List String) -> %
    ++ namedPoints(ptin, nmin) constructs a list of named points.
  namedPoints : (scin : List Scene(PT), nmin : List String) -> %
    ++ namedPoints(scin, nmin) constructs a list of named nodes.
  namedBranch : (chin : List %, nmin : List String) -> %
    ++ namedBranch(chin, nmin) constructs a branch in the tree structure
  isPointLeaf? : (n : %) -> Boolean
    ++ isPointLeaf?(n) returns true if this is a pt node, that is,
    ++ if this node contains points
  isNodeLeaf? : (n : %) -> Boolean
    ++ isNodeLeaf?(n) returns true if this is a leaf which holds
    ++ Scene nodes.
  isNodeBranch? : (n : %) -> Boolean
    ++ isNodeBranch?(n) returns true if this is a branch.
  getNames : (n : %) -> List String
    ++ getNames(n) returns a list of names for this node
  findPoint : (n : %, ptName : String) -> PT
    ++ findPoint(n, ptName) find the point associated with a given
    ++ string.
  addPoints! : (n : %, ptName : String, pts : %) -> %
    ++ addPoints!(n, ptName, pts) adds a points node.
  addNode! : (n : %, ptName : String, sc : Scene(PT)) -> %
    ++ addNode!(n, ptName, sc) adds a scene node.
  findNode : (n : %, ptName : String) -> Scene(PT)
    ++ findNode(n, ptName) finds a scene node
  toString : (n : %) -> String
    ++ toString(n) returns string representation for debug purposes.

 Implementation == add

  math_to_string ==> mathObject2String$Lisp

  Rep := Union(_
         pt : Record(listPT : List PT, listNM : List String), _
         nd : Record(listND : List Scene(PT), listNM : List String), _
         br : Record(ch : List %, listHNM : List String)_
         )
    ++ this is a tree structure where the leaves are either:
    ++ * lists of named points.
    ++ * lists of named nodes in Scene structure.
    ++ The branches are also named so a point or node can be
    ++ determined from a hierarchy of names.

  import from Record(ch : List %, listHNM : List String)
  import from Record(listND : List Scene(PT), listNM : List String)

  -- construct a list of named points
  namedPoints(ptin : List PT, nmin : List String) : % ==
   [[ptin, nmin]]

  -- construct a list of named nodes
  namedPoints(scin : List Scene(PT), nmin : List String) : % ==
   [[scin, nmin]]

  -- construct a branch in the tree structure
  namedBranch(chin : List %, nmin : List String) : % ==
   [[chin, nmin]]

  -- return true if this is a pt node, that is,
  -- if this node contains points
  isPointLeaf?(n : %) : Boolean == n case pt

  -- return true if this is a leaf which holds Scene nodes.
  isNodeLeaf?(n : %) : Boolean == n case nd

  -- return true if this is a leaf which holds Scene nodes.
  isNodeBranch?(n : %) : Boolean == n case br

  -- return list of names for this node
  getNames(n : %) : List String ==
    if n case pt then return n.pt.listNM
    n.br.listHNM

  -- find the point associated with a given string
  findPoint(n : %, ptName : String) : PT ==
    if isNodeBranch?(n) then
      c : List(%) := n.br.ch
      if c = [] then -- this is an empty branch node
        return sipnt(0, 0)
      -- if we get here then this is a non-empty branch node
      fst : % := first(c)
      if isNodeLeaf?(fst) then return sipnt(0, 0)
      -- this is point node
      return findPoint(fst, ptName)
    for sNum in 1..#(n.pt.listNM) repeat
      s : String := n.pt.listNM.sNum
      if s = ptName then return n.pt.listPT.sNum
    sipnt(0, 0)

  -- adds a points node
  addPoints!(n : %, ptName : String, pts : %) : % ==
    if not isNodeBranch?(n) then
      error "can only add points to branch"
    n.br.ch := concat(n.br.ch, pts)
    n.br.listHNM := concat(n.br.listHNM, ptName)
    pts

  -- adds a scene node
  addNode!(n : %, ptName : String, sc : Scene(PT)) : % ==
    if not isNodeLeaf?(n) then
      if isPointLeaf?(n) then error "cannot mix def and named points"
      -- if we get here then this is a branch node
      scnd : % := namedPoints([sc], [ptName])
      c : List(%) := n.br.ch
      if c = [] then -- this is an empty branch node
        n.br.ch := [scnd]
        n.br.listHNM := ["useNames"]
        return scnd
      -- if we get here then this is a non-empty branch node
      fst : % := first(c)
      if isNodeLeaf?(fst) then return addNode!(fst, ptName, sc)
      -- this branch node does not have node leaf so add one
      n.br.ch := concat(c, scnd)
      n.br.listHNM := concat(n.br.listHNM,"useNames")
      return scnd
    -- if we get here then this is a node leaf node
    n.nd.listND := concat(n.nd.listND, sc)
    n.nd.listNM := concat(n.nd.listNM, ptName)
    n

  -- finds a scene node with a given name
  findNode(n : %, ptName : String) : Scene(PT) ==
    if not isNodeLeaf?(n) then
      if isPointLeaf?(n) then error "cannot mix def and named points"
      -- if we get here then this is a branch node
      c : List(%) := n.br.ch
      if c = [] then -- this is an empty branch node
        error "findNode - cant find"
      fst : % := first(c)
      if isNodeLeaf?(fst) then return findNode(fst, ptName)
    -- if we get here then this is a node leaf node
    for name in n.nd.listNM for namei in 1..#(n.nd.listNM) repeat
      if name = ptName then return (n.nd.listND).namei
    error "findNode - cant find"

  -- return string representation for debug purposes.
  toString(n : %) : String ==
    s:String := ""
    if n case pt then
      s := "pt:"
      lenNM : NNI := #(n.pt.listNM)
      lenPT : NNI := #(n.pt.listPT)
      if lenNM ~= lenPT then
        return concat(["string number:",string(lenNM),_
                       " ~= point number:",string(lenPT)])$String
      for lst in n.pt.listNM for ptr in 1..#(n.pt.listNM) repeat
        s := if ptr = 1
          then concat([s,lst,":",_
                 math_to_string n.pt.listPT.ptr])
          else concat([s,",",lst,":",_
                 math_to_string n.pt.listPT.ptr])
    if n case nd then
      s := "nd:"
      lenNM : NNI := #(n.nd.listNM)
      lenPT : NNI := #(n.nd.listND)
      if lenNM ~= lenPT then
        return concat(["string number:",string(lenNM),_
                       " ~= point number:",string(lenPT)])$String
      for lst in n.nd.listNM for ptr in 1..#(n.nd.listNM) repeat
        s := if ptr = 1
          then concat([s, lst])
          else concat([s,",",lst])
    if n case br then
      s := "br:"
      for l1 in n.br.listHNM for ptr in 1..#(n.br.listHNM) repeat
        s := if ptr = 1
          then concat([s,l1,"(",toString(n.br.ch.ptr),")"])
          else concat([s,",",l1,"(",toString(n.br.ch.ptr),")"])
    s

  -- start of SetCategory definitions --

  hash(s : %) : SingleInteger == 0$SingleInteger

  -- returns true if x equals y
  _=(x : %, y : %) : Boolean ==
    if x case pt and y case pt then
      if (x.pt.listPT = y.pt.listPT) and_
         (x.pt.listNM = y.pt.listNM) then return true
    if x case nd and y case nd then
      --if (x.nd.listND = y.nd.listND) and
      -- above line does not work because Scene does not have
      -- SetCategory and we can't use '=' but its good enough
      -- that the names below are the same
      if (x.nd.listNM = y.nd.listNM) then return true
    if x case br and y case br then
      if (x.br.ch = y.br.ch) and_
         (x.br.listHNM = y.br.listHNM) then return true
    false

  -- returns true if x is not equal to y
  _~_=(x : %, y : %) : Boolean == not(x = y)

  -- output (duplicate of toString but to OutputForm instead of String)
  coerce(n : %) : OutputForm ==
    s : OutputForm := message("")
    if n case pt then
      s := message("pt:")
      lenNM : NNI := #(n.pt.listNM)
      lenPT : NNI := #(n.pt.listPT)
      if lenNM ~= lenPT then
        return hconcat([message("string number:"), lenNM::OutputForm,_
                       message(" ~= point number:"), lenPT::OutputForm])
      for lst in n.pt.listNM for ptr in 1..#(n.pt.listNM) repeat
        s := if ptr = 1
          then hconcat([s,lst::OutputForm, message(":"), _
                 (n.pt.listPT.ptr)::OutputForm])
          else hconcat([s, message(","), lst::OutputForm, message(":"), _
                 (n.pt.listPT.ptr)::OutputForm])
    if n case nd then
      s := message("nd:")
      lenNM : NNI := #(n.nd.listNM)
      lenPT : NNI := #(n.nd.listND)
      if lenNM ~= lenPT then
        return hconcat([message("string number:"), lenNM::OutputForm,_
                        message(" ~= point number:"), lenPT::OutputForm])
      for lst in n.nd.listNM for ptr in 1..#(n.nd.listNM) repeat
        s := if ptr = 1
          then hconcat([s, lst::OutputForm])
          else hconcat([s, message(","), lst::OutputForm])
    if n case br then
      s := message("br:")
      for l1 in n.br.listHNM for ptr in 1..#(n.br.listHNM) repeat
        s := if ptr = 1
          then hconcat([s, l1::OutputForm, message("("),
                        (n.br.ch.ptr)::OutputForm, message(")")])
          else hconcat([s, message(","), l1::OutputForm, message("("),
                       (n.br.ch.ptr)::OutputForm, message(")")])
    s

)if false

\section{domain STransform}
Existing transforms in FriCAS (such as dhmatrix.spad and
moebius.spad) are stand alone chunks of algebra which don't
interwork with each other and it would be a lot of work to incorporate them
into other code especially graphics and geometry.

The aim of STransform is to make a completely general template for
transforms so that specific transforms like a Moebius transform could
be implemented so that the slot directly into the geometry/graphics
framework.

We have not achieved that aim yet. STransform does work with the various
implementations of the SPointCategory such as SCartesian(2), SCartesian(3),
SArgand and SConformal. What I would like to do is to have
a transform category which could be implemented by domains like
dhmatrix.spad and moebius.spad, then they could be
used in the geometry/graphics framework anywhere transforms are appropriate.

The most general form of transform would be represented by PT->PT where
PT is any implementation of SPointCategory. So ideally all transforms
should be defined in this way. The usual way to transform a vector is to
use a matrix like this:
transform(in : Vector, def : Matrix) : Vector
So what I need is a way to curry it into something like this:
transform(def : Matrix) : (Vector->Vector)
I don't know how to this? that is, how do I create an anonymous function
where a variable (in this case def : Matrix) is a constant in the function?

A scenegraph can be defined over various types of point and vectors:
\begin{itemize}
\item Cartesian Coordinates in 'n' dimensions.
\item Complex Coordinates (Argand Plane).
\item Conformal Coordinates in 'n' dimensions.
\end{itemize}
Each of these can be used with transforms, defined in various ways.
Here we will look at each type of space and see how it can be
transformed.
There tends to be a natural matching between the coordinate type and
the transform type as follows:
\begin{tabular}{ l c r }
  Coordinates & transform type & alternative & other options \\
  SCartesian(n) - Cartesian & matrix with (n+1)x(n+1) elements
  & function : Point->Point &  \\
  SArgand - Complex & Complex DF -> Complex
  & function : Point->Point &  matrix \\
  SConformal(n) - Conformal & multivector with 2^(n+2) elements
  & function : Point->Point &  matrix \\
\end{tabular}
However there are alternatives and we can often use different types
and they will be converted by the code.

Here we will look at each type of space in turn as show how to apply
transforms in that space which you can link to here:
\begin{itemize}
\item Transforms Using Cartesian Coordinates.
\item Transforms in a Complex Plane.
\item Transforms Using Conformal Coordinates.
\end{itemize}
\subsection{Transforms Using Cartesian Coordinates.}
The simplest way to transform Cartesian coordinates is to multiply
by a matrix. So here we will use a transform node and set it with
various matrix values. In this case we will work in 2 dimensions
(in a plane) so we can define our scene over SCartesian(2).
The dimension of the matrix should be one grater than this so
we use a 3x3 matrix. The reason for this extra dimension is to
use a projective space system which means we can translate in
addition to rotating and scaling and so on.

First we create a simple shape as follows. In line (5) we include
the transform node and set it to 'identity' to mean that, at this
stage, it has no effect but we can change it later.
\begin{verbatim}
(1) -> DF ==> DoubleFloat
                                                             Type : Void

(2) -> PT ==> SCartesian(2)
                                                             Type : Void

(3) -> view := boxBoundary(sipnt(500, 500)$PT,
                                   sipnt(1200, 1200)$PT)$SBoundary(PT)
   (3)  bound box : pt(500.0, 500.0)->pt(1200.0, 1200.0)
                                         Type : SBoundary(SCartesian(2))

(4) -> sc := createSceneRoot(view)$Scene(PT)
   (4)  scene root bound box : pt(500.0, 500.0)->pt(1200.0, 1200.0) #ch = 0
                                             Type : Scene(SCartesian(2))

(5) -> tr := addSceneTransform(sc, identity()$STransform(PT))$Scene(PT)
   (5)  scene transform tr = iden #ch = 0
                                             Type : Scene(SCartesian(2))

(6) -> gd := addScenePattern(tr,"HOUSE"::Symbol,4,view)$Scene(PT)
   (6)
   scene line
     [[pt(500.0, 500.0), pt(500.0, 535.0), ....],
      [pt(605.0, 500.0), pt(640.0, 500.0), ....], ....]
      #ch = 0
                                             Type : Scene(SCartesian(2))

(7) -> writeSvg(sc,"testGraph/example06a1.svg")
                                                             Type : Void
\end{verbatim}
We can then modify the image by changing the transform in the
existing transform node.
First we can try a simple translation. We will translate the
'house' right by 200 units and up by 200 units, so the put these
values in the right hand column of the matrix. Apart from the
translation, we don't want to change the shape in any other way,
so the remainder of the matrix is an identity matrix. Note : the
bottom right element of the matrix is set to 1, this is nearly
always the case, if we were to set it to 0 then it would covert
points to vectors which would not behave as expected of points
in any subsequent transformations.
\begin{verbatim}
(8) -> offsetx := 200::DF
   (8)  200.0
                                                      Type : DoubleFloat

(9) -> offsety := 200::DF
   (9)  200.0
                                                      Type : DoubleFloat

(10) -> trb := stransform([[1::DF, 0::DF, offsetx], _
                [0::DF, 1::DF, offsety], _
                [0::DF, 0::DF, 1::DF]])$STransform(PT)

            +1.0  0.0  200.0+
   (10)  mtx|0.0  1.0  200.0|
            +0.0  0.0   1.0 +
                                        Type : STransform(SCartesian(2))

(11) -> setTransform!(tr, trb)$Scene(PT)
                                                             Type : Void

(12) -> writeSvg(sc,"testGraph/example06b1.svg")
                                                             Type : Void
\end{verbatim}
Then we can apply a rotation, to rotate by the angle theta, about the
origin, we use the following matrix:
\begin{tabular}{ l c r }
  cos(theta) & sin(theta) \\
  -sin(theta) & cos(theta) \\
\end{tabular}
To make this into a 3x3 matrix we add a row below it consisting
of zeroes and a column on the right containing the translation
(in this case zero). Again the bottom right element of the matrix
is set to 1, unless we want to convert the points to vectors, in
which case we would set to zero.
\begin{verbatim}
(13) -> theta : DF := (45::DF) * %pi /(180::DF)
   (13)  0.7853981633974483
                                                      Type : DoubleFloat

(14) -> trc := stransform([[cos(theta), sin(theta), 0::DF], _
                [-sin(theta), cos(theta), 0::DF], _
                [0::DF, 0::DF, 1::DF]])$STransform(PT)

            +0.7071067811865476  0.7071067811865475  0.0+
   (14)  mtx|-0.7071067811865475 0.7071067811865476  0.0|
            +       0.0                 0.0          1.0+
                                        Type : STransform(SCartesian(2))

(15) -> setTransform!(tr, trc)$Scene(PT)
                                                             Type : Void

(16) -> writeSvg(sc,"testGraph/example06c1.svg")

                                                             Type : Void
\end{verbatim}
Another type of transform is scaling. to do this we put the scale
factor along the leading diagonal. Again the bottom right element
is 1.
\begin{verbatim}
(17) -> scale : DF := 0.25::DF
   (17)  0.25
                                                      Type : DoubleFloat

(18) -> trd := stransform([[scale, 0::DF, 0::DF], _
                [0::DF, scale, 0::DF], _
                [0::DF, 0::DF, 1::DF]])$STransform(PT)

            +0.25  0.0   0.0+
   (18)  mtx|0.0   0.25  0.0|
            +0.0   0.0   1.0+
                                        Type : STransform(SCartesian(2))

(19) -> setTransform!(tr, trd)$Scene(PT)
                                                             Type : Void

(20) -> writeSvg(sc,"testGraph/example06d1.svg")
                                                             Type : Void
\end{verbatim}
There are many other types of transforms that we could apply, such as, reflection in a plane.
We can also combine transforms, such as those above, by either of the following which are equivalent to each other :
\begin{itemize}
\item Multiplying the matrices together.
\item Nesting the transforms.
\end{itemize}
In both cases order is significant.
\subsection{Transforms in a Complex Plane.}
Transforms in a Complex Plane are done slightly differently than the
above examples. In this case we need to supply the transform node
with an appropriate function of the type:
\begin{verbatim}
  Complex DF -> Complex DF.
\end{verbatim}
This function implements typical transforms by a combination
of the following:
\begin{tabular}{ l c r }
   & function \\
  translate & add complex number \\
  rotate & multiply unit complex number \\
  scale & multiply real \\
\end{tabular}
First we create a simple shape as follows:
\begin{verbatim}
(1) -> DF ==> DoubleFloat
                                                             Type : Void

(2) -> C ==> Complex DF
                                                             Type : Void

(3) -> AR ==> SArgand
                                                             Type : Void

(4) -> view := boxBoundary(sipnt(500, 500)$AR, sipnt(1200, 1200)$AR)$SBoundary(AR)
   (4)  bound box : 500.0 + %i500.0->1200.0 + %i1200.0
                                               Type : SBoundary(SArgand)

(5) -> sc := createSceneRoot(view)$Scene(AR)
   (5)  scene root bound box : 500.0 + %i500.0->1200.0 + %i1200.0 #ch = 0
                                                   Type : Scene(SArgand)

(6) -> tr := addSceneTransform(sc, identity()$STransform(AR))$Scene(AR)
   (6)  scene transform tr = iden #ch = 0
                                                   Type : Scene(SArgand)

(7) -> gd := addScenePattern(tr,"HOUSE"::Symbol,4,view)$Scene(AR)
   (7)
   scene line
     [ [500.0 + %i500.0, 500.0 + %i535.0, ....],
      [605.0 + %i500.0, 640.0 + %i500.0, ....], ....]
       #ch = 0
                                                   Type : Scene(SArgand)

(8) -> writeSvg(sc,"testGraph/example06a2.svg")
                                                             Type : Void
\end{verbatim}
We can then modify the image by changing the transform in the
existing transform node.

First we can try a simple translation, this is done by creating a
function which adds a constant complex number to its input.
\begin{verbatim}
(9) -> translateComplex(x : C) : C == x + complex(200::DF, 200::DF)
   Function declaration translateComplex : Complex(DoubleFloat) ->
      Complex(DoubleFloat) has been added to workspace.
                                                             Type : Void

(10) -> trb := stransform(translateComplex)$STransform(AR)
   Compiling function translateComplex with type Complex(DoubleFloat)
       -> Complex(DoubleFloat)
   (10)  function as transform
                                              Type : STransform(SArgand)

(11) -> setTransform!(tr, trb)$Scene(AR)
                                                             Type : Void

(12) -> writeSvg(sc,"testGraph/example06b2.svg")
                                                             Type : Void
\end{verbatim}
Then we can apply a rotation, this is done by creating a function which
multiplies its input by a given complex number.
\begin{verbatim}
(13) -> theta : DF := (45::DF) * %pi /(180::DF)
   (13)  0.7853981633974483
                                                      Type : DoubleFloat

(14) -> rotateComplex(x : C) : C == x * complex(cos(theta), sin(theta))
   Function declaration rotateComplex : Complex(DoubleFloat) -> Complex
      (DoubleFloat) has been added to workspace.
                                                             Type : Void

(15) -> trc := stransform(rotateComplex)$STransform(AR)
   Compiling function rotateComplex with type Complex(DoubleFloat) ->
      Complex(DoubleFloat)
   (15)  function as transform
                                              Type : STransform(SArgand)

(16) -> setTransform!(tr, trc)$Scene(AR)
                                                             Type : Void

(17) -> writeSvg(sc,"testGraph/example06c2.svg")
                                                             Type : Void
\end{verbatim}
Scaling, this is done by creating a function which scales its input
by a DF value.
\begin{verbatim}
(18) -> scale : DF := 0.25::DF
   (18)  0.25
                                                      Type : DoubleFloat

(19) -> scaleComplex(x : C) : C == x * scale
   Function declaration scaleComplex : Complex(DoubleFloat) -> Complex(
      DoubleFloat) has been added to workspace.
                                                             Type : Void

(20) -> trd := stransform(scaleComplex)$STransform(AR)
   Compiling function scaleComplex with type Complex(DoubleFloat) ->
      Complex(DoubleFloat)
   (20)  function as transform
                                              Type : STransform(SArgand)

(21) -> setTransform!(tr, trd)$Scene(AR)
                                                             Type : Void

(22) -> writeSvg(sc,"testGraph/example06d2.svg")
                                                             Type : Void
\end{verbatim}
\subsection{Transforms Using Conformal Coordinates.}
First we create a simple shape as follows:
\begin{verbatim}
(1) -> DF ==> DoubleFloat
                                                             Type : Void

(2) -> CS ==> SConformal(2)
                                                             Type : Void

(3) -> view := boxBoundary(sipnt(500, 500)$CS,
                                sipnt(1200, 1200)$CS)$SBoundary(CS)
   (3)
   bound box:
     (0.0, - 1.0, 250000.0, 0.0, 500.0, 0.0, 0.0, 0.0,
      500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
     ->
     (0.0, - 1.0, 1440000.0, 0.0, 1200.0, 0.0, 0.0, 0.0,
      1200.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
                                         Type : SBoundary(SConformal(2))

(4) -> sc := createSceneRoot(view)$Scene(CS)
   (4)
   scene root
     bound box:
       (0.0, - 1.0, 250000.0, 0.0, 500.0, 0.0, 0.0, 0.0,
        500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
       ->
       (0.0, - 1.0, 1440000.0, 0.0, 1200.0, 0.0, 0.0, 0.0,
        1200.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
      #ch = 0
                                             Type : Scene(SConformal(2))

(5) -> tr := addSceneTransform(sc, identity()$STransform(CS))$Scene(CS)
   (5)  scene transform tr = iden #ch = 0
                                             Type : Scene(SConformal(2))

(6) -> gd := addScenePattern(tr, "HOUSE"::Symbol, 4, view)$Scene(CS)
   (6)
   scene line
     [
       [
         (0.0, - 1.0, 250000.0, 0.0, 500.0, 0.0, 0.0, 0.0,
          500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
         ,

         (0.0, - 1.0, 268112.5, 0.0, 500.0, 0.0, 0.0, 0.0,
           535.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
         ,
        ....]
       ,

       [
         (0.0, - 1.0, 308012.5, 0.0, 605.0, 0.0, 0.0, 0.0,
           500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
         ,

         (0.0, - 1.0, 329800.0, 0.0, 640.0, 0.0, 0.0, 0.0,
           500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
         ,
        ....]
       ,
      ....]
      #ch = 0
                                             Type : Scene(SConformal(2))

(7) -> writeSvg(sc, "testGraph/example06a2.svg")
                                                             Type : Void
\end{verbatim}
We can then modify the image by changing the transform in the
existing transform node.

First we can try a simple translation:
\begin{verbatim}
(8) -> offsetx := 200::DF
   (8)  200.0
                                                      Type : DoubleFloat

(9) -> offsety := 200::DF
   (9)  200.0
                                                      Type : DoubleFloat

(10) -> trb := stransform([1::DF, 0::DF, 0::DF, 0::DF, _
                0::DF, 0::DF, offsetx*(0.5::DF), 0::DF, _
                0::DF, 0::DF, offsety*(0.5::DF), 0::DF, _
                0::DF, 0::DF, 0::DF, 0::DF])$STransform(CS)
   (10)
   multiv[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0,
                  0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0]
                                        Type : STransform(SConformal(2))

(11) -> setTransform!(tr, trb)$Scene(CS)
                                                             Type : Void

(12) -> writeSvg(sc, "testGraph/example06b3.svg")
                                                             Type : Void
\end{verbatim}
Then we can apply a rotation:
\begin{verbatim}
(13) -> theta : DF := (45::DF) * %pi /(180::DF)
   (13)  0.7853981633974483
                                                      Type : DoubleFloat

(14) -> trc := stransform([cos(theta/2), 0::DF, 0::DF, 0::DF, _
                0::DF, 0::DF, 0::DF, 0::DF, _
                0::DF, 0::DF, 0::DF, 0::DF, _
                -sin(theta/2), 0::DF, 0::DF, 0::DF])$STransform(CS)
   (14)
   multiv
     [0.9238795325112867, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
       0.0, 0.0, 0.0, 0.0, - 0.3826834323650898, 0.0, 0.0, 0.0]
                                        Type : STransform(SConformal(2))

(15) -> setTransform!(tr, trc)$Scene(CS)
                                                             Type : Void

(16) -> writeSvg(sc, "testGraph/example06c3.svg")
                                                             Type : Void
\end{verbatim}
Scaling:
\begin{verbatim}
(17) -> scale : DF := 0.25::DF
   (17)  0.25
                                                      Type : DoubleFloat

(18) -> trd := stransform(
               [cosh(scale*0.5::DF), 0::DF, 0::DF, sinh(scale*0.5::DF), _
                0::DF, 0::DF, 0::DF, 0::DF, _
                0::DF, 0::DF, 0::DF, 0::DF, _
                0::DF, 0::DF, 0::DF, 0::DF])$STransform(CS)
   (18)
   multiv
     [1.0078226778257109, 0.0, 0.0, 0.12532577524111546, 0.0,
      0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
                                        Type : STransform(SConformal(2))

(19) -> setTransform!(tr, trd)$Scene(CS)
                                                             Type : Void

(20) -> writeSvg(sc, "testGraph/example06d3.svg")
                                                             Type : Void
\end{verbatim}
We can also do transform types that are not easy to do in other spaces
such as reflection in unit circle:
\begin{verbatim}
(21) -> tre := stransform([0::DF, 1::DF, -1000::DF, 0::DF, _
                0::DF, 0::DF, 0::DF, 0::DF, _
                0::DF, 0::DF, 0::DF, 0::DF, _
                0::DF, 0::DF, 0::DF, 0::DF])$STransform(CS)
   (21)
   multiv[0.0, 1.0, - 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                                     0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
                                        Type : STransform(SConformal(2))

(22) -> setTransform!(tr, tre)$Scene(CS)
                                                             Type : Void

(23) -> writeSvg(sc, "testGraph/example06e3.svg")
                                                             Type : Void
\end{verbatim}
)endif

)abbrev domain STR STransform
++ Author: Martin Baker
++ Date Created: September 2010
++ Basic Operations: spnt, sipnt, svec, sivec, screenCoords
++ Related Constructors: SCartesian, SArgand, SConformal
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ http://www.euclideanspace.com/prog/scratchpad/mycode/graph/
++ Description:
++ This domain implements transforms from SPoint to SPoint

STransform(PT) : Exports == Implementation where
  ++ Domains for transform from SPoint to SPoint
  ++ STransform transform domain can by used to represent linear transforms
  ++ for instance when working with SCartesian
  ++ where its Representation is a matrix of DoubleFloat
  ++ Other transform domains may not necessarily
  ++ be linear and will have different representations

  PT : SPointCategory

  PI ==> PositiveInteger
  NNI ==> NonNegativeInteger
  DF ==> DoubleFloat
  TRAN ==> PT -> PT
  C ==> Complex DF
  FCV ==> C -> C
  PARAMS==> Union(mtx : PrimitiveArray PrimitiveArray DF, _
                 general : TRAN, _
                 multiv : PrimitiveArray DF, _
                 iden:"iden")

  Exports == with

    stransform : (m : List List DF) -> %
      ++ stransform(m) constructs transform with given matrix elements
    stransform : (gen : PT -> PT) -> %
      ++ stransform(gen) construct transform in general form as a
      ++ mapping from PT to PT
    stransform : (cpx : C -> C) -> %
      ++ stransform(cpx) construct transform as function of complex
      ++ variable can only be used when PT is SArgand so this can be
      ++ converted to PT -> PT
    stransform : (m : List DF) -> %
      ++ stransform(m) construct transform using a multivector
    stranslate : (offsetx : DF, offsety : DF, offsetz : DF, scalex : DF, _
                 scaley : DF, scalez : DF) -> %
      ++ stranslate(offsetx, offsety, offsetz, scalex, scaley, scalez)
      ++ constructs a transform which represents pure translation
      ++ we can also combine with scale which, for instance, is useful
      ++ when writing to SVG file because the y dimension is inverted
    identity : () -> %
      ++ identity() returns the identity element which is do nothing
      ++ transform
    xform : (tr : %, inpt : PT) -> PT
      ++ xform(tr, inpt) apply transform to a point producing another
      ++ point
    compound : (tr : %, inpt : %) -> %
      ++ compound(tr, inpt) combine two transforms
    coerce : (tr : %) -> OutputForm
      ++ coerce(tr) output

  Implementation == add

    import from OutputForm

    Rep := PARAMS

    -- qelt(m, i, j) returns the element in the ith row and jth
    -- column of the array m
    -- NO error check to determine if indices are in proper ranges
    qelt(m : %, i : Integer, j : Integer) : DF ==
      qelt(qelt(m.mtx, i)$(PrimitiveArray PrimitiveArray DF), j)

    -- construct with given matrix elements
    stransform(m : List List DF) : % ==
      PT has SCartesian(2) =>
        arr : PrimitiveArray PrimitiveArray DF := new(3, empty())
        for i in 0..2 repeat
          qsetelt!(arr, i, new(3, 0::DF))
          for j in 0..2 repeat
            (arr.i).j := (m.(i+1)).(j+1)
        [arr]
      PT has SCartesian(3) =>
        arr : PrimitiveArray PrimitiveArray DF := new(4, empty())
        for i in 0..3 repeat
          qsetelt!(arr, i, new(4, 0::DF))
          for j in 0..3 repeat
            (arr.i).j := (m.(i+1)).(j+1)
        [arr]
      error "invalid point type in transform constructor"
      identity()

    -- construct with a general mapping from PT to PT
    stransform(gen : PT -> PT) : % ==
      [gen]

    -- construct transform as function of complex variable
    -- can only be used when PT is SArgand so this can be
    -- converted to PT -> PT
    stransform(cpx : C -> C) : % ==
      pp : (PT -> PT) := ((x : PT) : PT +-> _
         (cpx((x pretend SArgand):: C))::SArgand pretend PT)
      [pp]

    -- construct with a multivector
    stransform(m : List DF) : % ==
      [construct m]

    -- construct transform which represents pure translation
    -- we can also combine with scale which, for instance, is useful
    -- when writing to SVG file because the y dimension is inverted
    stranslate(offsetx : DF, offsety : DF, offsetz : DF, _
               scalex : DF, scaley : DF, scalez : DF) : % ==
      if PT has SCartesian(2) then
        return stransform([_
             [scalex, 0::DF, offsetx], _
             [0::DF, scaley, offsety], _
             [0::DF, 0::DF, 1::DF]])
      if PT has SCartesian(3) then
        return stransform([_
             [scalex, 0::DF, 0::DF, offsetx], _
             [0::DF, scaley, 0::DF, offsety], _
             [0::DF, 0::DF, scalez, offsetz], _
             [0::DF, 0::DF, 0::DF, 1::DF]])
      if PT has SConformal(2) then
        m := matrix([[0::DF, 1::DF, 0::DF, 0::DF], _
                           [1::DF, 0::DF, 0::DF, 0::DF], _
                           [0::DF, 0::DF, 1::DF, 0::DF], _
                           [0::DF, 0::DF, 0::DF, 1::DF]])$Matrix(DF)
        CA := CliffordAlgebra(4, DF, squareMatrix(m)$SquareMatrix(4, DF))
        trConf : CA := multivector([1::DF, 0::DF, 0::DF, 0::DF, _
                0::DF, 0::DF, offsetx*(0.5::DF), 0::DF, _
                0::DF, 0::DF, offsety*(0.5::DF), 0::DF, _
                0::DF, 0::DF, 0::DF, 0::DF])$CA
        return [trConf pretend PrimitiveArray DF]
      if PT has SArgand then
        pp : (PT -> PT) := ((x : PT) : PT +-> _
          (((x pretend SArgand):: C)+ complex(offsetx, offsety))::SArgand pretend PT)
        return [pp]
      identity()

    -- returns the identity element which is 'do-nothing' transform
    identity():% == ["iden"]

    -- apply transform to a point producing another point
    applyTransPt2(tr : %, inpt : SCartesian(2)) : SCartesian(2) ==
      if tr case iden then return inpt
      if tr case general then
        print message("can't transform Euclidean point using general function")
        return inpt
      vin : List DF := extendedCoords(inpt)$SCartesian(2)
      a : DF := tr.mtx.0.0 * vin.1 + tr.mtx.0.1 * vin.2 + tr.mtx.0.2 * vin.3
      b : DF := tr.mtx.1.0 * vin.1 + tr.mtx.1.1 * vin.2 + tr.mtx.1.2 * vin.3
      spnt(a, b)$SCartesian(2)

    -- apply transform to a point producing another point
    applyTransPt3(tr : %, inpt : SCartesian(3)) : SCartesian(3) ==
      if tr case iden then return inpt
      if tr case general then
        print message("can't transform Euclidean point using general function")
        return inpt
      vin : List DF := extendedCoords(inpt)$SCartesian(3)
      a : DF := tr.mtx.0.0 * vin.1 + tr.mtx.0.1 * vin.2_
            + tr.mtx.0.2 * vin.3 + tr.mtx.0.3 * vin.4
      b : DF := tr.mtx.1.0 * vin.1 + tr.mtx.1.1 * vin.2_
            + tr.mtx.1.2 * vin.3 + tr.mtx.1.3 * vin.4
      c : DF := tr.mtx.2.0 * vin.1 + tr.mtx.2.1 * vin.2_
            + tr.mtx.2.2 * vin.3 + tr.mtx.2.3 * vin.4
      spnt(a, b, c)$SCartesian(3)

    -- apply transform to a (complex number) point producing another point
    applyTransCx(tr : %, inpt : SArgand) : SArgand ==
      if tr case iden then return inpt
      if tr case mtx then
        print(message("can't transform complex using matrix"))
        print coerce(tr)
        return inpt
      f := tr.general
      res := f(inpt pretend PT)
      res pretend SArgand

    -- apply transform to a 2D conformal point producing another point
    applyTransConf2(tr : %, inpt : SConformal(2)) : SConformal(2) ==
      if tr case iden then return inpt
      if not (tr case multiv) then
        print(message("this transform not compatible with conformal"))
        print coerce(tr)
        return inpt
      m := matrix([[0::DF, 1::DF, 0::DF, 0::DF], _
                           [1::DF, 0::DF, 0::DF, 0::DF], _
                           [0::DF, 0::DF, 1::DF, 0::DF], _
                           [0::DF, 0::DF, 0::DF, 1::DF]])$Matrix(DF)
      CA := CliffordAlgebra(4, DF, squareMatrix(m)$SquareMatrix(4, DF))
      ptConf : CA := inpt pretend CA
      trConf : CA := (tr.multiv) pretend CA
      conjugation := trConf * ptConf * reverse(trConf)
      res := conjugation pretend SConformal(2)
      normalisePoint(res)

    -- apply transform to a point producing another point
    xform(tr : %, inpt : PT) : PT ==
      PT has SCartesian(2) => _
        applyTransPt2(tr, inpt pretend SCartesian(2)) pretend PT
      PT has SCartesian(3) => _
        applyTransPt3(tr, inpt pretend SCartesian(3)) pretend PT
      PT has SArgand => applyTransCx(tr, inpt pretend SArgand) pretend PT
      PT has SConformal(2) => _
        applyTransConf2(tr, inpt pretend SConformal(2)) pretend PT
      error "invalid point type in transform"
      spnt(0::DF, 0::DF)$SCartesian(2) pretend PT

    -- combine two 2D transforms
    compound2(tr : %, inpt : %) : % ==
      arr : PrimitiveArray PrimitiveArray DF := new(3, empty())
      for i in 0..2 repeat
        qsetelt!(arr, i, new(3, 0::DF))
        for j in 0..2 repeat
          (arr.i).j := tr.mtx.0.j * inpt.mtx.i.0_
                     + tr.mtx.1.j * inpt.mtx.i.1_
                     + tr.mtx.2.j * inpt.mtx.i.2
      return [arr]

    -- combine two 3D transforms
    compound3(tr : %, inpt : %) : % ==
      arr : PrimitiveArray PrimitiveArray DF := new(4, empty())
      for i in 0..3 repeat
        qsetelt!(arr, i, new(4, 0::DF))
        for j in 0..3 repeat
          (arr.i).j := tr.mtx.0.j * inpt.mtx.i.0_
                     + tr.mtx.1.j * inpt.mtx.i.1_
                     + tr.mtx.2.j * inpt.mtx.i.2_
                     + tr.mtx.3.j * inpt.mtx.i.3
      return [arr]

    -- combine two 2D conformal transforms
    compoundConf2(tr : %, inpt : %) : % ==
      m := matrix([[0::DF, 1::DF, 0::DF, 0::DF], _
                           [1::DF, 0::DF, 0::DF, 0::DF], _
                           [0::DF, 0::DF, 1::DF, 0::DF], _
                           [0::DF, 0::DF, 0::DF, 1::DF]])$Matrix(DF)
      CA := CliffordAlgebra(4, DF, squareMatrix(m)$SquareMatrix(4, DF))
      trConf : CA := (tr.multiv) pretend CA
      inptConf : CA := (inpt.multiv) pretend CA
      resConf : CA := trConf*inptConf
      return [resConf pretend PrimitiveArray DF]

    -- combine two transforms
    compound(tr : %, inpt : %) : % ==
      -- first check for simple case where one of the operands is identity
      if tr case iden then return inpt -- iden*inpt = inpt
      if inpt case iden then return tr -- tr*iden = tr
      if tr case mtx and inpt case mtx then
        if PT has SCartesian(2) then return compound2(tr, inpt)
        if PT has SCartesian(3) then return compound3(tr, inpt)
      if tr case general and inpt case general then
        in1 : PT -> PT := tr.general
        in2 : PT -> PT := inpt.general
        fn : PT -> PT := _*(in1, in2)$MappingPackage3(PT, PT, PT)
        return [fn]
      if tr case multiv and inpt case multiv then
        return compoundConf2(tr, inpt)
      identity()

    -- local function used by coerce(%): OutputForm to format array
    outputArray(x : PrimitiveArray DF) : OutputForm ==
      l : List OutputForm
      m : NNI := #x
      bracket([(x.i)::OutputForm for i in 0..(m-1)])

    -- local function used by coerce(%): OutputForm to format matrix
    outputMatrix(x : PrimitiveArray PrimitiveArray DF) : OutputForm ==
      l : List List OutputForm
      m : NNI := #x
      l := [[(x.i.j)::OutputForm for j in 0..(m-1)] for i in 0..(m-1)]
      matrix(l)

    -- output
    coerce(tr : %) : OutputForm ==
      -- iden means identity transform
      if tr case iden then return message("iden")
      -- can't display function as OutputForm
      if tr case general then return message("function as transform")
      -- if matrix output matrix
      if tr case mtx then
        return hconcat(message("mtx"), outputMatrix(tr.mtx))
      -- if above tests are false then tr must be multiv
      hconcat(message("multiv"), outputArray(tr.multiv))

)if false

\section{Boundary}
SBoundary implements boundaries for use in scene graph

This defines the outer extent of the scene or of an element
in the scene or of some branch in the scene.

The difference between this boundary and an n-dimensional
surface, such as IFS, is that boundary must always have a
well defined inside and an outside.

SBoundary does not necessarily follow the exact outer contours
of the shape but just constructs a simple boundary shape where
all points of the shape are guaranteed to be inside the boundary.

The uses of SBoundary are:
\begin{itemize}
\item So we know how much to scale a given scene to fit in a
given size.
\item So that we know where to terminate arrows going in out out of
of the shape.
\item To detect if objects intersect (collision detect), or if one
object is contained completely inside another.
\end{itemize}

Currently there are 3 boundary shapes:
\begin{itemize}
\item "box"::Symbol which is a n-dimensional axis-aligned rectangle
\item "ellipoid"::Symbol which is a n-dimensional axis-aligned ellipse
\item "none"::Symbol is used, for example, when the boundary of a
material node is requested.
\end{itemize}
In future we may add other boundary shapes, we may also allow a
boundary to consist of a union of these other shapes.

In the case of box it is specified by two points, on opposite ends
of diagonal, where edges are aligned with coordinate axes.

In the case of ellipse : first parameter is centre and second
determines the radius aligned with each coordinate axis (half
the full height and width).

First we create some types to work with
\begin{verbatim}
(1) -> DF ==> DoubleFloat
                                                             Type : Void
(2) -> PT ==> SCartesian(2)
                                                             Type : Void
(3) -> B ==> SBoundary PT
                                                             Type : Void
\end{verbatim}
As discussed above SBoundary can have one of three forms : a null
boundary, a box boundary and an ellipoid boundary. These 3 forms can
be constructed as in 3, 4 and 5 below:
\begin{verbatim}
(4) -> B1 := nullBoundary()$B
   (4)  bound none
                                         Type : SBoundary(SCartesian(2))

(5) -> B2 := boxBoundary(spnt(20::DF, -20::DF), spnt(10::DF, 10::DF))$B
   (5)  bound box : pt(10.0, - 20.0)->pt(20.0, 10.0)
                                         Type : SBoundary(SCartesian(2))

(6) -> B3 := ellipseBoundary(spnt(30::DF, 10::DF), spnt(25::DF, 5::DF))$B
   (6)  bound ellipoid : pt(30.0, 10.0)->pt(25.0, 5.0)
                                         Type : SBoundary(SCartesian(2))
\end{verbatim}
We can now do various operations on the boundaries that we have
constructed.

Line (7) construct a union of two boundaries, that is, a boundary
that completely encloses the input boundaries.

Line (8) is also a union, but this time, one of the operands is a
point. We can think of this as extending the boundary to include
the point.

Lines (9) and (10) demonstrate how we can test if a point is
inside the boundary.
\begin{verbatim}
(7) -> B4 := sunion(B2, B3)
   (7)  bound box : pt(5.0, - 20.0)->pt(55.0, 15.0)
                                         Type : SBoundary(SCartesian(2))

(8) -> B5 := extendToPoint(B2, spnt(30::DF, 10::DF))$B
   (8)  bound box : pt(10.0, - 20.0)->pt(30.0, 10.0)
                                         Type : SBoundary(SCartesian(2))

(9) -> containsPoint?(B2, spnt(15::DF, 5::DF))$B
   (9)  true
                                                          Type : Boolean

(10) -> containsPoint?(B2, spnt(0::DF, 5::DF))$B
   (10)  false
                                                          Type : Boolean
\end{verbatim}
\section{SBOUND SBoundary}
)endif

)abbrev domain SBOUND SBoundary
++ Author: Martin Baker
++ Date Created: February 2012
++ Basic Operations: spnt, sipnt, svec, sivec, screenCoords
++ Related Constructors: SCartesian, SArgand, SConformal
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ http://www.euclideanspace.com/prog/scratchpad/mycode/graph/
++ Description:
++ This domain implements boundaries for use in scene graph

SBoundary(PT) : Exports == Implementation where
  ++ SBoundary implements boundaries for use in scene graph
  ++ This defines the outer extent of the scene, or of an element
  ++ in the scene, or of some branch in the scene.
  ++ The difference between this boundary and an n-dimensional
  ++ surface, such as IFS, is that boundary must always have a
  ++ well defined inside and an outside.
  ++ SBoundary does not necessarily follow the exact outer contours
  ++ of the shape but just constructs a simple boundary shape where
  ++ all points of the shape are guaranteed to be inside the boundary.
  ++ Currently there are the following boundary forms:
  ++  Simple types:
  ++   "box"::Symbol which is a n-dimensional axis-aligned rectangle.
  ++   "ellipoid"::Symbol which is a n-dimensional axis-aligned ellipse.
  ++  Compound types:
  ++   This is a combination of simple and other compound types such
  ++   as union or intersection.
  ++    "union"::Symbol represents union of supplied list
  ++    "intersection"::Symbol represents intersection of supplied list
  ++  Null types:
  ++   "none" is used for a node which does not have a boundary in
  ++       space, for example, when the boundary of a material node
  ++       is requested.
  ++ The uses of SBoundary are:
  ++ 1) So we know how much to scale a given scene to fit in a
  ++    given size.
  ++ 2) So that we know where to terminate arrows going in out out of
  ++    of the shape.
  ++ 3) To detect if objects intersect (collision detect), or if one
  ++    object is contained completely inside another.

  PT : SPointCategory

  PI ==> PositiveInteger
  NNI ==> NonNegativeInteger
  DF ==> DoubleFloat
  LINE ==> List PT
  PARAMS ==> Union(nul:"none",
               simple : Record(ty : Symbol, control1 : PT, control2 : PT),
               compound : Record(ty : Symbol, parts : List %))
  Exports == with

    nullBoundary : () -> %
      ++ nullBoundary() constructs a null boundary, for example,
      ++ when the boundary of a material node is requested.
    boxBoundary : (c1 : PT, c2 : PT) -> %
      ++ boxBoundary(c1, c2) constructs a box boundary
      ++ (n-dimensional axis-aligned rectangle) with given
      ++ parameters
      ++ c1: PT holds minimum coordinates
      ++ c2: PT holds maximum coordinates
    ellipseBoundary : (c1 : PT, c2 : PT) -> %
      ++ ellipseBoundary(c1, c2) constructs an ellipse boundary
      ++ n-dimensional axis-aligned ellipoid with given
      ++ parameters
      ++ c1: PT holds centre point
      ++ c2: PT holds extent (radius for each coordinate)
    union : (x : List %) -> %
      ++ union(x) constructs a compound boundary from the union
      ++ of x
      ++ x: List % is supplied list of boundaries to form union
    intersection : (x : List %) -> %
      ++ intersection(x) constructs a compound boundary from the
      ++ intersection of x
      ++ x: List % is supplied list of boundaries to form
      ++ intersection.
    sunion : (x : %, y : %) -> %
      ++ sunion(x, y) gives the union of two boundaries as a box,
      ++   that is it gives a box that is external to both.
      ++ x: % is first boundary to be combined
      ++ y: % is second boundary to be combined
    extendToPoint : (n : %, p : PT) -> %
      ++ extendToPoint(n, p) creates a boundary that is the union
      ++   of a boundary and a point.
      ++ n is the boundary
      ++ p is the point
    containsPoint? : (n : %, p : PT) -> Boolean
      ++ containsPoint?(n, p) returns true if the point is inside
      ++   the boundary.
      ++ n is the boundary
      ++ p is the point
    lineIntersect : (n : %, p : PT) -> PT
      ++ lineIntersect(n, p) returns the point where a line,
      ++ from the centre of the boundary to the point p, intersects
      ++ with the boundary.
      ++ n: % is the boundary
      ++ p: PT is the point at the end of the line (outside boundary)
    isNull? : (n : %) -> Boolean
      ++ isNull?(n) returns true if this is a null boundary, that is
      ++ no boundary
    isEllipse? : (n : %) -> Boolean
      ++ isEllipse?(n) returns true if this is a ellipsoid boundary.
    isBox? : (n : %) -> Boolean
      ++ isBox?(n) returns true if this is a rectangular boundary.
    getMin : (n : %) -> PT
      ++ getMin(n) returns minimum coordinate of the boundary.
      ++ n is the boundary
    getMax : (n : %) -> PT
      ++ getMax(n) returns maximum coordinate of the boundary.
      ++ n is the boundary
    getCentre : (n : %) -> PT
      ++ getCentre(n) returns centre coordinate of the boundary.
      ++ n: % is the boundary
    link : (m : %, n : %) -> LINE
      ++ link(m, n) returns a line which is the shortest distance
      ++ between the two boundaries. The line is represented by its
      ++ two endpoints.
      ++ m: % is the first boundary to be linked
      ++ n: % is the second boundary to be linked
    coerce : (bd : %) -> OutputForm
      ++ coerce(bd) outputs description of bd as formatted text

  Implementation == add

    Rep := PARAMS

    -- nullBoundary() constructs a null boundary, for example,
    --      when the boundary of a material node is requested.
    nullBoundary() : % ==
      ["none"]

    -- boxBoundary(c1, c2) constructs a box boundary with given
    -- parameters
    boxBoundary(c1 : PT, c2 : PT) : % ==
      [["box"::Symbol,min(c1,c2),max(c1,c2)]]

    -- ellipseBoundary(c1, c2) constructs a ellipse boundary with given
    -- parameters
    ellipseBoundary(c1 : PT, c2 : PT) : % ==
      [["ellipoid"::Symbol,c1,c2]]

    -- union(x) constructs a compound boundary from the union
    -- of x
    -- x: List % is supplied list of boundaries to form union
    union(x : List %) : % ==
      [["union"::Symbol,x]]

    -- intersection(x) constructs a compound boundary from the
    -- intersection of x
    -- x: List % is supplied list of boundaries to form
    -- intersection.
    intersection(x : List %) : % ==
      [["intersection"::Symbol,x]]

    -- sunion(x, y) gives the union of two boundaries as a box,
    --   that is it gives a box that is external to both.
    -- x: % is first boundary to be combined
    -- y: % is second boundary to be combined
    sunion(x : %, y : %) : % ==
      if x case nul then return y
      if y case nul then return x
      if x case compound then
        p : List % := x.compound.parts
        if #p = 0 then return y
        if #p = 1 then return sunion(first p, y)
        res : % := sunion(first p, y)
        for ptr in 2..#p repeat res := sunion(res, p.ptr)
        return res
      if y case compound then
        p : List % := y.compound.parts
        if #p = 0 then return x
        if #p = 1 then return sunion(first p, x)
        res : % := sunion(first p, x)
        for ptr in 2..#p repeat res := sunion(res, p.ptr)
        return res
      -- since we have reached here then we have simple boundaries
      xmin : PT := x.simple.control1
      xmax : PT := x.simple.control2
      ymin : PT := y.simple.control1
      ymax : PT := y.simple.control2
      if x.simple.ty= "ellipoid"::Symbol then
        xmin := x.simple.control1 - x.simple.control2
        xmax := x.simple.control1 + x.simple.control2
      if y.simple.ty= "ellipoid"::Symbol then
        ymin := y.simple.control1 - y.simple.control2
        ymax := y.simple.control1 + y.simple.control2
      [["box"::Symbol,min(xmin,ymin),max(xmax,ymax)]]

    -- extendToPoint(n, p) creates a boundary that is the union
    --   of a boundary and a point.
    -- n is the boundary
    -- p is the point
    extendToPoint(n : %, p : PT) : % ==
      if n case nul then return [["box"::Symbol,p,p]]
      if n case compound then
        pa : List % := n.compound.parts
        if #pa = 0 then return [["box"::Symbol,p,p]]
        if #pa = 1 then return extendToPoint(first pa, p)
        res : % := extendToPoint(first pa, p)
        for ptr in 2..#pa repeat res := sunion(res, pa.ptr)
        return res
      -- since we have reached here then we have simple boundaries
      xmin : PT := getMin(n)
      xmax : PT := getMax(n)
      [["box"::Symbol,min(xmin,p),max(xmax,p)]]

    -- containsPoint?(n, p) returns true if the point is inside
    --   the boundary.
    -- n is the boundary
    -- p is the point
    containsPoint?(n : %, p : PT) : Boolean ==
      if n case nul then return false
      if n case compound then
        pa : List % := n.compound.parts
        for sub in pa repeat
          if containsPoint?(sub, p) then return true
        return false
      -- since we have reached here then we have simple boundaries
      if isEllipse?(n) then
        pRel := n.simple.control1 - p
        pDist : DF := screenCoordX(pRel)*screenCoordX(pRel) + _
                    screenCoordY(pRel)*screenCoordY(pRel)
        rDist : DF := screenCoordX(n.simple.control2) * _
                    screenCoordX(n.simple.control2) + _
                    screenCoordY(n.simple.control2) * _
                    screenCoordY(n.simple.control2)
        return rDist > pDist
      inBounds?(p, n.simple.control1, n.simple.control2)

    -- lineIntersect(n, p) returns the point where a line,
    -- from the centre of the boundary to the point p, intersects
    -- with the boundary.
    -- n is the boundary
    -- p is the point at the end of the line (outside boundary)
    lineIntersect(n : %, p : PT) : PT ==
      if n case nul then return p
      if n case compound then
        error "cant compute lineIntersect with compound border"
      if isEllipse?(n) then
        -- equation of ellipse is x^2/a^2 + y^2/b^b = 1
        -- equation of straight line is y/x = py/px
        -- solving for x and y gives:
        centre : PT := n.simple.control1
        rad : PT := n.simple.control2
        pRel := p - centre
        a := screenCoordX(rad)
        b := screenCoordY(rad)
        xp := screenCoordX(pRel)
        yp := screenCoordY(pRel)
        ab := a*b
        divisor := sqrt(a*a*yp*yp + b*b*xp*xp)
        x := 0::DF
        y := 0::DF
        if not (divisor = 0::DF) then
          x := ab*xp/divisor
          y := ab*yp/divisor
        --print(vconcat([_
        -- hconcat("lineIntersect Ellipse control1="::OutputForm,_
        -- (n.simple.control1)::OutputForm), _
        -- hconcat(" control2="::OutputForm,(n.simple.control2)::OutputForm),_
        -- hconcat(" p="::OutputForm,p::OutputForm),_
        -- hconcat(" a="::OutputForm,a::OutputForm),_
        -- hconcat(" b="::OutputForm,b::OutputForm),_
        -- hconcat(" xp="::OutputForm,xp::OutputForm),_
        -- hconcat(" yp="::OutputForm,yp::OutputForm),_
        -- hconcat(" ab="::OutputForm,ab::OutputForm),_
        -- hconcat(" divisor="::OutputForm,divisor::OutputForm),_
        -- hconcat(" x="::OutputForm,x::OutputForm),_
        -- hconcat(" y="::OutputForm,y::OutputForm),_
        -- hconcat(" result="::OutputForm,(centre+svec(x,y)$PT)::OutputForm)]))
        return centre+svec(x, y)$PT
      cent := 0.5::DF * (n.simple.control1 + n.simple.control2)
      pRel := p - cent
      rad : PT := 0.5::DF * (n.simple.control2 - n.simple.control1)
      a := screenCoordX(rad)
      b := screenCoordY(rad)
      xp := screenCoordX(pRel)
      yp := screenCoordY(pRel)
      x := if yp ~= 0 then b*xp/yp else a+1::DF
      y := if yp > 0 then b else -b
      if yp < 0 then x := -x
      if x > a or x < -a then
        y := if xp ~= 0 then a*yp/xp else b
        x := if xp > 0 then a else -a
        if xp < 0 then y := -y
      --print(vconcat([_
      -- hconcat("lineIntersect Box control1="::OutputForm,(n.simple.control1)::OutputForm),_
      -- hconcat(" control2="::OutputForm,(n.simple.control2)::OutputForm),_
      -- hconcat(" p="::OutputForm,p::OutputForm),_
      -- hconcat(" cent="::OutputForm,cent::OutputForm),_
      -- hconcat(" a="::OutputForm,a::OutputForm),_
      -- hconcat(" b="::OutputForm,b::OutputForm),_
      -- hconcat(" xp="::OutputForm,xp::OutputForm),_
      -- hconcat(" yp="::OutputForm,yp::OutputForm),_
      -- hconcat(" pRel="::OutputForm,pRel::OutputForm),_
      -- hconcat(" rad="::OutputForm,rad::OutputForm),_
      -- hconcat(" x="::OutputForm,x::OutputForm),_
      -- hconcat(" y="::OutputForm,y::OutputForm)]))
      cent + svec(x, y)$PT

    -- isNull?(n) returns true if this is a null boundary, that is
    -- no boundary
    isNull?(n : %) : Boolean == n case nul

    -- isEllipse?(n) returns true if this is a ellipsoid boundary.
    isEllipse?(n : %) : Boolean ==
      if not (n case simple) then return false
      n.simple.ty = "ellipoid"::Symbol

    -- isBox?(n) returns true if this is a rectangular boundary.
    isBox?(n : %) ==
      if not (n case simple) then return false
      n.simple.ty = "box"::Symbol

    -- getMin(n) returns minimum coordinate of the boundary.
    -- n is the boundary
    getMin(n : %) : PT ==
      if isEllipse?(n) then
        --print(vconcat([_
        -- hconcat("scenebd getMin n.control1="::OutputForm,(n.control1)::OutputForm),_
        -- hconcat(" n.control2="::OutputForm,(n.control2)::OutputForm),_
        -- hconcat(" (n.control1) - (n.control2)="::OutputForm,((n.control1) - (n.control2))::OutputForm)]))
        return toPoint((n.simple.control1) - (n.simple.control2))
      --print(hconcat(["scenebd getMin("::OutputForm,_
      -- n::OutputForm, _
      -- ")="::OutputForm,_
      -- (n.control1)::OutputForm]))
      n.simple.control1

    -- getMax(n) returns maximum coordinate of the boundary.
    -- n is the boundary
    getMax(n : %) : PT ==
      if isEllipse?(n) then
        return toPoint((n.simple.control1) + (n.simple.control2))
      n.simple.control2

    -- getCentre(n) returns centre coordinate of the boundary.
    -- n is the boundary
    getCentre(n : %) : PT ==
      if isEllipse?(n) then return n.simple.control1
      0.5::DF * (n.simple.control1 + n.simple.control2)

    -- link(m, n) returns a line which is the shortest distance
    -- between the two boundaries. The line is represented by its
    -- two endpoints.
    -- m is the first boundary to be linked
    -- n is the second boundary to be linked
    link(m : %, n : %) : LINE ==
      bm := lineIntersect(m, getCentre(n))
      bn := lineIntersect(n, getCentre(m))
      [bm, bn]

    -- output
    coerce(n : %) : OutputForm ==
      ty:OutputForm := outputForm("none "::Symbol)
      if n case simple then ty := (n.simple.ty)::OutputForm
      if n case compound then ty := (n.compound.ty)::OutputForm
      s:OutputForm := hconcat(outputForm("bound "::Symbol),ty)
      if n case nul then return s
      if n case simple then return _
        hconcat([s, message(":"), n.simple.control1::OutputForm,_
               outputForm("->"::Symbol),n.simple.control2::OutputForm])
      sub : OutputForm := bracket([x::OutputForm for x in n.compound.parts])
      hconcat([s,outputForm(":"::Symbol),sub])

)if false

\section{XML code}
There are 2 domains and one package here to support the storage and writing
of XML information, these are:
\begin{itemize}
\item XmlAttribute domain - this holds an attribute
\item XmlElement domain - this holds an element, including a list of all
           its elements and a list of all child elements.
\item ExportXml package - has code for writing out the complete XML file.
\end{itemize}
This XML code is used to write both SVG and X3D files. It was the original
intention that this XML should not be specific to the scenegraph but be
general enough to be used in other places (perhaps it could be used in the
html and mathml code).

However there is some scengraph specific code, that is code to write to
VRML because VRML is a non-XML version of X3D. So by including a outputVRML
function we can support two formats without too much duplication.
\section{XmlAttribute domain}
)endif

)abbrev domain XMLAT XmlAttribute
++ Author: Martin John Baker
++ Date Created: September 2010
++ Basic Operations: writeXml, writeVRML
++ Related Constructors: xmlAttribute, xmlElement
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ http://www.euclideanspace.com/prog/scratchpad/mycode/graph/
++ Description:
++ This package provides support for XML files
++ XML attributes are inside the opening tag:
++ <tagname attributes>
++ and usually have the form: name=value

XmlAttribute() : Exports==Implementation where
 Exports == with
  xmlAttribute : (name : String, value : String) -> %
    ++ xmlAttribute(name, value) construct a xmlAttribute from its name
    ++ and value
  xmlAttribute : (name : String, values : List String) -> %
    ++ xmlAttribute(name, values) constructs a xmlAttribute from its name
    ++ and list of values
  coerce : % -> String
    ++ coerce(x) outputs x as a string
  outputVRML : (rp : %, f1 : TextFile) -> Void
    ++ outputVRML(rp, f1) writes the attribute as VRML2. VRML2 is
    ++ not xml but it has the same semantics and node names as X3D
    ++ but a different syntax so it makes sense to create an XML structure
    ++ and then format the output differently.

 Implementation == add

  Rep := Record(n : String, v : List String)

  -- construct a xmlAttribute from its name and value
  xmlAttribute(name : String, value : String) : % == [name, [value]]

  -- construct a xmlAttribute from its name and list of values
  xmlAttribute(name : String, values : List String) : % == [name, values]

  coerce(rp : %) : String ==
   val:String := ""
   for vn in rp.v repeat
     if val = "" then val := vn
     else val := concat([val," ",vn])$String
   concat([" ",rp.n,concat("=",quote()),concat(val,quote())])$String

  -- outputVRML(rp, f1) writes the attribute as VRML2. VRML2 is
  -- not xml but it has the same semantics and node names as X3D
  -- but a different syntax so it makes sense to create an XML structure
  -- and then format the output differently.
  outputVRML(rp : %, f1 : TextFile) : Void ==
   val:String := ""
   for vn in rp.v repeat
     if val = "" then
       val := vn
     else val := concat([val,",",vn])$String
   writeLine!(f1,concat([rp.n," ",val])$String)
   Void


)if false

\section{XmlElement domain}
)endif

)abbrev domain XMLEL XmlElement
++ Author: Martin John Baker
++ Date Created: September 2010
++ Basic Operations: writeXml, writeVRML
++ Related Constructors: xmlAttribute, xmlElement
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ http://www.euclideanspace.com/prog/scratchpad/mycode/graph/
++ Description:
++ This package provides support for XML files
++ an XML element is something which either has the form:
++ <tagname attributes> content </tagname>
++ or
++ <tagname attributes/>
++ where content may be a sequence of sub-elements or unstructured text

XmlElement() : Exports==Implementation where

 NNI==> NonNegativeInteger

 Exports == with
  xmlElement : (name : String, elements : List %, attributes : List XmlAttribute) -> %
    ++ xmlElement(name, elements, attributes) construct an element with
    ++ structured elements
  xmlElement : (name : String, txt : String, attributes : List XmlAttribute) -> %
    ++ xmlElement(name, txt, attributes) construct an element with
    ++ unstructured text
  empty? : (el : %) -> Boolean
    ++ empty?(el) returns true is this element does not contain anything
  coerce : % -> List String
    ++ coerce(x) outputs x as a list of strings. Each element in the
    ++ list represents a new line. This is an alternative to creating
    ++ a long single string with 'newline' embedded in it.
  outputVRML : (rp : %, f1 : TextFile) -> Void
    ++ outputVRML(rp, f1) writes the element as VRML2. VRML2 is
    ++ not xml but it has the same semantics and node names as X3D
    ++ but a different syntax so it makes sense to create an XML structure
    ++ and then format the output differently.

 Implementation == add
  Rep := Record(n : String, e : List %, a : List XmlAttribute, content : String)
    -- e holds child elements.
    -- a holds attributes.
    -- content holds unstructured data as a string

  -- construct an element with structured elements
  xmlElement(name : String, elements : List %, attributes : List XmlAttribute) : % ==
   [name,elements,attributes,""]

  -- construct an element with unstructured text
  xmlElement(name : String, txt : String, attributes : List XmlAttribute) : % ==
   [name, [], attributes, txt]

  -- local function, used to coerce to String, to output information
  -- structured as xml attributes.
  outputStructured(rp : %) : List String ==
   atts := concat([s::String for s in rp.a ])$String
   -- if there are no child elements then we can out put in the
   -- shortened form as a single tag
   #(rp.e) < 1 =>
    [concat(["<",rp.n,atts," />"])$String]
   -- res will hold start tag, child elements then end tag
   res:List String := [concat(["<",rp.n,atts,">"])$String]
   for el in rp.e repeat
     res := concat(res, el::(List String))$(List String)
   concat(res,concat(["</",rp.n,">"])$String)$(List String)

  -- local function, used to coerce to String, to output information
  -- not structured as xml attributes but as an unstructured string.
  outputUnstructured(rp : %) : List String ==
   atts := concat([s::String for s in rp.a ])$String
   -- start tag
   res:List String := [concat(["<",rp.n,atts,">"])$String]
   -- unstructured data
   res := concat(res, rp.content)$(List String)
   -- end tag
   concat(res,concat(["</",rp.n,">"])$String)$(List String)

  -- coerce(rp) outputs rp as a list of strings. Each element in the
  -- list represents a new line. This is an alternative to creating
  -- a long single string with 'newline' embedded in it.
  coerce(rp : %) : List String ==
   rp.content = "" => outputStructured(rp)
   outputUnstructured(rp)

  -- empty?(el) returns true is this element does not contain anything
  empty?(el : %) : Boolean ==
   if el.n = "" then true else false

  -- outputVRML(rp, f1) writes the element as VRML2. VRML2 is
  -- not xml but it has the same semantics and node names as X3D
  -- but a different syntax so it makes sense to create an XML structure
  -- and then format the output differently.
  outputVRML(rp : %, f1 : TextFile) : Void ==
   if rp.n = "X3D" or rp.n = "Scene" then
     for el in rp.e repeat
       outputVRML(el, f1)
     return Void
   writeLine!(f1,concat(rp.n,"{"))
   if rp.n = "Shape" then writeLine!(f1,"geometry")
   for att in rp.a repeat
     outputVRML(att, f1)$XmlAttribute
   if (#(rp.e))::NNI > 0::NNI then
     if rp.n = "Transform" then writeLine!(f1,"children[")
     for el in rp.e repeat
       outputVRML(el, f1)
     if rp.n = "Transform" then writeLine!(f1,"]")
   writeLine!(f1,"}")
   Void

)if false
\section{ExportXml package}
)endif

)abbrev package XML ExportXml
++ Author: Martin John Baker
++ Date Created: September 2010
++ Basic Operations: writeXml, writeVRML
++ Related Constructors: xmlAttribute, xmlElement
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ http://www.euclideanspace.com/prog/scratchpad/mycode/graph/
++ Description:
++ This package provides support for XML files


ExportXml() : with

  writeXml : (content : XmlElement, filename : String) -> Void
    ++ writeXml(content, filename) writes an xml element and all the
    ++ elements below it to an xml file. This assumes that all
    ++ elements, in a file, have a single root which is usually the case.
  writeVRML : (content : XmlElement, filename : String) -> Void
    ++ writeVRML(content, filename) writes the element as VRML2. VRML2 is
    ++ not xml but it has the same semantics and node names as X3D
    ++ but a different syntax so it makes sense to create an XML structure
    ++ and then format the output differently.

 == add

  -- writeXml(content, filename) writes an xml element and all the
  -- elements below it to an xml file. This assumes that all
  -- elements, in a file, have a single root which is usually the case.
  writeXml(content : XmlElement, filename : String) : Void ==
    f1:TextFile := open(filename::FileName,"output")
    s1:String := "<?xml version="
    s1 := concat(s1, quote())
    s1 := concat(s1,"1.0")
    s1 := concat(s1, quote())
    s1 := concat(s1," encoding=")
    s1 := concat(s1, quote())
    s1 := concat(s1,"UTF-8")
    s1 := concat(s1, quote())
    s1 := concat(s1," standalone=")
    s1 := concat(s1, quote())
    s1 := concat(s1,"no")
    s1 := concat(s1, quote())
    s1 := concat(s1,"?>")
    writeLine!(f1, s1)
    lines := content::List String
    for line in lines repeat
      writeLine!(f1, line)
    close! f1

  -- writeVRML(content, filename) writes the element as VRML2. VRML2 is
  -- not xml but it has the same semantics and node names as X3D
  -- but a different syntax so it makes sense to create an XML structure
  -- and then format the output differently.
  writeVRML(content : XmlElement, filename : String) : Void ==
    f1:TextFile := open(filename::FileName,"output")
    writeLine!(f1,"#VRML V2.0 utf8")
    outputVRML(content, f1)
    close! f1

)if false

\section{Scene domain}
Usually, when programming using SPAD, I try to use a functional programming
style as much as possible, for instance making objects (representations)
immutable. However I don't think it would be practical to implement the
scenegraph in this way. A design requirement is that the scenegraph needs
to hold a large amount of information, A 3D scene might be made from
thousands (perhaps millions?) of polygons. For the sake of efficiency we
need to be able to modify it without copying the whole thing into a new
object for each change.

The object oriented style of programming would seem to be ideal for a
scenegraph. Each type of node in the scenegraph could be a different object
all inheriting from a common object. Unfortunately is is not easy to do
this in SPAD because it does not support polymorphism.

So what I have ended up with is perhaps the worst of both styles of
programming? each node type is distinguished by a different symbol value
in its rep. I find this very messy as it leads to lots of bits of code like this:
\begin{verbatim}
if typ = "ROOT"::Symbol then
...
if typ = "GROUP"::Symbol then
...
if typ = "LINE"::Symbol then
...
\end{verbatim}

so if we want to change a particular node type we have to search through
all these categories, domains and packages to find everywhere it is referred
to and change each occurrence. If we could use object oriented programming
then the code for each node type could be grouped together as a separate
object.
)endif

)abbrev domain SCENE Scene
++ Author: Martin Baker
++ Date Created: September 2010
++ Basic Operations: spnt, sipnt, svec, sivec, screenCoords
++ Related Constructors: SCarteasian, SConformal, SArgand, STransform
++ Also See:
++ AMS Classifications:
++ Keywords:
++ References:
++ http://www.euclideanspace.com/prog/scratchpad/mycode/graph/
++ Description: This domain implements scenegraph.

Scene(PT) : Exports == Implementation where
 PT : SPointCategory

 x<<y ==> hconcat(x::OutputForm,y::OutputForm)
 TR ==> STransform PT
 I ==> Integer
 C ==> Complex DoubleFloat
 NNI==> NonNegativeInteger
 PI==> PositiveInteger
 DF==> DoubleFloat
 SEG ==> Segment DF
 PPC ==> ParametricPlaneCurve(DF -> DF) -- created with curve(f1, f2)
 FCV ==> C -> C -- function of a complex variable
 PSC ==> ParametricSpaceCurve(DF -> DF) -- created with curve(f1, f2, f3)
 PSF ==> ParametricSurface((DF, DF) -> DF) -- created with surface(f1, f2, f3)
 PSFUN ==> (DF, DF) -> PT
 PCFUN ==> DF -> PT
 LINE ==> Record(st : PT, en : PT) -- specifies a straight line by its start and
                        -- end point

 -- each node contains PARAMS in Rep definition, which is a union of
 -- the following, each node type will have different content.
 IFS ==> Record(inx : List List NNI, pts : List PT)
   ++ indexed face set
 TEXT ==> Record(txt : String, siz : NNI, pos : PT, np : List String)
   ++ information for text node
   ++ txt = text to be displayed
   ++ siz = font size
   ++ pos = absolute position
   ++ np = named points
 MATERIAL ==> Record(lineWidth : DF, lineCol : String, fillCol : String, matOpacity : DF)
   ++ a set of attributes which governs the appearance of nodes
   ++ lineWidth: width of line or border
   ++ lineCol: colour of line or border
   ++ fillCol: colour of inside fill
   ++ matOpacity: 1.0 = fully opaque, 0.0 = fully transparent
 LINES ==> List List PT
   ++ holds multiple lines, each line is a list of points.
 ARROWS ==> Record(ln : List List PT, mode : Symbol, size : DF)
   ++ holds multiple arrows, each arrow is defined by a list
   ++ of points.
   ++ 'mode' and 'size' parameters determine the size of the arrowhead.
   ++ mode can have the following values:
   ++   "fixed"::Symbol -- fixed size line width given by 'size' parameter
   ++   "proportional"::Symbol -- size as a proportion of the overall bounds
   ++   "variable"::Symbol -- size as a proportion of the arrow length
   ++ So "proportional" would typically be used when drawing a graph
   ++ (in graph theory) where it looks better if each arrow head is the
   ++ same. "variable" would typically be used when drawing a force field
   ++ where a bigger arrow head might indicate a stronger force.
 ARROW ==> Record(st : String, en : String, offset : PT, mode : Symbol, size : DF)
   ++ holds an arrow, represented by a start points and end point from
   ++ named points together with an offset.
   ++ 'mode' and 'size' parameters determine the size of the arrowhead.
   ++ mode can have the following values:
   ++   "fixed"::Symbol -- fixed size line width given by 'size' parameter
   ++   "proportional"::Symbol -- size as a proportion of the overall bounds
   ++   "variable"::Symbol -- size as a proportion of the arrow length
   ++ So "proportional" would typically be used when drawing a graph
   ++ (in graph theory) where it looks better if each arrow head is the
   ++ same. "variable" would typically be used when drawing a force field
   ++ where a bigger arrow head might indicate a stronger force.
 SHAPE ==> Record(shptype : Symbol, centre : PT, size : PT, fill : Boolean)
   ++ A shape is something like a rectangle or an ellipse in 2 dimensions
   ++ or a sphere or box in 3 dimensions.
   ++ Its main purpose here, especially in 2 dimensions, is to provide a
   ++ way to graphically show sets. That is, the shape could be drawn around
   ++ other elements to show that they are a set. For example we could
   ++ draw Venn diagrams or show how one set of elements maps to another
   ++ set of elements.
   ++ shptype may have the following values:
   ++   "rect"::Symbol
   ++   "ellipse"::Symbol
   ++   "box"::Symbol
   ++   "sphere"::Symbol
   ++ centre determines where the shape is drawn.
   ++ size determines the extent of the shape (which may be different in each
   ++   dimension).
   ++ fill, if set to true, will fill the shape with the current fill colour.
   ++   otherwise, if set to false, the inside of the shape is transparent.
 NODENAME ==> Record(nme : String, node : %)
   ++ This associates a node with a name.
   ++ It is used by the def/use mechanism, that is,
   ++ def: (short for definition) gives a name to a place in the
   ++      scenegraph.
   ++ use: uses a part of the scenegraph previously defined.
 TRANS ==> TR
   ++ holds transform parameters
 PARAMS ==> Union(points : LINES, material : MATERIAL, text : TEXT, _
                 boundbox : SBoundary(PT), trans : TRANS, ifs : IFS, _
                 arrws : ARROWS, arrw : ARROW, shpe : SHAPE, _
                 nodename : NODENAME, _
                 np:SceneNamedPoints PT,empty:"empty")
   ++ these are the parameters that can be set for nodes of
   ++ various types

 Exports == with
  createSceneRoot : () -> %
    ++ createSceneRoot() constructs the root node, all other nodes
    ++ are contained in a tree structure under this node
  createSceneRoot : (bb : SBoundary(PT)) -> %
    ++ createSceneRoot(bb) constructs the root node, all other
    ++ nodes are contained in a tree structure under this node
  createSceneRoot : (minx : I, miny : I, maxx : I, maxy : I) -> %
    ++ createSceneRoot(minx, miny, maxx, maxy) constructs
    ++ the root node, all other nodes are contained in
    ++ a tree structure under this node
  createSceneGroup : () -> %
    ++ createSceneGroup() constructs a group node, this node
    ++ does not do anything itself but contains other nodes
  addSceneGroup : (n : %) -> %
    ++ addSceneGroup(n) is a convenience function which combines
    ++ createSceneGroup with addChild!
  createSceneLine : (line : List PT) -> %
    ++ createSceneLine(line) constructs a line node, this contains
    ++ a line (represented by a list of points) in n-dimensional
    ++ space. The space is implicit in the PT and TR parameters
    ++ supplied when this Scene was created.
  addSceneLine : (n : %, line : List PT) -> %
    ++ addSceneLine(n, line) is a convenience function which
    ++ combines createSceneLine with addChild!
  createSceneLine : (st : %, en : %, fontScale : DF) -> %
    ++ createSceneLine(st, en, fontScale) constructs a line node, that
    ++ contains a line, in n-dimensions, from st to en.
    ++ 'st' and 'en' are nodes in the scene and the line will be drawn
    ++ upto their boundary.
    ++ st is the node at the start of the line
    ++ en is the node at the end of the line
    ++ fontScale should be set to the full width of the diagram (not
    ++ just the width of this node). If this is not known then choose
    ++ an approximate value. This only affects text nodes, if st or en,
    ++ or its subnodes, do not contain a text node then its
    ++ value is not significant.
  addSceneLine : (n : %, st : %, en : %, fontScale : DF) -> %
    ++ addSceneLine(n, st, en, fontScale) is a convenience function which
    ++ combines createSceneLine with addChild!
  createSceneLine : (st : %, en : %, fontScale : DF,
                     shortenLine : Boolean) -> %
    ++ createSceneLine(st, en, fontScale, shortenLine) constructs a line node,
    ++ that contains a line, in n-dimensions, from st to en.
    ++ 'st' and 'en' are nodes in the scene and the line will be drawn
    ++ upto their boundary.
    ++ st is the node at the start of the line
    ++ en is the node at the end of the line
    ++ fontScale should be set to the full width of the diagram (not
    ++ just the width of this node). If this is not known then choose
    ++ an approximate value. This only affects text nodes, if st or en,
    ++ or its subnodes, do not contain a text node then its
    ++ value is not significant.
    ++ shortenLine if true line drawn upto boundary rather than
    ++             centre of destination.
  addSceneLine : (n : %, st : %, en : %, fontScale : DF,
                  shortenLine : Boolean) -> %
    ++ addSceneLine(n, st, en, fontScale) is a convenience function which
    ++ combines createSceneLine with addChild!
  createSceneLines : (line : LINES) -> %
    ++ createSceneLines(line) constructs a line node, this contains lines
    ++ (represented by a list of points) in n-dimensional space.
    ++ The space is implicit in the PT and TR parameters supplied
    ++ when this Scene was created.
  addSceneLines : (n : %, line : LINES) -> %
    ++ addSceneLines(n, line) is a convenience function which combines
    ++ createSceneLines with addChild!
  createSceneShape : (shape : SHAPE) -> %
    ++ createSceneShape(shape) constructs a shape node, this contains
    ++ a shape such as a rectangle or an ellipse.
  addSceneShape : (n : %, shape : SHAPE) -> %
    ++ addSceneShape(n, shape) is a convenience function which combines
    ++ createSceneShape with addChild!
  createSceneShape : (shape : SBoundary(PT)) -> %
    ++ createSceneShape(shape) constructs a shape node from a boundary,
    ++ this contains a shape such as a rectangle or an ellipse.
  addSceneShape : (n : %, shape : SBoundary(PT)) -> %
    ++ addSceneShape(n, shape) is a convenience function which combines
    ++ createSceneShape with addChild!
  createSceneDef : (nam : String, nde : %) -> %
    ++ createSceneDef(nam, nde) defines a point in the scenegraph
    ++ so that it can be used elsewhere.
  addSceneDef : (n : %, nam : String, nde : %) -> %
    ++ addSceneDef(n, nam, nde) is a convenience function which
    ++ combines createSceneDef with addChild!
  createSceneUse : (nam : String) -> %
    ++ createSceneUse(nam) uses another point in the scenegraph.
  addSceneUse : (n : %, nam : String) -> %
    ++ addSceneUse(n, nam) is a convenience function which
    ++ combines createSceneUse with addChild!
  createSceneArrows : (line : List List PT, mode : Symbol, size : DF) -> %
    ++ createSceneArrows(line, mode, size) constructs an arrow node,
    ++ this contains arrows (represented by a
    ++ list of points) in n-dimensional space. The space is implicit
    ++ in the PT and TR parameters supplied when this Scene was
    ++ created.
    ++ 'mode' and 'size' parameters determine the size of the arrowhead.
    ++ mode can have the following values:
    ++   "fixed"::Symbol -- fixed size line width given by 'size' parameter
    ++   "proportional"::Symbol -- size as a proportion of the overall bounds
    ++   "variable"::Symbol -- size as a proportion of the arrow length
    ++ So "proportional" would typically be used when drawing a graph
    ++ (in graph theory) where it looks better if each arrow head is the
    ++ same. "variable" would typically be used when drawing a force field
    ++ where a bigger arrow head might indicate a stronger force.
  addSceneArrows : (n : %, line : List List PT, mode : Symbol, size : DF) -> %
    ++ addSceneArrows(n, line, mode, size) is a convenience function which
    ++ combines createSceneArrows with addChild!
  createSceneArrow : (st : String, en : String, offset : PT, mode : Symbol, size : DF) -> %
    ++ createSceneArrow(st, en, offset, mode, size) constructs an arrow node,
    ++ in n-dimensional space, going from st to en.
    ++ 'st' and 'en' are determined from a list of points from named points
    ++ node.
    ++ st is the start of the arrow
    ++ en is the end of the arrow
    ++ 'mode' and 'size' parameters determine the size of the arrowhead.
    ++ mode can have the following values:
    ++   "fixed"::Symbol -- fixed size line width given by 'size' parameter
    ++   "proportional"::Symbol -- size as a proportion of the overall bounds
    ++   "variable"::Symbol -- size as a proportion of the arrow length
    ++ So "proportional" would typically be used when drawing a graph
    ++ (in graph theory) where it looks better if each arrow head is the
    ++ same. "variable" would typically be used when drawing a force field
    ++ where a bigger arrow head might indicate a stronger force.
  addSceneArrow : (n : %, st : String, en : String, offset : PT, mode : Symbol, size : DF) -> %
    ++ addSceneArrow(n, st, en, offset, mode, size) is a convenience function
    ++ which combines createSceneArrow with addChild!
  createSceneArrow : (st : %, en : %, offset : PT, mode : Symbol, size : DF, fontScale : DF) -> %
    ++ createSceneArrow(st, en, offset, mode, size, fontScale) constructs an arrow node,
    ++ in n-dimensional space, going from st to en.
    ++ 'st' and 'en' are nodes in the scene and the arrows will be drawn
    ++ upto their boundary.
    ++ st is the node at the start of the arrow
    ++ en is the node at the end of the arrow
    ++ 'mode' and 'size' parameters determine the size of the arrowhead.
    ++ mode can have the following values:
    ++   "fixed"::Symbol -- fixed size line width given by 'size' parameter
    ++   "proportional"::Symbol -- size as a proportion of the overall bounds
    ++   "variable"::Symbol -- size as a proportion of the arrow length
    ++ So "proportional" would typically be used when drawing a graph
    ++ (in graph theory) where it looks better if each arrow head is the
    ++ same. "variable" would typically be used when drawing a force field
    ++ where a bigger arrow head might indicate a stronger force.
    ++ fontScale should be set to the full width of the diagram (not
    ++ just the width of this node). If this is not known then choose
    ++ an approximate value. This only affects text nodes, if st or en,
    ++ or its subnodes, do not contain a text node then its
    ++ value is not significant.
  addSceneArrow : (n : %, st : %, en : %, offset : PT, mode : Symbol, size : DF, fontScale : DF) -> %
    ++ addSceneArrow(n, st, en, offset, mode, size, fontScale) is a convenience function
    ++ which combines createSceneArrow with addChild!
  createSceneNamedPoints : (np : SceneNamedPoints PT) -> %
    ++ createSceneNamedPoints(np) constructs a named points node, this
    ++ allows us to define a set of points which can be used multiple
    ++ times in the scenegraph.
  addSceneNamedPoints : (n : %, np : SceneNamedPoints PT) -> %
    ++ addSceneNamedPoints(n, np) is a convenience function which
    ++ combines createSceneNamedPoints with addChild!
  createSceneIFS : (inx : List List NNI, pts : List PT) -> %
    ++ createSceneIFS(inx, pts) constructs an indexed face set node, this
    ++ defines a surface represented by a set of polygons in n-dimensional
    ++ space.
  addSceneIFS : (n : %, inx : List List NNI, pts : List PT) -> %
    ++ addSceneIFS(n, inx, pts) is a convenience function which combines
    ++ createSceneIFS with addChild!
  createSceneIFS : (in1 : SceneIFS(PT)) -> %
    ++ createSceneIFS(in1) constructs an indexed face set node, this
    ++ defines a surface represented by a set of polygons in n-dimensional
    ++ space.
  addSceneIFS : (n : %, in1 : SceneIFS(PT)) -> %
    ++ addSceneIFS(n, in1) is a convenience function which combines
    ++ createSceneIFS with addChild!
  createSceneBox : (size : DF) -> %
    ++ createSceneBox(size) constructs an indexed face set node
    ++ which is a 3D box of a given size
  addSceneBox : (n : %, size : DF) -> %
    ++ addSceneBox(n, size) is a convenience function which
    ++ combines createSceneBox with addChild!
  createSceneText : (text : TEXT) -> %
    ++ createSceneText(text) constructs a text node, text can
    ++ be used for labelling anything such as graphs, axes and so on.
  addSceneText : (n : %, text : TEXT) -> %
    ++ addSceneText(n, text) is a convenience function which combines
    ++ createSceneText with addChild!
  createSceneText : (str : String, sz : NNI, pz : PT) -> %
    ++ createSceneText(str, sz, pz) constructs a text node, text can be
    ++ used for labelling anything such as graphs, axes and so on.
  addSceneText : (n : %, str : String, sz : NNI, pz : PT) -> %
    ++ addSceneText(n, str, sz, pz) is a convenience function which combines
    ++ createSceneText with addChild!
  createSceneText : (str : List String, sz : NNI, pz : PT) -> %
    ++ createSceneText(str, sz, pz) constructs a text node using
    ++ information from named points node.
  addSceneText : (n : %, str : List String, sz : NNI, pz : PT) -> %
    ++ addSceneText(n, str, sz, pz) is a convenience function which
    ++ combines createSceneText with addChild!
  createSceneClip : (bb : SBoundary(PT)) -> %
    ++ createSceneClip(bb) constructs a clip node, clips its sub
    ++ nodes in the coordinate system in force at the clip node.
  addSceneClip : (n : %, bb : SBoundary(PT)) -> %
    ++ addSceneClip(n, bb) is a convenience function which combines
    ++ createSceneClip with addChild!
  createSceneGrid : (stepSize : DF, bb : SBoundary(PT)) -> %
    ++ createSceneGrid(stepSize, bb) constructs a grid, consists of a
    ++ group, under which are vertical and horizontal lines. The
    ++ spacing between lines is given by step.
  addSceneGrid : (n : %, stepSize : DF, bb : SBoundary(PT)) -> %
    ++ addSceneGrid(n, stepSize, bb) is a convenience
    ++ function which combines createSceneGrid with addChild!
  createSceneGrid : (bb : SBoundary(PT)) -> %
    ++ createSceneGrid(bb) constructs a grid with
    ++ narrow blue lines every 20 units
    ++ wide blue lines every 100 units
    ++ wide red lines every 200 units
  addSceneGrid : (n : %, bb : SBoundary(PT)) -> %
    ++ addSceneGrid(n, bb) is a convenience function which
    ++ combines createSceneGrid with addChild!
  createScenePattern : (ptype : Symbol, step : NNI, bb : SBoundary(PT)) -> %
    ++ createScenePattern(ptype, step, bb) creates a test pattern
    ++ suitable for showing the effect of
    ++ transforms. The pattern depends on ptype parameter as follows:
    ++ ptype="GRID"::Symbol : construct a set of horizontal and vertical
    ++ lines in the current
    ++ clip boundary and current material with a spacing between
    ++ lines given by the step parameter.
    ++ ptype="SIERPINSKI"::Symbol: constructs a Sierpinski fractal.
    ++ step parameter gives the level of subdivision.
    ++ ptype="HOUSE"::Symbol: constructs a house shape.
  addScenePattern : (n : %, ptype : Symbol, step : NNI, bb : SBoundary(PT)) -> %
    ++ addScenePattern(n, ptype, step, bb) is a convenience function
    ++ which combines createScenePattern with addChild!
  createSceneRuler : (ptype : Symbol, offset : PT, bb : SBoundary(PT)) -> %
    ++ createSceneRuler(ptype, offset, bb) creates a scale that can
    ++ be used to provide numeric values for an axis:
    ++ ptype= "HORIZONTAL"::Symbol : horizontal axis.
    ++ ptype= "VERTICAL"::Symbol: vertical axis.
    ++ ptype= "DEBTH"::Symbol: depth axis.
  addSceneRuler : (n : %, ptype : Symbol, offset : PT, bb : SBoundary(PT)) -> %
    ++ addSceneRuler(n, ptype, offset, bb) is a convenience function
    ++ which combines createSceneRuler with addChild!
  createSceneGraph : (g : DirectedGraph(String), x : NNI, y : NNI, dispArrowName : Boolean) -> %
    ++ createSceneGraph(g, x, y, dispArrowName) creates a graph theory
    ++ diagram
  addSceneGraph : (n : %, g : DirectedGraph(String), x : NNI, y : NNI, dispArrowName : Boolean) -> %
    ++ addSceneGraph(n, g, x, y, dispArrowName) is a convenience function
    ++ which combines createSceneGraph with addChild!
  createSceneMaterial : (mat : MATERIAL) -> %
    ++ createSceneMaterial(mat) constructs a material node
    ++ This sets the lineWidth, lineCol and fillCol for all nodes under
    ++ this, unless overridden by another material node.
    ++ That is the material parameters that apply to a given node are
    ++ those of the closest material node above it in the hierarchy
    ++ line width is expressed relative to the width of current boundary
  addSceneMaterial : (n : %, mat : MATERIAL) -> %
    ++ addSceneMaterial(n, mat) is a convenience function which combines
    ++ createSceneMaterial with addChild!
  createSceneMaterial : (lineW : DF, lineC : String, fillC : String) -> %
    ++ createSceneMaterial(lineW, lineC, fillC) constructs a material node
    ++ This sets the lineWidth, lineCol and fillCol for all nodes under
    ++ this, unless overridden by another material node.
    ++ That is the material parameters that apply to a given node are
    ++ those of the closest material node above it in the hierarchy
  addSceneMaterial : (n : %, lineW : DF, lineC : String, fillC : String) -> %
    ++ addSceneMaterial(n, lineW, lineC, fillC) is a convenience function
    ++ which combines createSceneMaterial with addChild!
  createSceneTransform : (tran : TR) -> %
    ++ createSceneTransform(tran) constructs a transform node
    ++ This transforms the points and vectors below this node
    ++ If a given node has more than one transform node above
    ++ it in the hierarchy then the transforms are compound
  addSceneTransform : (n : %, tran : TR) -> %
    ++ addSceneTransform(n, tran) is a convenience function
    ++ which combines createSceneTransform with addChild!
  createPlot1Din2D : (f : DF -> PT, tRange : SEG, numPts : NNI) -> %
    ++ createPlot1Din2D(f, tRange, numPts) creates a two dimensional
    ++ function plot draws the graph from a mapping of float to point
    ++ as x ranges from min(a, b) to max(a, b).
    ++ tRange is a..b
  addPlot1Din2D : (n : %, f : DF -> PT, tRange : SEG, numPts : NNI) -> %
    ++ addPlot1Din2D(n, f, tRange, numPts) is a convenience function
    ++ which combines createPlot1Din2D with addChild!
  createPlot1Din2D : (f : DF -> DF, tRange : SEG, numPts : NNI) -> %
    ++ createPlot1Din2D(f, tRange, numPts) creates two
    ++ dimensional function plot. Draws the graph of y = f(x) as x
    ++ ranges from min(a, b) to max(a, b).
    ++ tRange is a..b
  addPlot1Din2D : (n : %, f : DF -> DF, tRange : SEG, numPts : NNI) -> %
    ++ addPlot1Din2D(n, f, tRange, numPts) is a convenience function
    ++ which combines createPlot1Din2D with addChild!
  createPlot1Din2Dparametric : (PPC, tRange : SEG, numPts : NNI) -> %
    ++ createPlot1Din2Dparametric(ppc, tRange, numPts) creates a node
    ++ from plot using Parametric Plane Curve. This represents 1
    ++ dimension (line - possibly curved) in 2 dimensions (plane)
    ++ In theory a line has no width but in that case we would not see it
    ++ so we give it a width given by the material node that is
    ++ applicable in this part of the scene graph
    ++ PPC is ParametricPlaneCurve(DF -> DF) which is created with
    ++ curve(f1, f2)
    ++ where f1 and f2 are functions of type ComponentFunction, in this
    ++ case DF -> DF
  addPlot1Din2Dparametric : (n : %, PPC, tRange : SEG, numPts : NNI) -> %
    ++ addPlot1Din2Dparametric(n, ppc, tRange, numPts) is a convenience
    ++ function which combines createPlot1Din2Dparametric with addChild!
  createPlot1Din3Dparametric : (PSC, tRange : SEG, numPts : NNI) -> %
    ++ createPlot1Din3Dparametric(psc, tRange, numPts) create a line
    ++ (1D subspace) in 3D space. This represents 1 dimension
    ++ (line - possibly curved) in 3 dimensions
    ++ In theory a line has no width but in that case we would not see it
    ++ so we give it a width given by the material node that is
    ++ applicable in this part of the scene graph
    ++ PSC ParametricSpaceCurve(DF -> DF) is created with curve(f1, f2, f3)
    ++ where f1, f2 and f3 are functions of type ComponentFunction, in this
    ++ case DF -> DF
  addPlot1Din3Dparametric : (n : %, PSC, tRange : SEG, numPts : NNI) -> %
    ++ addPlot1Din3Dparametric(n, psc, tRange, numPts) is a convenience
    ++ function which combines createPlot1Din3Dparametric with addChild!
  createPlot1Din3Dparametric : (PCFUN, tRange : SEG, numPts : NNI) -> %
    ++ createPlot1Din3Dparametric(pcfun, tRange, numPts) create a line
    ++ (1D subspace) in 3D space.
    ++ This represents 1 dimension (line - possibly curved) in 3 dimensions
    ++ In theory a line has no width but in that case we would not see it
    ++ so we give it a width given by the material node that is
    ++ applicable in this part of the scene graph
    ++ PCFUN is a function from float to point: DF -> PT
  addPlot1Din3Dparametric : (n : %, PCFUN, tRange : SEG, numPts : NNI) -> %
    ++ addPlot1Din3Dparametric(n, pcfun, tRange, numPts) is a
    ++ convenience function which combines createPlot1Din3Dparametric
    ++ with addChild!
  createPlot2Din3D : (ptFun : PSFUN, uSeg : SEG, vSeg : SEG, numPts : NNI) -> %
    ++ createPlot2Din3D(ptFun, uSeg, vSeg, numPts) create a surface
    ++ (2D subspace) in 3D space.
    ++ The surface is approximated by polygons which are
    ++ represented by in indexed face set (IFS) node
  createPlot2Din3D : ((DF, DF) -> DF, uSeg : SEG, vSeg : SEG, numPts : NNI) -> %
    ++ createPlot2Din3D(f, a..b, c..d) returns a scene node
    ++ which contains the graph of z = f(x, y)
    ++ as x ranges from min(a, b) to max(a, b) and y ranges from
    ++ min(c, d) to max(c, d).
    ++ create a surface (2D subspace) in 3D space
    ++ The surface is approximated by polygons which are
    ++ represented by in indexed face set (IFS) node
  addPlot2Din3D : (n : %, (DF, DF) -> DF, uSeg : SEG, vSeg : SEG, numPts : NNI) -> %
    ++ addPlot2Din3D(n, f, uSeg, vSeg, numPts) is a convenience
    ++ function which combines createPlot2Din3D with addChild!
  createPlot2Din3Dparametric : (PSFUN, uSeg : SEG, vSeg : SEG, numPts : NNI) -> %
    ++ createPlot2Din3Dparametric(f, a..b, c..d, l) returns a scene node
    ++ which contains the graph of the parametric surface f(u, v)
    ++ as u ranges from min(a, b) to
    ++ max(a, b) and v ranges from min(c, d) to max(c, d).
    ++ This creates a surface (2D subspace) in 3D space
    ++ The surface is approximated by polygons which are
    ++ represented by in indexed face set (IFS) node
  addPlot2Din3Dparametric : (n : %, PSFUN, uSeg : SEG, vSeg : SEG, numPts : NNI) -> %
    ++ addPlot2Din3Dparametric(n, f, uSeg, vSeg, numPts) is
    ++ a convenience function which combines createPlot2Din3Dparametric with
    ++ addChild!
  createPlot2Din3Dparametric : (PSF, uSeg : SEG, vSeg : SEG, numPts : NNI) -> %
    ++ createPlot2Din3Dparametric(surface(f, g, h), a..b, c..d, l) returns a
    ++ scene node which contains the
    ++ graph of the parametric surface x = f(u, v), y = g(u, v),
    ++ z = h(u, v) as u ranges from min(a, b) to
    ++ max(a, b) and v ranges from min(c, d) to max(c, d).
    ++ This creates a surface (2D subspace) in 3D space
    ++ The surface is approximated by polygons which are
    ++ represented by in indexed face set (IFS) node
  addPlot2Din3Dparametric : (n : %, PSF, uSeg : SEG, vSeg : SEG, numPts : NNI) -> %
    ++ addPlot2Din3Dparametric(n, f, uSeg, vSeg, numPts) is a convenience
    ++ function which combines createPlot2Din3Dparametric with addChild!
  createArrows2Din2D : (ptFun : PT -> PT, uSeg : SEG, vSeg : SEG, numPts : NNI) -> %
    ++ createArrows2Din2D(ptFun, uSeg, vSeg, numPts) creates arrows to
    ++ represent output for each input point
    ++ in other words this represents a two dimensional force
    ++ field with the arrows showing the direction of the field
  addArrows2Din2D : (n : %, ptFun : PT -> PT, uSeg : SEG, vSeg : SEG, numPts : NNI) -> %
    ++ addArrows2Din2D(n, ptFun, uSeg, vSeg, numPts) is a convenience function
    ++ which combines createArrows2Din2D with addChild!
  boundary : (n : %, fontScale : DF) -> SBoundary(PT)
    ++ boundary(n, fontScale) returns the boundary of a given node, so if this
    ++ is called on the root node, it will return the boundary of the
    ++ whole scene.
    ++ There is a difficulty involving text nodes, that is they have
    ++ a fixed font size. But we want to calculate the boundary in local
    ++ coordinates, to do this conversion we need to know the total
    ++ boundary size but that is what we are trying to calculate! To get
    ++ round this contradiction the parameter fontScale is required since
    ++ fonts are rendered at a fixed size we need a scaling factor so
    ++ that we can give the boundary in our coordinate system.
    ++ fontScale should be set to the full width of the diagram (not
    ++ just the width of this node). If this is not known then choose
    ++ an approximate value. This only affects text nodes, if this
    ++ node, or its subnodes, do not contain a text node then its
    ++ value is not significant.
  addChild! : (n : %, c : %) -> Void
    ++ addChild!(n, c) adds the specified child node 'c' below node 'n'
  removeChild! : (n : %, c : %) -> Void
    ++ removeChild!(n, c) removes the specified child node 'c'
    ++ below node 'n'
  setTransform! : (n : %, tran : TR) -> Void
    ++ setTransform!(n, tran) changes the transform without altering
    ++ the scene hierarchy
  toSVG : (n : %, mat : MATERIAL, tran : TR, bb : SBoundary(PT), _
         scale : DF, clipEn : Boolean, _
         useInteger : Boolean, npt : SceneNamedPoints PT) -> XmlElement
    ++ toSVG(n, mat, tran, bb, scale, clipEn, useInteger, npt) creates an
    ++ XmlElement containing a 'SVG' representation of node
    ++ 'n' and the nodes below it.
    ++ n is the scene to be output to XML elements
    ++ mat is the default material which may be overridden by subnodes
    ++ tran is the default transform which may be overridden by subnodes
    ++ bb is the default boundary which may be overridden by subnodes
    ++ scale is the scale used to scale into canvas, it would be better
    ++       to merge this into trans.
    ++ clipEn if true this clips the output to the boundary
    ++ useInteger if true truncate to integer values.
    ++ npt names points can be provided. If not required then set
    ++     to namedBranch([], [])
  toX3D : (n : %, mat : MATERIAL, tran : TR, bb : SBoundary(PT)) -> XmlElement
    ++ toX3D(n, mat, tran, bb) creates an XmlElement containing a
    ++ 'X3D' representation of node 'n' and the nodes below it.
    ++ n is the scene to be output to XML elements
    ++ mat is the default material which may be overridden by subnodes
    ++ tran is the default transform which may be overridden by subnodes
    ++ bb is the default boundary which may be overridden by subnodes
  toObj : (n : %, ptLst : Reference List PT, _
         indexLst : Reference List List NNI, _
         indexNxt : Reference NNI, tran : TR, bb : SBoundary(PT)) -> Void
    ++ toObj(n, ptLst, indexLst, indexNxt, tran, bb) creates an .OBJ
    ++ (Wavefront) file from scenegraph tree structure
    ++ called recursively for each node, so when called on root node
    ++ in scenegraph all other nodes in the scenegraph will get called.
    ++ n is the scene to be output to ptLst and indexLst
    ++ ptLst is the points. This will be set to ref([]) at the start
    ++       and will be built up as this function is called recursively
    ++ indexLst is list of faces, where faces are list of points indexed
    ++          by ptLst This will be set to ref([]) at the start
    ++       and will be built up as this function is called recursively
    ++ tran is the default transform which may be overridden by subnodes
    ++ bb is the default boundary which may be overridden by subnodes
  writeSvg : (n : %, filename : String) -> Void
    ++ writeSvg(n, filename) writes an 'SVG' representation of node
    ++ 'n' to the filename supplied.
  writeSvgQuantised : (n : %, filename : String) -> Void
    ++ writeSvgQuantised(n, filename) writes an 'SVG' representation
    ++ of node 'n' to the filename supplied.
  writeX3d : (n : %, filename : String) -> Void
    ++ writeX3d: (n, filename) writes an 'X3D' representation of
    ++ node 'n' to the filename supplied.
  writeVRML : (n : %, filename : String) -> Void
    ++ writeVRML(n, filename) writes an 'VRML' representation of
    ++ node 'n' to the filename supplied.
  writeObj : (n : %, filename : String) -> Void
    ++ writeObj(n, filename) writes an 'OBJ' (Wavefront) representation
    ++ of node 'n' to the filename supplied.
  coerce : (n : %) -> OutputForm
    ++ output
    ++ a full scenegraph could contain a lot of data and so we have
    ++ to be careful to restrict the output to what is reasonable on
    ++ the command line. Therefore we do not display all subnodes or
    ++ the full information from nodes such as IFS.

 Implementation == add

  Rep := Record(type : Symbol, children : List %, parameters : PARAMS)
    ++ this represents a tree structure where each node in the
    ++ tree can have:
    ++ * a type
    ++ * children
    ++ * some parameters
    ++ valid types are:
    ++ type = "ROOT"::Symbol -- root node
    ++ type = "GROUP"::Symbol -- group node
    ++ type = "LINE"::Symbol -- line node
    ++ type = "SHAPE"::Symbol -- shape node
    ++ type = "MATERIAL"::Symbol -- material node
    ++ type = "TEXT"::Symbol -- text node
    ++ type = "TRANSFORM"::Symbol -- transform node
    ++ type = "CLIP"::Symbol -- clip node
    ++ type = "IFS"::Symbol -- Indexed Face Set
    ++ type = "ARROWS"::Symbol -- arrows node
    ++ type = "ARROW"::Symbol -- single arrow node
    ++ type = "NAMEDPOINTS"::Symbol -- named points node
    ++ type = "DEF"::Symbol -- def node
    ++ type = "USE"::Symbol -- use node

  math_to_string ==> mathObject2String$Lisp

  -- Constructs the root node, all other nodes are contained in
  -- a tree structure under this node
  createSceneRoot(bb : SBoundary(PT)) : % == ["ROOT"::Symbol,[],[bb]]

  -- Constructs the root node, all other nodes are contained in
  -- a tree structure under this node
  createSceneRoot(minx : I, miny : I, maxx : I, maxy : I) : % ==
    bb : SBoundary(PT) := boxBoundary(sipnt(minx, miny)$PT, sipnt(maxx, maxy)$PT)
    ["ROOT"::Symbol,[],[bb]]

  -- Constructs the root node, all other nodes are contained in
  -- a tree structure under this node
  createSceneRoot() : % ==
    ["ROOT"::Symbol,[],[nullBoundary()]]

  -- Constructs a group node, this node does not do anything itself
  -- but contains other nodes
  createSceneGroup() : % == ["GROUP"::Symbol,[],["empty"]]

  -- a convenience function which combines createSceneGroup with addChild!
  addSceneGroup(n : %) : % ==
    c := createSceneGroup()
    addChild!(n, c)
    c

  -- Constructs a line node, this contains a line (represented by a
  -- list of points) in n-dimensional space. The space is implicit
  -- in the PT and TR parameters supplied when this Scene was
  -- created.
  createSceneLine(line : List PT) : % == ["LINE"::Symbol,[],[[line]]]

  -- a convenience function which combines createSceneLine with addChild!
  addSceneLine(n : %, line : List PT) : % ==
    c := createSceneLine(line)
    addChild!(n, c)
    c

  -- createSceneLine(st, en) constructs a line node, this contains
  -- a line, in n-dimensions, from st to en.
  -- 'st' and 'en' are nodes in the scene and the line will be drawn
  -- upto their boundary.
  -- st is the node at the start of the arrow
  -- en is the node at the end of the arrow
  createSceneLine(st : %, en : %, fontScale : DF) : % ==
    createSceneLine(st, en, fontScale, true)

  -- addSceneLine(n, st, en) is a convenience function which
  -- combines createSceneLine with addChild!
  addSceneLine(n : %, st : %, en : %, fontScale : DF) : % ==
    addSceneLine(n , st, en, fontScale, true)

  -- createSceneLine(st, en) constructs a line node, this contains
  -- a line, in n-dimensions, from st to en.
  -- 'st' and 'en' are start and end nodes of the line in the scene.
  -- st is the node at the start of the arrow.
  -- en is the node at the end of the arrow.
  -- fontScale allows the boundary of text to be calculated, it should be
  -- set to the full width of the diagram.
  -- shortenLine if true line drawn upto boundary rather than
  --             centre of destination.
  createSceneLine(st : %, en : %, fontScale : DF, shortenLine:Boolean) : % ==
    stBoundary : SBoundary(PT) := boundary(st, fontScale)
    enBoundary : SBoundary(PT) := boundary(en, fontScale)
    stPoint := getCentre(stBoundary)
    enPoint := getCentre(enBoundary)
    if shortenLine then
      stPoint := lineIntersect(enBoundary, stPoint)
      enPoint := lineIntersect(stBoundary, enPoint)
    --print("createSceneLine2 start="::OutputForm << stPoint << _
    --      " end="::OutputForm << enPoint)
    ["LINE"::Symbol,[],[[[stPoint,enPoint]]]]

  -- addSceneLine(n, st, en) is a convenience function which
  -- combines createSceneLine with addChild!
  addSceneLine(n : %, st : %, en : %, fontScale : DF,
               shortenLine : Boolean) : % ==
      c := createSceneLine(st, en, fontScale, shortenLine)
      addChild!(n, c)
      c

  -- Constructs a line node, this contains lines (represented by a
  -- list of points) in n-dimensional space. The space is implicit
  -- in the PT and TR parameters supplied when this Scene was
  -- created.
  createSceneLines(lines : LINES) : % == ["LINE"::Symbol,[],[lines]]

  -- a convenience function which combines createSceneLines with addChild!
  addSceneLines(n : %, lines : LINES) : % ==
    c := createSceneLines(lines)
    addChild!(n, c)
    c

  -- Constructs a shape node, this contains a shape such as a rectangle
  -- or an ellipse.
  -- SHAPE ==> Record(shptype: Symbol, centre: PT, size: PT, fill: Boolean)
  createSceneShape(shape : SHAPE) : % ==
    ["SHAPE"::Symbol,[],[shape]]

  -- a convenience function which combines createSceneShape with addChild!
  addSceneShape(n : %, shape : SHAPE) : % ==
    c := createSceneShape(shape)
    addChild!(n, c)
    c

  -- Constructs a shape node from a boundary, this contains a shape
  -- such as a rectangle or an ellipse.
  createSceneShape(shape : SBoundary(PT)) : % ==
    mx : PT := getMax(shape)
    mn : PT := getMin(shape)
    sh:SHAPE := ["rect"::Symbol,mn,mx-mn,false]
    --if isEllipse?(shape) then
    --  sh := ["rect"::Symbol,mn,mx,false]
    --t:% := ["SHAPE"::Symbol,[],[sh]]
    --print(hconcat(["createSceneShape shape="::OutputForm,_
    --       shape::OutputForm, _
    --       " getMin(shape)="::OutputForm,mn::OutputForm,_
    --       " getMax(shape)="::OutputForm,mx::OutputForm,_
    --       " t="::OutputForm,t::OutputForm]))
    ["SHAPE"::Symbol,[],[sh]]

  -- a convenience function which combines createSceneShape with addChild!
  addSceneShape(n : %, shape : SBoundary(PT)) : % ==
    c := createSceneShape(shape)
    addChild!(n, c)
    c

  -- Defines a point in the scenegraph so that it can be used elsewhere.
  createSceneDef(nam : String, nde : %) : % ==
    nn : NODENAME := [nam, nde]
    ["DEF"::Symbol,[],[nn]]

  -- a convenience function which combines createSceneDef with addChild!
  addSceneDef(n : %, nam : String, nde : %) : % ==
    c := createSceneDef(nam, nde)
    addChild!(n, c)
    c

  -- Uses another point in the scenegraph.
  createSceneUse(nam : String) : % ==
    nn:NODENAME := [nam,["GROUP"::Symbol,[],["empty"]]]
    ["USE"::Symbol,[],[nn]]

  -- a convenience function which combines createSceneUse with addChild!
  addSceneUse(n : %, nam : String) : % ==
    c := createSceneUse(nam)
    addChild!(n, c)
    c

  -- Constructs an arrow node, this contains arrows (represented by a
  -- list of points) in n-dimensional space. The space is implicit
  -- in the PT and TR parameters supplied when this Scene was
  -- created.
  -- 'mode' and 'size' parameters determine the size of the arrowhead.
  -- 'mode' can have the following values:
  --   "fixed"::Symbol -- fixed size line width given by 'size' parameter
  --   "proportional"::Symbol -- size as a proportion of the overall bounds
  --   "variable"::Symbol -- size as a proportion of the arrow length
  -- So "proportional" would typically be used when drawing a graph
  -- (in graph theory) where it looks better if each arrow head is the
  -- same. "variable" would typically be used when drawing a force field
  -- where a bigger arrow head might indicate a stronger force.
  createSceneArrows(lines : List List PT, mode : Symbol, size : DF) : % ==
    ar : ARROWS := [lines, mode, size]
    ["ARROWS"::Symbol,[],[ar]]

  -- a convenience function which combines createSceneArrows with addChild!
  addSceneArrows(n : %, lines : List List PT, mode : Symbol, size : DF) : % ==
    c := createSceneArrows(lines, mode, size)
    addChild!(n, c)
    c

  -- Constructs an arrow node, this contains arrows (represented by a
  -- list of points from named points node.) in n-dimensional space.
  -- 'st' and 'en' are nodes in the scene and the arrows will be drawn
  -- upto their boundary.
  -- st is the start of the arrow
  -- en is the end of the arrow
   -- 'mode' and 'size' parameters determine the size of the arrowhead.
  -- 'mode' can have the following values:
  --   "fixed"::Symbol -- fixed size line width given by 'size' parameter
  --   "proportional"::Symbol -- size as a proportion of the overall bounds
  --   "variable"::Symbol -- size as a proportion of the arrow length
  -- So "proportional" would typically be used when drawing a graph
  -- (in graph theory) where it looks better if each arrow head is the
  -- same. "variable" would typically be used when drawing a force field
  -- where a bigger arrow head might indicate a stronger force.
  createSceneArrow(st : String, en : String, offset : PT, mode : Symbol, size : DF) : % ==
    ar : ARROW := [st, en, offset, mode, size]
    ["ARROW"::Symbol,[],[ar]]

  -- a convenience function which combines createSceneArrows with addChild!
  addSceneArrow(n : %, st : String, en : String, offset : PT, mode : Symbol, size : DF) : % ==
    c := createSceneArrow(st, en, offset, mode, size)
    addChild!(n, c)
    c

  -- createSceneArrow(st, en, offset, mode, size) constructs an arrow node,
  -- in n-dimensional space, going from st to en.
  -- 'st' and 'en' are nodes in the scene and the arrows will be drawn
  -- upto their boundary.
  -- st is the start of the arrow
  -- en is the end of the arrow
  -- 'mode' and 'size' parameters determine the size of the arrowhead.
  -- mode can have the following values:
  --   "fixed"::Symbol -- fixed size line width given by 'size' parameter
  --   "proportional"::Symbol -- size as a proportion of the overall bounds
  --   "variable"::Symbol -- size as a proportion of the arrow length
  -- So "proportional" would typically be used when drawing a graph
  -- (in graph theory) where it looks better if each arrow head is the
  -- same. "variable" would typically be used when drawing a force field
  -- where a bigger arrow head might indicate a stronger force.
  createSceneArrow(st : %, en : %, offset : PT, mode : Symbol, size : DF, fontScale : DF) ==
    stBoundary : SBoundary(PT) := boundary(st, fontScale)
    enBoundary : SBoundary(PT) := boundary(en, fontScale)
    stPoint := lineIntersect(stBoundary, getCentre(enBoundary))
    enPoint := lineIntersect(enBoundary, getCentre(stBoundary))
    --print(hconcat(["scene createSceneArrow stBoundary="::OutputForm,stBoundary::OutputForm,_
    -- " enBoundary="::OutputForm,enBoundary::OutputForm,_
    -- " stPoint="::OutputForm,stPoint::OutputForm,_
    -- " enPoint="::OutputForm,enPoint::OutputForm]))$OutputForm
    ar : ARROWS := [[[stPoint, enPoint]], mode, size]
    ["ARROWS"::Symbol,[],[ar]]

  -- addSceneArrow(n, st, en, offset, mode, size) is a convenience function
  -- which combines createSceneArrow with addChild!
  addSceneArrow(n : %, st : %, en : %, offset : PT, mode : Symbol, size : DF, fontScale : DF) ==
    c := createSceneArrow(st, en, offset, mode, size, fontScale)
    addChild!(n, c)
    c

  -- Constructs a named points node, this allows us to define a
  -- set of points which can be used multiple times in the
  -- scenegraph.
  createSceneNamedPoints(np : SceneNamedPoints PT) : % ==
    ["NAMEDPOINTS"::Symbol,[],[np]]

  -- a convenience function which combines createSceneNamedPoints
  -- with addChild!
  addSceneNamedPoints(n : %, np : SceneNamedPoints PT) : % ==
    c := createSceneNamedPoints(np)
    addChild!(n, c)
    c

  -- Constructs an indexed face set node, this defines a surface
  -- represented by a set of polygons in n-dimensional space.
  createSceneIFS(inx1 : List List NNI, pts1 : List PT) : % ==
    ["IFS"::Symbol,[],[[inx1,pts1]]]

  -- a convenience function which combines createSceneIFS with addChild!
  addSceneIFS(n : %, inx1 : List List NNI, pts1 : List PT) : % ==
    c := createSceneIFS(inx1, pts1)
    addChild!(n, c)
    c

  -- Constructs an indexed face set node, this defines a surface
  -- represented by a set of polygons in n-dimensional space.
  createSceneIFS(in1 : SceneIFS(PT)) : % ==
    inx1 : List List NNI := indexes(in1)
    pts1 : List PT := pointList(in1)
    ["IFS"::Symbol,[],[[inx1,pts1]]]

  -- a convenience function which combines createSceneIFS with addChild!
  addSceneIFS(n : %, in1 : SceneIFS(PT)) : % ==
    c := createSceneIFS(in1)
    addChild!(n, c)
    c

  -- Constructs an indexed face set node which is a 3D box of a
  -- given size
  createSceneBox(size : DF) : % ==
    pts : List PT := [_
      spnt(size, -size, size)$PT, _
      spnt(size, size, size)$PT, _
      spnt(-size, size, size)$PT, _
      spnt(-size, -size, size)$PT, _
      spnt(-size, -size, -size)$PT, _
      spnt(-size, size, -size)$PT, _
      spnt(size, size, -size)$PT, _
      spnt(size, -size, -size)$PT_
      ]
    inx : List List NNI := [_
      [0::NNI, 1::NNI, 2::NNI, 3::NNI], _
      [4::NNI, 5::NNI, 6::NNI, 7::NNI], _
      [7::NNI, 6::NNI, 1::NNI, 0::NNI], _
      [3::NNI, 2::NNI, 5::NNI, 4::NNI], _
      [1::NNI, 6::NNI, 5::NNI, 2::NNI], _
      [3::NNI, 4::NNI, 7::NNI, 0::NNI]]
    createSceneIFS(inx, pts)

  -- a convenience function which combines createSceneBox with addChild!
  addSceneBox(n : %, size : DF) : % ==
    c := createSceneBox(size)
    addChild!(n, c)
    c

  -- Constructs a text node, text can be used for labelling anything
  -- such as graphs, axes and so on.
  createSceneText(text : TEXT) : % == ["TEXT"::Symbol,[],[text]]

  -- a convenience function which combines createSceneText with addChild!
  addSceneText(n : %, text : TEXT) : % ==
    c := createSceneText(text)
    addChild!(n, c)
    c

  -- Constructs a text node, text can be used for labelling anything
  -- such as graphs, axes and so on.
  createSceneText(str : String, sz : NNI, pz : PT) : % ==
    text : TEXT := [str, sz, pz, []]
    ["TEXT"::Symbol,[],[text]]

  -- a convenience function which combines createSceneText with addChild!
  addSceneText(n : %, str : String, sz : NNI, pz : PT) : % ==
    c := createSceneText(str, sz, pz)
    addChild!(n, c)
    c

  -- Constructs a text node using information from named
  -- points node.
  createSceneText(str : List String, sz : NNI, pz : PT) : % ==
    text : TEXT := [first str, sz, pz, str]
    ["TEXT"::Symbol,[],[text]]

  -- a convenience function which combines createSceneText with addChild!
  addSceneText(n : %, str : List String, sz : NNI, pz : PT) : % ==
    c := createSceneText(str, sz, pz)
    addChild!(n, c)
    c

  -- Constructs a clip node, clips its sub nodes in the coordinate
  -- system in force at the clip node.
  createSceneClip(bb : SBoundary(PT)) : % ==
    ["CLIP"::Symbol,[],[bb]]

  -- a convenience function which combines createSceneClip with addChild!
  addSceneClip(n : %, bb : SBoundary(PT)) : % ==
    c := createSceneClip(bb)
    addChild!(n, c)
    c

  -- construct a grid in the current clip boundary and current material
  -- with a spacing between lines given by the step parameter
  createSceneGrid(stepSize : DF, bb : SBoundary(PT)) : % ==
    minx : DF := screenCoordX(getMin(bb))
    miny : DF := screenCoordY(getMin(bb))
    maxx : DF := screenCoordX(getMax(bb))
    maxy : DF := screenCoordY(getMax(bb))
    stepsx : NNI := wholePart((maxx - minx)/stepSize)::NNI
    stepsy : NNI := wholePart((maxy - miny)/stepSize)::NNI
    gp := createSceneGroup()
    -- vertical lines
    for i in 1..stepsx repeat
      ln := addSceneLine(gp, [spnt(minx+(i::DF)*stepSize, miny), _
            spnt(minx+(i::DF)*stepSize, maxy)])
    -- horizontal lines
    for i in 1..stepsy repeat
      ln := addSceneLine(gp, [spnt(minx, miny+(i::DF)*stepSize), _
            spnt(maxx, miny+(i::DF)*stepSize)])
    gp

  -- a convenience function which combines createSceneGrid with addChild!
  addSceneGrid(n : %, stepSize : DF, bb : SBoundary(PT)) : % ==
    c := createSceneGrid(stepSize, bb)
    addChild!(n, c)
    c

  -- construct a grid with
  -- narrow blue lines every 20 units
  -- wide blue lines every 100 units
  -- wide red lines every 200 units
  createSceneGrid(bb : SBoundary(PT)) : % ==
    minx : DF := screenCoordX(getMin(bb))
    maxx : DF := screenCoordX(getMax(bb))
    stepSize : DF := (maxx-minx)/100
    gp := createSceneGroup()
    mt1 := addSceneMaterial(gp,[1::DF,"blue","blue",0.5::DF])
    addSceneGrid(mt1, stepSize, bb)
    mt2 := addSceneMaterial(gp,[2::DF,"blue","blue",0.5::DF])
    addSceneGrid(mt2, stepSize*5::DF, bb)
    mt3 := addSceneMaterial(gp,[2::DF,"red","red",0.5::DF])
    addSceneGrid(mt3, stepSize*10::DF, bb)
    gp

  -- a convenience function which combines createSceneGrid with addChild!
  addSceneGrid(n : %, bb : SBoundary(PT)) : % ==
    c := createSceneGrid(bb)
    addChild!(n, c)
    c

  -- local function used by createScenePattern to
  -- construct a grid in the current clip boundary and current material
  -- with a spacing between lines given by the step parameter
  createScenePattern1(step : NNI, bb : SBoundary(PT)) : % ==
    minx : DF := screenCoordX(getMin(bb))
    miny : DF := screenCoordY(getMin(bb))
    maxx : DF := screenCoordX(getMax(bb))
    stepSize : DF := (maxx - minx)/1200
    gp := createSceneGroup()
    mt1 := addSceneMaterial(gp,[2::DF,"blue","blue",0.5::DF])
    --addChild!(gp, mt1)
    for i in 1..1200 by step repeat
      pts : List PT := [spnt(minx+(i::DF)*stepSize, miny+(j::DF)*stepSize)_
                       for j in 1..1200 by step]
      ln := addSceneLine(mt1, pts)
      --addChild!(mt1, ln)
    mt2 := addSceneMaterial(gp,[2::DF,"red","red",0.5::DF])
    --addChild!(gp, mt2)
    for i in 1..1000 by step repeat
      pts : List PT := [spnt(minx+(j::DF)*stepSize, miny+(i::DF)*stepSize)_
                       for j in 1..1200 by step]
      ln := addSceneLine(mt2, pts)
      --addChild!(mt2, ln)
    gp

  -- local function used by createScenePattern to
  -- construct a Sierpinski fractal.
  createScenePattern2(level : NNI, bb : SBoundary(PT)) : % ==
    minx : DF := screenCoordX(getMin(bb))
    miny : DF := screenCoordY(getMin(bb))
    maxx : DF := screenCoordX(getMax(bb))
    maxy : DF := screenCoordY(getMax(bb))
    midx : DF := (minx+maxx)::DF * 0.5::DF
    face1 : List PT := [spnt(midx, miny), _
                      spnt(minx, maxy), _
                      spnt(maxx, maxy)]
    ifs : SceneIFS(PT) := singleFace(face1)
    ifs2 : SceneIFS(PT) := sierpinskiDivide(ifs, level)
    createSceneIFS(indexes(ifs2), pointList(ifs2))

  subdivideLine(level : NNI, inLine : List PT) : List PT ==
    level = 0 => inLine
    res : List PT := []
    lastPt : PT
    for x in 1..#inLine repeat
      if x ~= 1 then
        midpt : PT := 0.5::DF * (lastPt+inLine.x)::PT
        res := concat(res, midpt)
      lastPt := inLine.x
      res := concat(res, inLine.x)
    subdivideLine((level-1)::NNI, res)

  -- local function used by createScenePattern to
  -- construct a house shape.
  createScenePattern3(level : NNI, bb : SBoundary(PT)) : % ==
    minx : DF := screenCoordX(getMin(bb))
    miny : DF := screenCoordY(getMin(bb))
    maxx : DF := screenCoordX(getMax(bb))
    scale : DF := (maxx - minx)/1000
    xcoords : List List NNI := [[0, 0, 500, 1000, 1000, 0], _
                [150, 350, 350, 150, 150], _
                [100, 400, 400, 100, 100], _
                [600, 900, 900, 600, 600], _
                [600, 900, 900, 600, 600]]
    ycoords : List List NNI := [[0, 800, 1000, 800, 0, 0], _
                [0, 0, 300, 300, 0], _
                [500, 500, 700, 700, 500], _
                [500, 500, 700, 700, 500], _
                [100, 100, 300, 300, 100]]
    pts : List List PT := [[_
      spnt(minx+((((xcoords.i).j)::DF))*scale, _
           miny+((((ycoords.i).j)::DF))*scale)$PT_
             for j in 1..(#(xcoords.i))] for i in 1..(#xcoords)]
    pts2 : List List PT := []
    lev2 : NNI := level
    for l2 in pts repeat
      pts2 := concat(pts2, subdivideLine(lev2, l2))
      if lev2 = level then lev2 := 2
    ln := createSceneLines(pts2)

  -- creates a test pattern suitable for showing the effect of
  -- transforms. The pattern depends on ptype parameter as follows:
  -- ptype="GRID"::Symbol : construct a set of horizontal and vertical
  -- lines in the current
  -- clip boundary and current material with a spacing between
  -- lines given by the step parameter.
  -- ptype="SIERPINSKI"::Symbol: constructs a Sierpinski fractal.
  -- step parameter gives the level of subdivision.
  -- ptype="HOUSE"::Symbol: constructs a house shape.
  createScenePattern(ptype : Symbol, step : NNI, bb : SBoundary(PT)) : % ==
    if ptype = "GRID"::Symbol then return createScenePattern1(step,bb)
    if ptype = "SIERPINSKI"::Symbol then return createScenePattern2(step,bb)
    createScenePattern3(step, bb)

  -- a convenience function which combines createScenePattern with addChild!
  addScenePattern(n : %, ptype : Symbol, step : NNI, bb : SBoundary(PT)) : % ==
    c := createScenePattern(ptype, step, bb)
    addChild!(n, c)
    c

  -- creates a scale that can be used to provide numeric values
  -- for an axis:
  -- ptype= "HORIZONTAL"::Symbol : horizontal axis.
  -- ptype= "VERTICAL"::Symbol: vertical axis.
  -- ptype= "DEBTH"::Symbol: depth axis.
  createSceneRuler(ptype : Symbol, offset : PT, bb : SBoundary(PT)) : % ==
    minPrimary : DF := screenCoordX(getMin(bb))
    maxPrimary : DF := screenCoordX(getMax(bb))
    maxSecondary : DF := screenCoordY(getMax(bb))
    if ptype = "VERTICAL"::Symbol then
      minPrimary := screenCoordY(getMin(bb))
      maxPrimary := screenCoordY(getMax(bb))
      maxSecondary := screenCoordX(getMax(bb))
    stepSize : DF := maxPrimary - minPrimary
    expStep : Integer := wholePart(log10(stepSize+1)) - 1
    divn : DF := (10::DF) ^expStep
    minPrimaryNorm : DF := minPrimary/divn
    -- should now be scaled between 1 and 10
    maxPrimaryNorm : DF := maxPrimary/divn
    suffix:String := ""
    zeroes : Integer := expStep
    if zeroes > 2 then -- kilo
      zeroes := zeroes - 3
      suffix := "K"
      if zeroes > 2 then -- mega
        zeroes := zeroes - 3
        suffix := "M"
        if zeroes > 2 then -- giga
          zeroes := zeroes - 3
          suffix := "G"
          if zeroes > 2 then -- tera
            zeroes := zeroes - 3
            suffix := "T"
    if zeroes < 0 then -- mili
      zeroes := zeroes + 3
      suffix := "m"
      if zeroes < 0 then -- micro
        zeroes := zeroes + 3
        suffix := "u"
        if zeroes < 0 then -- nano
          zeroes := zeroes + 3
          suffix := "n"
          if zeroes < 0 then -- pico
            zeroes := zeroes + 3
            suffix := "p"
    if zeroes > 0 then
      suffix := concat("0",suffix)
    if zeroes > 1 then
      suffix := concat("0",suffix)
    gp := createSceneGroup()
    stepI:Integer := if ptype = "HORIZONTAL"::Symbol then 2 else 1
    for x in wholePart(minPrimaryNorm)..wholePart(maxPrimaryNorm) by stepI repeat
      str : String := (math_to_string x)@String
      pz : PT := spnt((x::DF)*divn, 0$DF)$PT + offset
      if ptype = "VERTICAL"::Symbol then
        pz := spnt(0$DF, (x::DF)*divn)$PT + offset
      if expStep = -1 then
        if x>0 and x<10 then str := concat("0.",str)
        if x = 10 then str := "1"
        if x>10 then
          d := divide(x, 10)
          str := concat([string(d.quotient),".",string(d.remainder)])
        if x<0 and x>-10 then str := concat(["-0.",(math_to_string (-x))@String])$String
        if x=-10 then str := "-1"
        if x< -10 then
          d := divide(-x, 10)
          str := concat(["-",string(d.quotient),".",string(d.remainder)])
      else if x ~= 0 then str := concat(str, suffix)
      addSceneText(gp, str, 10, pz)
    gp

  -- a convenience function which combines createSceneRuler with
  -- addChild!
  addSceneRuler(n : %, ptype : Symbol, offset : PT, bb : SBoundary(PT)) : % ==
    c := createSceneRuler(ptype, offset, bb)
    addChild!(n, c)
    c

  -- createSceneGraph(g, x, y, dispArrowName) creates a graph theory
  -- diagram
  createSceneGraph(g : DirectedGraph(String), x : NNI, y : NNI, dispArrowName : Boolean) : % ==
    tr := createSceneTransform(_
        stranslate(x::DF, y::DF, 0::DF, 0.25::DF, 0.25::DF, 0.25::DF))$Scene(SCartesian 2)
    --print(hconcat(["scene createSceneGraph g="::OutputForm,g::OutputForm,_
    --  " x="::OutputForm,x::OutputForm," y="::OutputForm,y::OutputForm,_
    --  " dispArrowName="::OutputForm,dispArrowName::OutputForm]))$OutputForm
    subdiagramSvg(tr, g, dispArrowName, false)$DirectedGraph(String)
    tr pretend %

  -- createSceneGraph(n, g, x, y, dispArrowName) is a convenience function
  -- which combines createSceneGraph with addChild!
  addSceneGraph(n : %, g : DirectedGraph(String), x : NNI, y : NNI, dispArrowName : Boolean) : % ==
    c := createSceneGraph(g, x, y, dispArrowName)
    addChild!(n, c)
    c

  -- Constructs a material node
  -- This sets the lineWidth, lineCol and fillCol for all nodes under
  -- this, unless overridden by another material node.
  -- That is the material parameters that apply to a given node are
  -- those of the closest material node above it in the hierarchy
  createSceneMaterial(mat : MATERIAL) : % ==
    ["MATERIAL"::Symbol,[],[mat]]

  -- a convenience function which combines createSceneMaterial with
  -- addChild!
  addSceneMaterial(n : %, mat : MATERIAL) : % ==
    c := createSceneMaterial(mat)
    addChild!(n, c)
    c

  -- Constructs a material node
  -- This sets the lineWidth, lineCol and fillCol for all nodes under
  -- this, unless overridden by another material node.
  -- That is the material parameters that apply to a given node are
  -- those of the closest material node above it in the hierarchy
  createSceneMaterial(lineW : DF, lineC : String, fillC : String) : % ==
    mat : MATERIAL := [lineW, lineC, fillC, 1::DF]
    ["MATERIAL"::Symbol,[],[mat]]

  -- a convenience function which combines createSceneMaterial with
  -- addChild!
  addSceneMaterial(n : %, lineW : DF, lineC : String, fillC : String) : % ==
    c := createSceneMaterial(lineW, lineC, fillC)
    addChild!(n, c)
    c

  -- Constructs a transform node
  -- This transforms the points and vectors below this node
  -- If a given node has more than one transform node above
  -- it in the hierarchy then the transforms are compounded
  createSceneTransform(tran : TR) : % ==
    ["TRANSFORM"::Symbol,[],[tran]]

  -- a convenience function which combines createSceneTransform with
  -- addChild!
  addSceneTransform(n : %, tran : TR) : % ==
    c := createSceneTransform(tran)
    addChild!(n, c)
    c

  -- creates arrows to represent output for each input point
  -- in other words this represents a two dimensional force
  -- field with the arrows showing the direction of the field
  createArrows2Din2D(ptFun : PT -> PT, uSeg : SEG, vSeg : SEG, numPts : NNI) : % ==
    arrows : List List PT := []
    ustep := (low(uSeg) - high(uSeg))/numPts
    vstep := (low(vSeg) - high(vSeg))/numPts
    someV := high(vSeg)
    for iv in numPts..0 by -1 repeat
      if zero? iv then someV := low(vSeg)
      -- hack: get last number in segment within segment
      lp : List PT := []
      someU := high(uSeg)
      for iu in numPts..0 by -1 repeat
        if zero? iu then someU := low(uSeg)
        -- hack: get last number in segment within segment
        inPt := spnt(someU, someV)$PT
        outpt := ptFun(inPt)
        --here we need to check for NaN (not a number)
        --numberCheck pt
        lp := [inPt, outpt]
        someU := someU + ustep
        arrows := concat(lp, arrows)
      someV := someV + vstep
    createSceneArrows(arrows,"variable"::Symbol,0.5::DF)

  -- a convenience function which combines createArrows2Din2D with addChild!
  addArrows2Din2D(n : %, ptFun : PT -> PT, uSeg : SEG, vSeg : SEG, numPts : NNI) : % ==
    c := createArrows2Din2D(ptFun, uSeg, vSeg, numPts)
    addChild!(n, c)
    c

  -- add the specified child node 'c' below node 'n'
  addChild!(n : %, c : %) : Void ==
    n.children := concat(n.children, c)

  -- remove the specified child node 'c' below node 'n'
  -- I can't get this to compile
  removeChild!(n : %, c : %) : Void ==
    --n.children := remove(c, n.children)$List %
    error "removeChild! not yet implemented"

  -- changes the transform without altering the scene hierarchy
  setTransform!(n : %, tran : TR) : Void ==
    n.type ~= "TRANSFORM"::Symbol => error "use setTransform! on transform only"
    n.parameters := [tran]

  -- the following functions: pathString, lineArrow, faceString, toSVG
  -- and writeSvg are used to write to a SVG file.

  -- this returns a string to represent a path in a SVG file
  pathString(pts : LINES, tran : TR, bb : SBoundary(PT), sc : DF, clipEn : Boolean, _
             useInteger : Boolean) : String ==
    ptStr:String := "" -- string to hold result
    pntNum : NNI -- hold current position in parameters
    lastValid : Boolean := true -- used to detect first valid entry
    -- pts: LINES is type List List PT where PT
    -- is SPointCategory(DoubleFloat)
    for line in pts repeat
      pntNum := 0::NNI
      for param in line repeat
        -- convert to SVG (always positive) coordinates
        -- param is type SPointCategory(DoubleFloat)
        -- which is what applyTrans requires
        param2 := xform(tran, param pretend PT)$TR
        -- check if original 'global' coordinates (before being converted
        -- to svg - always positive coordinates) are in bounds.
        valid := (not clipEn) or containsPoint?(bb, param)
        if valid then -- add to string
          pntNum := pntNum + 1
          if ptStr ~= "" then ptStr := concat(ptStr," ")
          if lastValid then
            if pntNum = 1 then ptStr := concat(ptStr,"M")
            if pntNum = 2 then ptStr := concat(ptStr,"L")
          if not lastValid then ptStr := concat(ptStr,"M")
          -- add coordinates, separated by comma, to string. Note that
          -- in svg y coordinate starts from top and positive direction
          -- is down so we need to negate that to conform with common
          -- mathematical usage.
          minusy : DF := -screenCoordY(param2)*sc
          if useInteger then
            thisStr : String := concat([_
             string(wholePart(screenCoordX(param2)*sc)),",",_
             string(wholePart(minusy))])$String
          else
            thisStr : String := concat([_
             (math_to_string (screenCoordX(param2)*sc))@String,",",_
             (math_to_string minusy)@String])$String
          ptStr := concat(ptStr, thisStr)
        lastValid := valid
    ptStr

  -- local function called by lineArrow to shorten the length of an arrow
  -- typically this is needed when drawing a graph (as in graph theory)
  -- so that the arrow stops just short of the vertex and so doesn't go
  -- through the vertex label.
  shorternArrow(pts : LINE, bb : SBoundary(PT)) : LINE ==
    totalwidth : DF := screenCoordX(getMax(bb) - getMin(bb))
    startpt := pts.st
    endpt := pts.en
    arrLength : DF := distance(startpt, endpt)
    -- next prevent divide by zero error in cases where arrow is
    -- zero length
    if arrLength < 0.1::DF then
      return pts
    reductionFactor : DF := totalwidth/((40.0::DF)*arrLength)
    -- next if reduction factor is too large then it probably means
    -- the arrow is short compared to the size of the boundary so
    -- the best thing here is probably to not reduce it at all.
    if reductionFactor > 0.4::DF then return pts
    relpt :=  endpt - startpt
    newstart := startpt + (_*(reductionFactor, relpt))
    newend := startpt + (_*(1.0::DF - reductionFactor, relpt))
    [newstart, newend]

  -- local function called by toSVG
  -- this returns an list of XML elements, each of which represents an arrow
  -- 'mode' and 'size' parameters determine the size of the arrowhead.
  -- 'mode' can have the following values:
  -- "fixed"::Symbol -- fixed size line width given by 'size' parameter
  -- "proportional"::Symbol -- size as a proportion of the overall bounds
  -- "variable"::Symbol -- size as a proportion of the arrow length
  -- So "proportional" would typically be used when drawing a graph
  -- (in graph theory) where it looks better if each arrow head is the
  -- same. "variable" would typically be used when drawing a force field
  -- where a bigger arrow head might indicate a stronger force.
  lineArrow(pts : LINES, tran : TR, bb : SBoundary(PT), mat : MATERIAL, sc : DF, clipEn : Boolean, _
            mode : Symbol, size : DF) : List XmlElement ==
    nodeEles : List XmlElement := []
    pntNum : NNI -- hold current position in parameters
    -- pts: LINES is type List List PT where PT
    -- is SPointCategory(DoubleFloat)
    for line in pts repeat
      pntNum := 0::NNI
      ptStr:String := "" -- string to hold result
      startPoint : PT := sipnt(0, 0)
      endPoint : PT := sipnt(0, 0)
      for param in line repeat
        -- convert to SVG (always positive) coordinates
        -- param is type SPointCategory(DoubleFloat)
        -- which is what applyTrans requires
        param2 := xform(tran, param pretend PT)$TR
        --print(hconcat(["scene lineArrow pntNum="::OutputForm,pntNum::OutputForm,_
        -- " param="::OutputForm,param::OutputForm,_
        -- " paramXfmd="::OutputForm,param2::OutputForm,_
        -- " mode="::OutputForm,mode::OutputForm]))$OutputForm
        -- check if original 'global' coordinates (before being converted
        -- to svg - always positive coordinates) are in bounds.
        valid := (not clipEn) or containsPoint?(bb, param)
        if valid then -- add to string
          pntNum := pntNum + 1
          if ptStr ~= "" then ptStr := concat(ptStr," ")
          if pntNum = 1 then
            ptStr := concat(ptStr,"M")
            startPoint := param2
          if pntNum = 2 then
            endPoint := param2
            aline : LINE := [startPoint, endPoint]
            if mode = "proportional"::Symbol then
              aline : LINE := shorternArrow([startPoint, endPoint], bb)
            -- add coordinates, separated by comma, to string. Note that
            -- in svg y coordinate starts from top and positive direction
            -- is down so we need to negate that to conform with common
            -- mathematical usage.
            minusStarty : DF := -screenCoordY(aline.st)*sc
            minusEndy : DF := -screenCoordY(aline.en)*sc
            thisStr : String := concat([_
              (math_to_string (screenCoordX(aline.st)*sc))@String,",",_
              (math_to_string minusStarty),"L",_
              (math_to_string (screenCoordX(aline.en)*sc))@String,",",_
              (math_to_string minusEndy)@String])$String
            ptStr := concat(ptStr, thisStr)
          if pntNum > 2 then
            endPoint := param2
            minusEndy : DF := -screenCoordY(endPoint)*sc
            thisStr : String := concat([_
              "L",(math_to_string (screenCoordX(endPoint)*sc))@String,",",_
              (math_to_string minusEndy)@String])$String
            ptStr := concat(ptStr, thisStr)
      if ptStr ~= "" then -- add a new arrow element
        -- first check if arrow starts and ends at the same place and if so draw a loop
        if distance(startPoint, endPoint) < 0.01::DF then
          -- draw loop
          -- c x1, y1 x2, y2 x, y+ draws a cubic Bezier curve (relative)
          -- Draws a cubic Bezier curve from the current point to (x, y) using
          -- (x1, y1) as the control point at the beginning of the curve and
          -- (x2, y2) as the control point at the end of the curve.
          ptStr := concat(["M ",_
            math_to_string (screenCoordX(startPoint)*sc),",",_
            math_to_string (-screenCoordY(startPoint)*sc), _
            "c -50,25 -50,-50 0,-25"])$String
        linWidth : DF := 2::DF
        if mode = "proportional"::Symbol then
          linWidth : DF := screenCoordX(getMax(bb) - getMin(bb))*size
        if mode = "variable"::Symbol then
          linWidth : DF := distance(startPoint, endPoint)*size
        nodeAtts : List XmlAttribute := [_
          xmlAttribute("d",ptStr),_
          xmlAttribute("fill","none"),_
          xmlAttribute("stroke",mat.lineCol),_
          xmlAttribute("stroke-width",_
          (math_to_string linWidth)@String), _
          xmlAttribute("style","marker-end:url(#Arrow)")_
            ]
        --  xmlAttribute("sodipodi:nodetypes","cc"),
        x : XmlElement := xmlElement("path",[],nodeAtts)
        nodeEles := concat(nodeEles, x)
    nodeEles

  -- this returns a string to represent a face in a SVG file
  faceString(faces : IFS, tran : TR, bb : SBoundary(PT), sc : DF, clipEn : Boolean) : String ==
    ptStr:String := "" -- string to hold result
    pntNum : NNI -- hold current position in parameters
    lastValid : Boolean := true -- used to detect first valid entry
    for ln in faces.inx repeat
      pntNum := 0::NNI
      for i in ln repeat
        param := (faces.pts).(i+1)
        param2 := xform(tran, param pretend PT)$TR
        -- check if original 'global' coordinates (before being converted
        -- to svg - always positive coordinates) are in bounds.
        valid := (not clipEn) or containsPoint?(bb, param)
        if valid then -- add to string
          pntNum := pntNum + 1
          if ptStr ~= "" then ptStr := concat(ptStr," ")
          if lastValid then
            if pntNum = 1 then ptStr := concat(ptStr,"M")
            if pntNum = 2 then ptStr := concat(ptStr,"L")
          if not lastValid then ptStr := concat(ptStr,"M")
          -- add coordinates, separated by comma, to string. Note that
          -- in svg y coordinate starts from top and positive direction
          -- is down so we need to negate that to conform with common
          -- mathematical usage.
          minusy : DF := -screenCoordY(param2)*sc
          thisStr : String := concat([_
             (math_to_string (screenCoordX(param2)*sc))@String,",",_
             (math_to_string minusy)@String])$String
          ptStr := concat(ptStr, thisStr)
        lastValid := valid
      ptStr := concat(ptStr,"z") -- make closed
    ptStr

  -- creates an XML tree structure (for SVG) from scenegraph tree
  -- structure
  -- called recursively for each node, so when called on root node
  -- in scenegraph all other nodes in the scenegraph will get called.
  -- svg file
  -- alternative ways to draw line:
  -- polyline:
  -- <polyline points="x1,y1 x2,y2"/>
  -- paths:
  -- <path d="*"/>
  -- where *:
  -- M0, 0 = move to 0, 0 (absolute)
  -- m0, 0 = move to 0, 0 (relative)
  -- L100, 0z = line to 100, 0 (absolute)
  -- l100, 0z = line to 100, 0 (relative)
  -- z after last point in line indicates closed shape
  -- C x1, y1 x2, y2 x, y+ draws a cubic Bezier curve (absolute)
  -- c x1, y1 x2, y2 x, y+ draws a cubic Bezier curve (relative)
  -- Draws a cubic Bezier curve from the current point to (x, y) using
  -- (x1, y1) as the control point at the beginning of the curve and
  -- (x2, y2) as the control point at the end of the curve.
  toSVG(n : %, mat : MATERIAL, tran : TR, bb : SBoundary(PT), scale : DF, clipEn : Boolean, _
        useInteger : Boolean, npt : SceneNamedPoints PT) : XmlElement ==
    -- I'm not sure if it is safe to modify parameters that have been
    -- passed to this function, so just to be sure, take copies of them:
    bb2 := bb ; tran2 := tran ; mat2 := mat ; np2 := npt
    clipEn2 := clipEn ; scale2 := scale
    -- XML elements and attributes to be constructed in
    -- this function
    nodeEles : List XmlElement := []
    nodeAtts : List XmlAttribute := []
    -- SVG nodename
    nodeName:String := ""
    --print("scene toSvg type=" << n.type << " parameters=" << _
    --    n.parameters << " bb=" << bb << " scale=" << scale)
    if n.type = "ROOT"::Symbol then -- root node
      nodeName := "svg" -- root node
      -- define arrow shape
      pthAtts := [_
        xmlAttribute("d",_
        "M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "),_
        xmlAttribute("style",_
        "fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"),_
        xmlAttribute("transform","scale(0.4) rotate(180) translate(10,0)")_
          ]
      pth := xmlElement("path",[],pthAtts)
      mkrAtts := [_
        xmlAttribute("orient","auto"),_
        xmlAttribute("refY","0.0"),_
        xmlAttribute("refX","0.0"),_
        xmlAttribute("id","Arrow"),_
        xmlAttribute("style","overflow:visible")_
          ]
      mkr := xmlElement("marker",[pth],mkrAtts)
      nodeEles := [xmlElement("defs",[mkr],[])]
      if not (n.parameters case boundbox) then
        error "toSVG parameter type not valid for root node"
      -- setup default transform. This makes sure view bounds are always
      -- positive and thy the 'y' direction is from down to up.
      bb2 := n.parameters.boundbox
      if isNull?(bb2) then bb2 := boundary(n, 1$DF)
      if isNull?(bb2) then error "scene contains no drawable elements"
      minx : DF := screenCoordX(getMin(bb2))
      miny : DF := screenCoordY(getMin(bb2))
      maxx : DF := screenCoordX(getMax(bb2))
      maxy : DF := screenCoordY(getMax(bb2))
      scale2 : DF := (1000::DF)/(maxx-minx)
      offsetx : DF := -minx
      offsety : DF := -maxy
      tran2 := stranslate(offsetx, offsety, 0::DF, 1::DF, 1::DF, 1::DF)$TR
      if useInteger then
        viewBoxStr : String := concat([_
          "0 0 ",_
          (math_to_string (wholePart((maxx-minx)*scale2)))@String, _
          " ",_
          (math_to_string (wholePart((maxy-miny)*scale2)))@String])
      else
        viewBoxStr : String := concat([_
          "0.0 0.0 ",_
          (math_to_string ((maxx-minx)*scale2))@String, _
          " ",_
          (math_to_string ((maxy-miny)*scale2))@String])
      nodeAtts := [xmlAttribute("xmlns","http://www.w3.org/2000/svg"),_
        xmlAttribute("width","12cm"),_
        xmlAttribute("height","8cm"),_
        xmlAttribute("viewBox",viewBoxStr)_
         ]
    if n.type = "GROUP"::Symbol then nodeName := "g" -- group node
    if n.type = "LINE"::Symbol then -- line node
      if not (n.parameters case points) then
        error "toSVG parameter type not valid for line node"
      nodeName := "path" -- line node
      nodeAtts := [_
        xmlAttribute("d",pathString(n.parameters.points,tran2,bb2,scale2,clipEn2,useInteger)),_
        xmlAttribute("fill","none"),_
        xmlAttribute("stroke",mat2.lineCol),_
        xmlAttribute("stroke-width",(math_to_string mat2.lineWidth)@String),_
        xmlAttribute("stroke-linecap","butt"),_
        xmlAttribute("stroke-linejoin","miter")_
          ]
      -- print("scene toSVC LINE nodeName="::Symbol << nodeName <<_
      --     " points="::Symbol << (n.parameters.points))
      if mat2.matOpacity < 0.95::DF then
        nodeAtts := concat(nodeAtts,xmlAttribute("stroke-opacity",_
          (math_to_string mat2.matOpacity)@String))
    if n.type = "SHAPE"::Symbol then -- shape node
      if not (n.parameters case shpe) then
        error "toSVG parameter type not valid for shape node"
      param2 := xform(tran, n.parameters.shpe.centre)$TR
      -- do not transform radius like this:
      -- r2 := xform(tran, n.parameters.shpe.size)$TR
      -- because that would translate and we only want to scale
      r2 := n.parameters.shpe.size
      --print("scene toSVC SHAPE nodeName="::Symbol << nodeName <<_
      --     " tran="::Symbol << tran << _
      --     " centre="::Symbol << (n.parameters.shpe.centre) << _
      --     " size="::Symbol << (n.parameters.shpe.size) << _
      --     " param2="::Symbol << param2 << _
      --     " r2="::Symbol << r2)
      rx : DF := screenCoordX(r2)*scale2
      ry : DF := screenCoordY(r2)*scale2
      nodeName := "ellipse"
      sx:String := "x"; sy:String := "y"; sw:String := "width"; sh:String := "height"
      if n.parameters.shpe.shptype = "rect"::Symbol then
        nodeName := "rect"
        sx := "x"; sy := "y"; sw := "width"; sh := "height"
        -- in all cases param2 is the centre of the shape, so no
        -- need to offset it
        --param2 := param2 + spnt(0::DF, screenCoordY(r2))$PT
      if n.parameters.shpe.shptype = "ellipse"::Symbol then
        nodeName := "ellipse"
        sx := "cx"; sy := "cy"; sw := "rx"; sh := "ry"
      if n.parameters.shpe.shptype = "box"::Symbol then
        nodeName := "rect"
        sx := "x"; sy := "y"; sw := "width"; sh := "height"
        --param2 := param2 + spnt(0::DF, screenCoordY(r2))$PT
      if n.parameters.shpe.shptype = "sphere"::Symbol then
        nodeName := "ellipse"
        sx := "cx"; sy := "cy"; sw := "rx"; sh := "ry"
      --print("toSVC nodeName="::Symbol << nodeName::OutputForm << _
      --     " sx="::Symbol << sx << (screenCoordX(param2)*scale2) << _
      --     " sy="::Symbol << sy << (-screenCoordY(param2)*scale2) << _
      --     " sw="::Symbol << sw << abs(rx) << _
      --     " sh="::Symbol << sh << abs(ry))
      nodeAtts := [_
        xmlAttribute("stroke",mat2.lineCol),_
        xmlAttribute("stroke-width",(math_to_string mat2.lineWidth)@String),_
        xmlAttribute(sx, (math_to_string (screenCoordX(param2)*scale2))@String), _
        xmlAttribute(sy, (math_to_string (-screenCoordY(param2)*scale2))@String), _
        xmlAttribute(sw, math_to_string(abs(rx))), _
        xmlAttribute(sh, math_to_string(abs(ry)))_
          ]
      if not n.parameters.shpe.fill
       then
        nodeAtts := concat(nodeAtts,xmlAttribute("fill","none"))
       else
        nodeAtts := concat(nodeAtts,xmlAttribute("fill",(math_to_string mat2.fillCol)@String))
      if mat2.matOpacity < 0.95::DF then
        nodeAtts := concat(nodeAtts,xmlAttribute("opacity",_
          (math_to_string mat2.matOpacity)@String))
    if n.type = "MATERIAL"::Symbol then -- set material
      nodeName := "g" -- material node
      if not (n.parameters case material) then
        error "toSVG parameter type not valid for material node"
      mat2 := n.parameters.material
    if n.type = "TEXT"::Symbol then -- text node
      nodeName := "text" -- text node
      if not (n.parameters case text) then
        error "toSVG parameter type not valid for text node"
      if n.parameters.text.np ~= [] then
        -- use named points
        for nam in n.parameters.text.np repeat
          param2 := xform(tran, findPoint(np2, nam)+n.parameters.text.pos)$TR
          nodeAttsTxt := [xmlAttribute("font-size", _
            string(n.parameters.text.siz)), _
            xmlAttribute("transform",_
            concat(["scale(", math_to_string(scale2/(2::DF))@String, ",", _
                      math_to_string(scale2/(2::DF))@String, ")"])@String), _
            xmlAttribute("x",_
            (math_to_string (screenCoordX(param2)*(2::DF)))@String), _
            xmlAttribute("y",_
            (math_to_string (-screenCoordY(param2)*(2::DF)))@String), _
            xmlAttribute("style",concat("fill:",mat2.fillCol))_
              ]
          xch : XmlElement := xmlElement(nodeName, nam, nodeAttsTxt)
          if not empty?(xch) then
            nodeEles := concat(nodeEles, xch)
        return xmlElement("g",nodeEles,nodeAtts)
      param2 : PT := xform(tran, n.parameters.text.pos)$TR
      --print("scene toSvg font-size=" << n.parameters.text.siz <<
      --      " scale2=" << math_to_string(scale2)@String)
      -- scale text but if screen is very small then limit text expansion
      -- so text does not fill too large a proportion of the screen
      textScale : DF := scale2/(2::DF)
      x : DF := screenCoordX(param2)*(2::DF)
      y : DF := -screenCoordY(param2)*(2::DF)
      if textScale > 2::DF then
          x := x * textScale / (2::DF)
          y := y * textScale / (2::DF)
          textScale := 2::DF
      nodeAtts := [xmlAttribute("font-size", string(n.parameters.text.siz)),_
        xmlAttribute("transform",_
          concat(["scale(", math_to_string(textScale)@String, ",", _
                   math_to_string(textScale)@String,")"])@String), _
        xmlAttribute("x", math_to_string(x)@String), _
        xmlAttribute("y", math_to_string(y)@String), _
        xmlAttribute("style", concat("fill:", mat2.fillCol))_
          ]
      return xmlElement(nodeName, n.parameters.text.txt, nodeAtts)
    if n.type = "TRANSFORM"::Symbol then -- set transform
      nodeName := "g" -- transform node
      if not (n.parameters case trans) then
        error "toSVG parameter type not valid for trans node"
      --print("scene toSVC TRANSFORM nodeName="::Symbol << nodeName <<_
      --     " trans="::Symbol << n.parameters.trans)
      tran2 := compound(n.parameters.trans, tran)
    if n.type = "CLIP"::Symbol then -- set default clip bounding box
      nodeName := "g" -- clip node
      clipEn2 := true
      if not (n.parameters case boundbox) then
        error "toSVG parameter type not valid for clip node"
      bb2 := n.parameters.boundbox
    if n.type = "IFS"::Symbol then -- indexed face set node
      -- indexed face set is normally associated with 3 (or more)
      -- dimensions so to output to SVG which displays in 2D then we
      -- need to flatten
      nodeName := "path" -- Indexed Face Set
      if not (n.parameters case ifs) then
        error "toSVG parameter type not valid for ifs node"
      nodeAtts := [_
        xmlAttribute("d",faceString(n.parameters.ifs,_
                     tran2, bb2, scale2, clipEn2)), _
        xmlAttribute("fill",mat2.fillCol),_
        xmlAttribute("stroke",mat2.lineCol),_
        xmlAttribute("stroke-width",_
        (math_to_string (mat2.lineWidth))@String)_
          ]
      return xmlElement(nodeName, [], nodeAtts)
    if n.type = "ARROWS"::Symbol then -- arrow node
      nodeName := "g" -- Arrows
      if not (n.parameters case arrws) then
        error "toSVG parameter type not valid for arrws node"
      nodeEles := lineArrow(n.parameters.arrws.ln, tran2, bb2, mat2, _
                            scale2, clipEn2, n.parameters.arrws.mode, _
                            n.parameters.arrws.size)
    if n.type = "ARROW"::Symbol then -- arrow from named points node
      if not (n.parameters case arrw) then
        error "toSVG parameter type not valid for arrw node"
      startPoint : PT := _
        xform(tran, findPoint(np2, n.parameters.arrw.st)+_
              n.parameters.arrw.offset)$TR
      endPoint : PT := _
        xform(tran, findPoint(np2, n.parameters.arrw.en)+_
              n.parameters.arrw.offset)$TR
      startPointM : DF := -(screenCoordY(startPoint)*scale)
      endPointM : DF := -(screenCoordY(endPoint)*scale)
      --print("scene toSVG start="::Symbol << (n.parameters.arrw.st) << _
      --  " end="::Symbol << (n.parameters.arrw.en) << _
      --  " startXfmd="::Symbol << startPoint << _
      --  " endXfmd="::Symbol << endPoint)
      -- first check if arrow starts and ends at the same place and
      -- if so draw a loop
      if n.parameters.arrw.st = n.parameters.arrw.en
       then -- draw loop
        ptStr : String := concat([_
         "M",_
         (math_to_string (screenCoordX(startPoint)*scale))@String,",",_
         (math_to_string startPointM)@String, _
         "c -50,25 -50,-50 0,-25"])$String
       else -- draw arrow
        ptStr : String := concat([_
         "M",_
         (math_to_string (screenCoordX(startPoint)*scale))@String,",",_
         (math_to_string startPointM)@String, _
         "L",_
         (math_to_string (screenCoordX(endPoint)*scale))@String,",",_
         (math_to_string endPointM)@String])$String
      linWidth : DF := n.parameters.arrw.size
      if n.parameters.arrw.mode = "proportional"::Symbol then
        linWidth : DF := _
          screenCoordX(getMax(bb2) - getMin(bb2))*n.parameters.arrw.size
      if n.parameters.arrw.mode = "variable"::Symbol then
        linWidth : DF := _
          distance(startPoint, endPoint)*n.parameters.arrw.size
      nodeAtts : List XmlAttribute := [_
          xmlAttribute("d",ptStr),_
          xmlAttribute("fill","none"),_
          xmlAttribute("stroke",mat.lineCol),_
          xmlAttribute("stroke-width",_
          (math_to_string linWidth)@String), _
          xmlAttribute("style","marker-end:url(#Arrow)")_
            ]
      nodeEles := [xmlElement("path",[],nodeAtts)]
      nodeName := "g" -- Arrows
    if n.type = "NAMEDPOINTS"::Symbol then -- named points node
      if not (n.parameters case np) then
        error "toSVG parameter type not valid for np node"
      --np2 := n.parameters.np
      addPoints!(np2,"points",n.parameters.np)
      nodeName := "g"
    if n.type = "DEF"::Symbol then -- names this point in scene
              -- so that it can be used again
      if not (n.parameters case nodename) then
        error "toSVG parameter type not valid for def node"
      nn : NODENAME := n.parameters.nodename
      name : String := nn.nme
      nde : % := nn.node
      nodeName := "g"
      addNode!(np2, name, nde pretend Scene(PT))
      --print(np2::OutputForm)
      xch := toSVG(nde, mat2, tran2, bb2, scale2, clipEn2, useInteger, np2)
      if not empty?(xch) then
        nodeEles := concat(nodeEles, xch)
    if n.type = "USE"::Symbol then -- use corresponding def node
      if not (n.parameters case nodename) then
        error "toSVG parameter type not valid for use node"
      nn : NODENAME := n.parameters.nodename
      name : String := nn.nme
      nde : % := findNode(np2, name) pretend %
      nodeName := "g"
      --print (np2::OutputForm)
      xch := toSVG(nde, mat2, tran2, bb2, scale2, clipEn2, useInteger, np2)
      if not empty?(xch) then
        nodeEles := concat(nodeEles, xch)
    -- if no children return empty element
    #(n.children) < 1 =>
      xmlElement(nodeName, nodeEles, nodeAtts)
    -- not empty so return group node
    for ch in n.children repeat
      xch := toSVG(ch, mat2, tran2, bb2, scale2, clipEn2, useInteger, np2)
      if not empty?(xch) then
        nodeEles := concat(nodeEles, xch)
    xmlElement(nodeName, nodeEles, nodeAtts)

  -- Write an 'SVG' representation of node 'n' to the filename supplied.
  writeSvg(n : %, filename : String) : Void ==
    defaultMaterial:MATERIAL := [2::DF,"black","black",1::DF]
    defaultTransform : TR := identity()$TR
    defaultBounds : SBoundary(PT) := nullBoundary()
    writeXml(toSVG(n, defaultMaterial, defaultTransform, defaultBounds, 1$DF, _
           false, false, namedBranch([], [])), filename)$ExportXml

  -- Write an 'SVG' representation of node 'n' to the filename supplied.
  writeSvgQuantised(n : %, filename : String) : Void ==
    defaultMaterial:MATERIAL := [2::DF,"black","black",1::DF]
    defaultTransform : TR := identity()$TR
    defaultBounds : SBoundary(PT) := nullBoundary()
    writeXml(toSVG(n, defaultMaterial, defaultTransform, defaultBounds, 1$DF, _
           false, true, namedBranch([], [])), filename)$ExportXml

  -- returns the boundary of a given node, so if this is called
  -- on the root node, it will return the boundary of the whole
  -- scene.
  -- n is node whose boundary is to be returned
  -- fontScale is required since fonts are rendered at a fixed size we
  -- need a scaling factor so that we can give the boundary in our
  -- coordinate system.
  boundary1(n : %, tran : TR, scale : DF, _
        useInteger : Boolean, npt : SceneNamedPoints PT, _
        fontScale : DF) : SBoundary(PT) ==
    res : SBoundary(PT) := nullBoundary()
    fontScale2 := fontScale
    tran2 := tran
    if n.type = "ROOT"::Symbol then -- returns boundary of whole scene
      bb2 := n.parameters.boundbox
      if not isNull?(bb2) then
        minx : DF := screenCoordX(getMin(bb2))
        maxx : DF := screenCoordX(getMax(bb2))
        fontScale2 := (maxx-minx) / (1000::DF)
        --print(hconcat(["scene boundary1 bb2="::OutputForm,bb2::OutputForm,_
        --     " minx="::OutputForm,minx::OutputForm,_
        --     " maxx="::OutputForm,maxx::OutputForm,_
        --     " fontScale2="::OutputForm,fontScale2::OutputForm,_
        --     " res="::OutputForm,res::OutputForm]))$OutputForm
      res := nullBoundary()
    if n.type = "GROUP"::Symbol then -- group node
      res := nullBoundary()
    if n.type = "LINE"::Symbol then -- line node
      pts : List List PT := n.parameters.points
      for lp in pts repeat
        for p in lp repeat
          res := extendToPoint(res, xform(tran, p))
    if n.type = "SHAPE"::Symbol then -- shape node
      pt1 : PT := n.parameters.shpe.centre
      pt2 : PT := n.parameters.shpe.size
      if n.parameters.shpe.shptype = "ellipse"::Symbol then
        return ellipseBoundary(xform(tran, pt1), pt2)
      res := extendToPoint(res, xform(tran, pt1))
      res := extendToPoint(res, xform(tran, pt1+pt2))
      --print(hconcat(["boundary1 pt1="::OutputForm,pt1::OutputForm,_
      --     " pt2="::OutputForm,pt2::OutputForm,_
      --     " pt1+pt2="::OutputForm,(pt1+pt2)::OutputForm,_
      --     " pt1-pt2="::OutputForm,(pt1-pt2)::OutputForm,_
      --     " res="::OutputForm,res::OutputForm]))$OutputForm
    if n.type = "MATERIAL"::Symbol then -- set material
      res := nullBoundary()
    if n.type = "TEXT"::Symbol then -- text node
      h : DF := fontScale * (n.parameters.text.siz)::DF
      w : DF := 0.5::DF * h *(#(n.parameters.text.txt))::DF
      pt1 : PT := n.parameters.text.pos
      res := extendToPoint(res, xform(tran, pt1))
      pt2 : PT := pt1 + spnt(w, h)$PT
      res := extendToPoint(res, xform(tran, pt2))
    if n.type = "TRANSFORM"::Symbol then -- set transform
      tran2 := compound(n.parameters.trans, tran)
    if n.type = "CLIP"::Symbol then -- set default clip bounding box
      res := nullBoundary()
    if n.type = "IFS"::Symbol then -- indexed face set node
      pts2 : List PT := n.parameters.ifs.pts
      for p in pts2 repeat
        res := extendToPoint(res, xform(tran, p))
    if n.type = "ARROWS"::Symbol then -- arrow node
      pts2 : List List PT := n.parameters.arrws.ln
      for lp in pts2 repeat
        for p in lp repeat
          res := extendToPoint(res, xform(tran, p))
    if n.type = "ARROW"::Symbol then -- arrow from named points node
      res := nullBoundary()
    if n.type = "NAMEDPOINTS"::Symbol then -- named points node
      res := nullBoundary()
    if n.type = "DEF"::Symbol then -- shape node
      res := nullBoundary()
    if n.type = "USE"::Symbol then -- shape node
      res := nullBoundary()
    for ch in n.children repeat
      res2 : SBoundary(PT) := boundary1(ch, tran2, scale, useInteger, _
                             npt, fontScale2)
      res := sunion(res, res2)
    res

  -- returns the boundary of a given node, so if this is called
  -- on the root node, it will return the boundary of the whole
  -- scene.
  -- There is a difficulty involving text nodes, that is they have
  -- a fixed font size. But we want to calculate the boundary in local
  -- coordinates, to do this conversion we need to know the total
  -- boundary size but that is what we are trying to calculate! To get
  -- round this contradiction the parameter fontScale is required since
  -- fonts are rendered at a fixed size we need a scaling factor so
  -- that we can give the boundary in our coordinate system.
  -- fontScale should be set to the full width of the diagram (not
  -- just the width of this node). If this is not known then choose
  -- an approximate value. This only affects text nodes, if this
  -- node, or its subnodes, do not contain a text node then its
  -- value is not significant.
  boundary(n : %, fontScale : DF) : SBoundary(PT) ==
    defaultTransform : TR := identity()$TR
    boundary1(n, defaultTransform, 1$DF, false, namedBranch([], []), _
      fontScale/1000::DF)

  -- this returns a string to represent a sequence of points in a X3D file
  pointString(pts : List PT, tran : TR, bb : SBoundary(PT)) : List String ==
    ptStr : List String := [] -- list to hold result
    pntNum : NNI -- hold current position in parameters
    for param in pts repeat
      -- convert to SVG (always positive) coordinates
      -- param is type SPointCategory(DoubleFloat)
      -- which is what applyTrans requires
        param2 := xform(tran, param pretend PT)$TR
        thisStr : String := concat([_
             (math_to_string screenCoordX(param2))@String," ",_
             (math_to_string screenCoordY(param2))@String," ",_
             (math_to_string screenCoordZ(param2))@String])$String
        ptStr := concat(ptStr, thisStr)
    ptStr

  -- this returns a string to represent a sequence of points in a X3D file
  pointIndexString(pts : List List NNI, tran : TR, bb : SBoundary(PT)) : List String ==
    ptStr : List String := [] -- string to hold result
    pntNum : NNI -- hold current position in parameters
    for line in pts repeat
      for param in line repeat
        ptStr := concat(ptStr, (math_to_string param)@String)
      -- '-1' is used as a separator between faces
      ptStr := concat(ptStr,"-1")
    ptStr

  -- the following functions: setX3DNodeName, toX3D
  -- and writeX3D are used to write to a X3D file.

  setX3DNodeName(typ : Symbol) : String ==
    nodeName:String := ""
    if typ = "ROOT"::Symbol then nodeName := "X3D" -- root node
    if typ = "GROUP"::Symbol then nodeName := "Group" -- group node
    if typ = "LINE"::Symbol then nodeName := "IndexedFaceSet" -- line node
    if typ = "SHAPE"::Symbol then nodeName := "Sphere" -- shape node
    if typ = "MATERIAL"::Symbol then nodeName := "Group" -- material node
    if typ = "TEXT"::Symbol then nodeName := "Text" -- text node
    if typ = "TRANSFORM"::Symbol then nodeName := "Transform" -- transform node
    if typ = "CLIP"::Symbol then nodeName := "Group" -- clip node
    if typ = "IFS"::Symbol then nodeName := "IndexedFaceSet" -- Indexed Face Set
    if typ = "ARROWS"::Symbol then nodeName := "IndexedFaceSet" -- arrows node
    if typ = "DEF"::Symbol then nodeName := "Group" -- def node
    if typ = "USE"::Symbol then nodeName := "Group" -- use node
    nodeName

  -- creates an XML tree structure (for X3D) from scenegraph tree
  -- structure
  -- called recursively for each node, so when called on root node
  -- in scenegraph all other nodes in the scenegraph will get called.
  --<Scene>
  -- <Shape>
  --  <Appearance>
  --   <Material diffuseColor="0.82 0.78 0.74"/>
  --  </Appearance>
  --  <IndexedFaceSet coordIndex="0 1 5 4">
  --   <Coordinate point="0.0 0.0 0.0 2.0 5 -2.7"/>
  --  </IndexedFaceSet>
  -- </Shape>
  --</Scene>
  toX3D(n : %, mat : MATERIAL, tran : TR, bb : SBoundary(PT)) : XmlElement ==
    nodeName : String := setX3DNodeName(n.type)
    bb2 := bb
    tran2 := tran
    mat2 := mat
    nodeAtts : List XmlAttribute := []
    if n.type = "ROOT"::Symbol then -- root node - set clip bounds to back to
                       -- local coordinates
      if not (n.parameters case boundbox) then
        error "toX3D parameter type not valid for root node"
      bb2 := n.parameters.boundbox
      if isNull?(bb2) then bb2 := boundary(n, 1$DF)
      if isNull?(bb2) then error "scene contains no drawable elements"
      nodeEles : List XmlElement := []
      for ch in n.children repeat
        xch := toX3D(ch, mat2, tran2, bb2)
        if not empty?(xch) then
          nodeEles := concat(nodeEles, xch)
      inner := xmlElement("Scene",nodeEles,nodeAtts)
      return xmlElement(nodeName, [inner], nodeAtts)
    if n.type = "MATERIAL"::Symbol then -- set material
      mat2 := n.parameters.material
    if n.type = "TRANSFORM"::Symbol then -- set transform
      if not (n.parameters case trans) then
        error "toX3D parameter type not valid for trans node"
      tran2 := compound(n.parameters.trans, tran)
    if n.type = "CLIP"::Symbol then -- set default clip bounding box
      if not (n.parameters case boundbox) then
        error "toX3D parameter type not valid for clip node"
      -- at the moment boundBox is in global coordinates
      -- to use local coordinates at this node we would need to transform:
      bb2 := n.parameters.boundbox
    if n.type = "TEXT"::Symbol then -- text node
      if not (n.parameters case text) then
        error "toX3D parameter type not valid for text node"
      nodeAtts := [xmlAttribute("string",n.parameters.text.txt)]
    if n.type = "LINE"::Symbol then -- line node
      -- its difficult to draw a line in 3D. it needs to have
      -- some finite width so that we can see it.
      -- We therefore draw it as a thin tube.
      if not (n.parameters case points) then
        error "toX3D parameter type not valid for line node"
      meshR : List List PT := _
        curveLoops(first n.parameters.points, 0.25::DF, 8)$SceneIFS(PT)
      ifsR : SceneIFS(PT) := smesh(meshR, false)$SceneIFS(PT)
      nodeAtts := [xmlAttribute("coordIndex",_
            pointIndexString(indexes(ifsR), tran, bb))]
      coord := xmlElement("Coordinate",[],_
             [xmlAttribute("point",pointString(pointList(ifsR),tran,bb))])
      ifset := xmlElement(nodeName, [coord], nodeAtts)
      return xmlElement("Shape",[ifset],[])
    if n.type = "SHAPE"::Symbol then -- shape node
      if not (n.parameters case shpe) then
        error "toX3D parameter type not valid for text node"
--      nodeAtts := [xmlAttribute("string",n.parameters.text.txt)]
    if n.type = "ARROWS"::Symbol then -- arrows node
      -- its difficult to draw an arrow in 3D. it needs to have
      -- some finite width so that we can see it.
      -- We therefore draw it as a thin tube.
      if not (n.parameters case arrws) then
        error "toX3D parameter type not valid for arrws node"
      meshR : List List PT := _
        curveLoops(first n.parameters.arrws.ln, 0.25::DF, 8)$SceneIFS(PT)
      ifsR : SceneIFS(PT) := smesh(meshR, false)$SceneIFS(PT)
      nodeAtts := [xmlAttribute("coordIndex",_
        pointIndexString(indexes(ifsR), tran, bb))]
      coord := xmlElement("Coordinate",[],_
             [xmlAttribute("point",pointString(pointList(ifsR),tran,bb))])
      ifset := xmlElement(nodeName, [coord], nodeAtts)
      return xmlElement("Shape",[ifset],[])
    if n.type = "IFS"::Symbol then -- indexed face set node
      if not (n.parameters case ifs) then
        error "toX3D parameter type not valid for ifs node"
      nodeAtts := [xmlAttribute("coordIndex",_
        pointIndexString(n.parameters.ifs.inx, tran, bb))]
      coord := xmlElement("Coordinate",[],_
             [xmlAttribute("point",_
              pointString(n.parameters.ifs.pts, tran2, bb2))])
      ifset := xmlElement(nodeName, [coord], nodeAtts)
      return xmlElement("Shape",[ifset],[])
    if n.type = "DEF"::Symbol then -- shape node
      if not (n.parameters case nodename) then
        error "toSVG parameter type not valid for def node"
    if n.type = "USE"::Symbol then -- shape node
      if not (n.parameters case nodename) then
        error "toSVG parameter type not valid for use node"
    -- if no children return empty element
    #(n.children) < 1 =>
      xmlElement(nodeName, [], nodeAtts)
    -- not empty so return children
    nodeEles : List XmlElement := []
    for ch in n.children repeat
      xch := toX3D(ch, mat2, tran2, bb2)
      if not empty?(xch) then
        nodeEles := concat(nodeEles, xch)
    xmlElement(nodeName, nodeEles, nodeAtts)

  -- Write an 'X3D' representation of node 'n' to the filename supplied.
  writeX3d(n : %, filename : String) : Void ==
    defaultMaterial:MATERIAL := [2::DoubleFloat,"black","black",1::DF]
    defaultTransform : TR := identity()$TR
    defaultBounds : SBoundary(PT) := nullBoundary()
    writeXml(toX3D(n, defaultMaterial, defaultTransform, defaultBounds), _
                   filename)$ExportXml

  -- creates an .OBJ (Wavefront) file from scenegraph tree
  -- structure
  -- called recursively for each node, so when called on root node
  -- in scenegraph all other nodes in the scenegraph will get called.
  toObj(n : %, ptLst : Reference List PT, indexLst : Reference List List NNI, _
                   indexNxt : Reference NNI, tran : TR, bb : SBoundary(PT)) : Void ==
    bb2 := bb
    tran2 := tran
    if n.type = "ROOT"::Symbol then
      -- root node - make sure view bounds are always positive
      if not (n.parameters case boundbox) then
        error "toObj parameter type not valid for root node"
      bb2 := n.parameters.boundbox
      if isNull?(bb2) then bb2 := boundary(n, 1$DF)
      if isNull?(bb2) then error "scene contains no drawable elements"
      minx : DF := screenCoordX(getMin(bb2))
      miny : DF := screenCoordY(getMin(bb2))
      offsetx : DF := 0::DF
      offsety : DF := 0::DF
      offsetRequired := false
      if minx < 0 then
        offsetRequired := true
        offsetx := -minx
      if miny < 0 then
        offsetRequired := true
        offsety := -miny
      if offsetRequired then
        mn := spnt(screenCoordX(getMin(bb2))+offsetx, screenCoordY(getMin(bb2))+_
                   offsety)$PT
        mx := spnt(screenCoordX(getMax(bb2))+offsetx, screenCoordY(getMax(bb2))+_
                   offsety)$PT
        bb2 := boxBoundary(mn, mx)
        tran2 := stranslate(offsetx, offsety, 0::DF, 1::DF, 1::DF, 1::DF)$TR
      for ch in n.children repeat
        toObj(ch, ptLst, indexLst, indexNxt, tran2, bb2)
      return Void
    if n.type = "TRANSFORM"::Symbol then -- set transform
      if not (n.parameters case trans) then
        error "toObj parameter type not valid for trans node"
      tran2 := compound(n.parameters.trans, tran)
    if n.type = "CLIP"::Symbol then -- set default clip bounding box
      if not (n.parameters case boundbox) then
        error "toObj parameter type not valid for clip node"
      -- at the moment boundBox is in global coordinates
      -- to use local coordinates at this node we would need to transform:
      bb2 := n.parameters.boundbox
      return Void
    if n.type = "LINE"::Symbol then -- line node
      if not (n.parameters case points) then
        error "toObj parameter type not valid for line node"
      meshR : List List PT := _
        curveLoops(first n.parameters.points, 0.25::DF, 8)$SceneIFS(PT)
      ifsR : SceneIFS(PT) := smesh(meshR, false)$SceneIFS(PT)
      i1 : List List NNI := indexes(ifsR)
      p1 : List PT := pointList(ifsR)
      i2 : List List NNI := [[j + elt(indexNxt) for j in k] for k in i1]
      setelt!(ptLst, concat(elt(ptLst), p1))
      setelt!(indexLst, concat(elt(indexLst), i2))
      setelt!(indexNxt, elt(indexNxt) + #p1)
      return Void
    if n.type = "SHAPE"::Symbol then -- shape node
      if not (n.parameters case shpe) then
        error "toObj parameter type not valid for shape node"
    if n.type = "ARROWS"::Symbol then -- arrow node
      if not (n.parameters case points) then
        error "toObj parameter type not valid for arrows node"
      meshR : List List PT := _
        curveLoops(first n.parameters.points, 0.25::DF, 8)$SceneIFS(PT)
      ifsR : SceneIFS(PT) := smesh(meshR, false)$SceneIFS(PT)
      i1 : List List NNI := indexes(ifsR)
      p1 : List PT := pointList(ifsR)
      i2 : List List NNI := [[j + elt(indexNxt) for j in k] for k in i1]
      setelt!(ptLst, concat(elt(ptLst), p1))
      setelt!(indexLst, concat(elt(indexLst), i2))
      setelt!(indexNxt, elt(indexNxt) + #p1)
      return Void
    if n.type = "IFS"::Symbol then -- indexed face set node
      if not (n.parameters case ifs) then
        error "toObj parameter type not valid for ifs node"
      i1 : List List NNI := n.parameters.ifs.inx
      p1 : List PT := n.parameters.ifs.pts
      i2 : List List NNI := [[j + elt(indexNxt) for j in k] for k in i1]
      setelt!(ptLst, concat(elt(ptLst), p1))
      setelt!(indexLst, concat(elt(indexLst), i2))
      setelt!(indexNxt, elt(indexNxt) + #p1)
      return Void
    if n.type = "DEF"::Symbol then -- shape node
      if not (n.parameters case nodename) then
        error "toSVG parameter type not valid for def node"
    if n.type = "USE"::Symbol then -- shape node
      if not (n.parameters case nodename) then
        error "toSVG parameter type not valid for use node"
    -- if no children return empty element
    #(n.children) < 1 =>
      Void
    -- not empty so return children
    for ch in n.children repeat
      toObj(ch, ptLst, indexLst, indexNxt, tran2, bb2)
    Void

  -- Write an 'OBJ' (Wavefront) representation of node 'n' to the filename
  -- supplied.
  writeObj(n : %, filename : String) : Void ==
    ptLst : Reference List PT := ref([])
    indexLst : Reference List List NNI := ref([])
    indexNxt : Reference NNI := ref(0::NNI)
    defaultTransform : TR := identity()$TR
    defaultBounds : SBoundary(PT) := nullBoundary()
    toObj(n, ptLst, indexLst, indexNxt, defaultTransform, defaultBounds)
    f1:TextFile := open(filename::FileName,"output")
    writeLine!(f1,"# mesh generated by fricas")
    for v in elt(ptLst) repeat
      writeLine!(f1,concat(["v ",_
                (math_to_string screenCoordX(v))@String," ",_
                (math_to_string screenCoordY(v))@String," ",_
                (math_to_string screenCoordZ(v))@String])$String)
    s:String := ""
    for row in elt(indexLst) repeat
      s := "f"
      for i in row repeat
        s := concat([s," ",string(i+1)])$String
      writeLine!(f1, s)
    close! f1
    Void

  -- Write an 'VRML' representation of node 'n' to the filename supplied.
  writeVRML(n : %, filename : String) : Void ==
    defaultMaterial:MATERIAL := [2::DoubleFloat,"black","black",1::DF]
    defaultTransform : TR := identity()$TR
    defaultBounds : SBoundary(PT) := nullBoundary()
    writeVRML(toX3D(n, defaultMaterial, defaultTransform, defaultBounds), _
                    filename)$ExportXml

  -- Test for NaN (Not a Number)
  -- That is have we divided by zero or taken sqrt of negative number at
  -- some stage?
  -- Common Lisp does not support NaN ? So we need some sort of test for
  -- invalid numbers
  -- If not equal to itself then we assume that its not a valid number
  Fnan?(x : DF) : Boolean == x ~= x

  -- apply function of one variable and trap any errors
  -- this is taken from draw.spad. I don't have any documentation for Lisp
  -- function 'trapNumericErrors' but I assume it detects errors like
  -- division by zero?
  applyfxTrap(ff : DF-> DF, f : DF) : DF ==
    s := trapNumericErrors(ff(f))$Lisp :: Union(DF, "failed")
    s case "failed" => 0
    r := s@DF
    r > max()$DF => max()$DF
    r < min()$DF => min()$DF
    r

  -- apply function of two variables and trap any errors
  -- this is taken from draw.spad. I don't have any documentation for Lisp
  -- function 'trapNumericErrors' but I assume it detects errors like
  -- division by zero?
  applyfxyTrap(ff : (DF, DF) -> DF, u : DF, v : DF) : DF ==
      s := trapNumericErrors(ff(u, v))$Lisp :: Union(DF, "failed")
      s case "failed" => 0
      r : DF := s@DF
      r >max()$DF => max()$DF
      r < min()$DF => min()$DF
      r

  -- Make sure the Segment goes from a low number to a high number
  -- this is taken from draw.spad
  normalize(seg : SEG) : Segment DF ==
    -- normalize [a, b]:
    -- error if a = b, returns [a, b] if a < b, returns [b, a] if b > a
    a := convert(low(seg))@DF; b := convert(high(seg))@DF
    a = b => error "The range specified is too small"
    a < b => segment(a, b)
    segment(b, a)

  -- checks that left-hand endpoint is less than right-hand endpoint
  checkRange(r : SEG) : SEG ==
    (low(r) > high(r) => error "ranges cannot be negative"; r)

  -- generates a plot from a mapping 'f' of
  -- scalar values to points
  -- mostly from plot.spad
  -- this is taken mostly from drawPlot in draw.spad but instead
  -- of outputting to TwoDimensionalViewport we output to a
  -- scenegraph node.
  -- The input is a Plot defined in plot.spad
  createPlot1Din2D(f : DF -> PT, tRange : SEG, numPts : NNI) : % ==
    checkRange tRange
    l := low(tRange)
    h := high(tRange)
    t : List DF := list l
    p0 : List PT := list f l
    s := (h-l)/(numPts-1)::DF
    for i in 2..numPts-1 repeat
      l := l+s
      t := concat(l, t)
      p0 := concat(f l, p0)
    t := reverse! concat(h, t)
    p0 := reverse! concat(f h, p0)
    branches : List List PT := []
    newl : List PT := []
    for p in p0 repeat
      if not Pnan? p then newl := cons(p, newl)
      else if not empty? newl then
            branches := concat(newl := reverse! newl, branches)
            newl := []
    if not empty? newl then branches := concat(newl := reverse! newl, branches)
    createSceneLines(branches)

  -- a convenience function which combines createPlot1Din2D with addChild!
  addPlot1Din2D(n : %, f : DF -> PT, tRange : SEG, numPts : NNI) : % ==
    c := createPlot1Din2D(f, tRange, numPts)
    addChild!(n, c)
    c

  -- mostly from draw.spad
  createPlot1Din2D(f : DF -> DF, seg : SEG, numPts : NNI) ==
    -- create function DF -> PT
    ff : List(DF -> PT) := [x +-> spnt(x, applyfxTrap(f, x))$PT]
    -- create PLOT
    createPlot1Din2D(first ff, normalize seg, numPts)

  -- a convenience function which combines createPlot1Din2D with addChild!
  addPlot1Din2D(n : %, f : DF -> DF, seg : SEG, numPts : NNI) ==
    c := createPlot1Din2D(f, seg, numPts)
    addChild!(n, c)
    c

  -- create a node from plot using Parametric Plane Curve
  -- This represents 1 dimension (line - possibly curved) in 2 dimensions
  -- (plane) In theory a line has no width but in that case we would not see
  -- it so we give it a width given by the material node that is
  -- applicable in this part of the scene graph
  -- PPC is ParametricPlaneCurve(DF -> DF) which is created with curve(f1, f2)
  -- where f1 and f2 are functions of type ComponentFunction, in this case
  -- DF -> DF
  createPlot1Din2Dparametric(ppc : PPC, seg : SEG, numPts : NNI) ==
    -- create function DF -> Point DF
    f := coordinate(ppc, 1); g := coordinate(ppc, 2)
    -- create an anonymous function of type DF->PT
    fcn : List(DF -> PT) := [x +-> spnt(applyfxTrap(f, x), _
                             applyfxTrap(g, x))$PT]
    -- create PLOT
    createPlot1Din2D(first fcn, normalize seg, numPts)

  -- a convenience function which combines createPlot1Din2Dparametric with
  -- addChild!
  addPlot1Din2Dparametric(n : %, ppc : PPC, seg : SEG, numPts : NNI) ==
    c := createPlot1Din2Dparametric(ppc, seg, numPts)
    addChild!(n, c)
    c

  -- create a line (1D subspace) in 3D space
  -- This represents 1 dimension (line - possibly curved) in 3 dimensions
  -- In theory a line has no width but in that case we would not see it
  -- so we give it a width given by the material node that is
  -- applicable in this part of the scene graph
  -- PSC ParametricSpaceCurve(DF -> DF) is created with curve(f1, f2, f3)
  -- where f1, f2 and f3 are functions of type ComponentFunction, in this case
  -- DF -> DF
  createPlot1Din3Dparametric(psc : PSC, seg : SEG, numPts : NNI) : % ==
      f := coordinate(psc, 1); g := coordinate(psc, 2); h := coordinate(psc, 3)
      -- create an anonymous function of type DF->PT
      fcn : DF -> PT := x +-> spnt(applyfxTrap(f, x), applyfxTrap(g, x), _
                            applyfxTrap(h, x))$PT
      createPlot1Din3Dparametric(fcn, seg, numPts)

  -- a convenience function which combines createPlot1Din3Dparametric with
  -- addChild!
  addPlot1Din3Dparametric(n : %, psc : PSC, seg : SEG, numPts : NNI) : % ==
    c := createPlot1Din3Dparametric(psc, seg, numPts)
    addChild!(n, c)
    c

  -- create a line (1D subspace) in 3D space
  -- This represents 1 dimension (line - possibly curved) in 3 dimensions
  -- In theory a line has no width but in that case we would not see it
  -- so we give it a width given by the material node that is
  -- applicable in this part of the scene graph
  -- PCFUN is a function from float to point: DF -> PT
  createPlot1Din3Dparametric(psc : PCFUN, seg : SEG, numPts : NNI) : % ==
    f := psc
    tRange := normalize seg
    checkRange tRange; l := low(tRange); h := high(tRange)
    t : List DF := list l; p : List PT := list f l
    s := (h-l)/(numPts-1)::DF
    for i in 2..numPts-1 repeat
      l := l+s; t := concat(l, t)
      p := concat(f l, p)
    t := reverse! concat(h, t)
    p := reverse! concat(f h, p)
    createSceneLine(p)

  -- a convenience function which combines createPlot1Din3Dparametric with
  -- addChild!
  addPlot1Din3Dparametric(n : %, psc : PCFUN, seg : SEG, numPts : NNI) : % ==
    c := createPlot1Din3Dparametric(psc, seg, numPts)
    addChild!(n, c)
    c

  -- parameterized equations of two variables
  -- from mesh.spad
  -- PSFUN is (DF, DF) -> PT
  createPlot2Din3D(ptFun : PSFUN, uSeg : SEG, vSeg : SEG, numPts : NNI) : % ==
    llp : List List PT := []
    ustep := (low(uSeg) - high(uSeg))/numPts
    vstep := (low(vSeg) - high(vSeg))/numPts
    someV := high(vSeg)
    for iv in numPts..0 by -1 repeat
      if zero? iv then someV := low(vSeg)
      -- hack: get last number in segment within segment
      lp : List PT := []
      someU := high(uSeg)
      for iu in numPts..0 by -1 repeat
        if zero? iu then someU := low(uSeg)
        -- hack: get last number in segment within segment
        pt := ptFun(someU, someV)
        --here we need to check for NaN (not a number)
        --numberCheck pt
        lp := concat(pt, lp)
        someU := someU + ustep
      llp := concat(lp, llp)
      someV := someV + vstep
    -- now llp contains a list of lists of points
    -- for a surface that is a result of a function of 2 variables,
    -- the main component is open and each sub-list is open as well
    createSceneIFS(smesh(llp, false))

  -- createPlot2Din3D(f, a..b, c..d) returns a scene node
  -- which contains the graph of z = f(x, y)
  -- as x ranges from min(a, b) to max(a, b) and y ranges from
  -- min(c, d) to max(c, d).
  createPlot2Din3D(f : (DF, DF) -> DF, xSeg : SEG, ySeg : SEG, numPts : NNI) : % ==
    --sp := space l
    -- process color function of two variables
    --col2 : List((DF, DF) -> DF) := [xCoord] -- dummy color function
    --pointsColored? : Boolean := false
    --if not (c2 := option(l, 'colorFunction2)) case "failed" then
    -- pointsColored? := true
    -- col2 := [retract(c2 :: Any)$ANY1((DF, DF) -> DF)]
    fcn : List((DF, DF) -> PT) :=
      [(x, y) +-> spnt(applyfxyTrap(f, x, y), x, y)$PT]
    createPlot2Din3D(first fcn, normalize xSeg, normalize ySeg, numPts)

  -- a convenience function which combines createPlot2Din3D with addChild!
  addPlot2Din3D(n : %, f : (DF, DF) -> DF, xSeg : SEG, ySeg : SEG, numPts : NNI) : % ==
    c := createPlot2Din3D(f, xSeg, ySeg, numPts)
    addChild!(n, c)
    c

  -- createPlot2Din3Dparametric(surface(f, g, h), a..b, c..d, l) returns a
  -- scene node which contains the
  -- graph of the parametric surface x = f(u, v), y = g(u, v),
  -- z = h(u, v) as u ranges from min(a, b) to
  -- max(a, b) and v ranges from min(c, d) to max(c, d).
  -- PSF: ParametricSurface((DF, DF) -> DF) -- created with surface(f1, f2, f3)
  createPlot2Din3Dparametric(s : PSF, uSeg : SEG, vSeg : SEG, numPts : NNI) : % ==
    --sp := space l
    -- create functions from expressions
    f : List((DF, DF) -> DF) := [coordinate(s, 1)]
    g : List((DF, DF) -> DF) := [coordinate(s, 2)]
    h : List((DF, DF) -> DF) := [coordinate(s, 3)]
    fcn : List((DF, DF) -> PT) := _
      [(x, y) +-> spnt(applyfxyTrap((first f), x, y), _
       applyfxyTrap((first g), x, y), _
       applyfxyTrap((first h), x, y))$PT]
    createPlot2Din3D(first fcn, normalize uSeg, normalize vSeg, numPts)

  -- a convenience function which combines createPlot2Din3Dparametric with
  -- addChild!
  addPlot2Din3Dparametric(n : %, s : PSF, uSeg : SEG, vSeg : SEG, numPts : NNI) : % ==
    c := createPlot2Din3Dparametric(s, uSeg, vSeg, numPts)
    addChild!(n, c)
    c

  -- createPlot2Din3Dparametric(f, a..b, c..d, l) returns a
  -- scene node which contains the
  -- graph of the parametric surface \spad{f(u, v)}
  -- as u ranges from min(a, b) to
  -- max(a, b) and v ranges from min(c, d) to max(c, d).
  -- PSFUN is (DF, DF) -> PT
  createPlot2Din3Dparametric(s : PSFUN, uSeg : SEG, vSeg : SEG, numPts : NNI) : % ==
    fcn : List((DF, DF) -> PT) := [s]
    createPlot2Din3D(first fcn, normalize uSeg, normalize vSeg, numPts)

  -- a convenience function which combines createPlot2Din3Dparametric with
  -- addChild!
  addPlot2Din3Dparametric(n : %, s : PSFUN, uSeg : SEG, vSeg : SEG, numPts : NNI) : % ==
    c := createPlot2Din3Dparametric(s, uSeg, vSeg, numPts)
    addChild!(n, c)
    c

  -- outputLPoints(ps) is a local function used by coerce(n): OutputForm
  -- the reason for this function is to avoid displaying a long list of
  -- points on the command line as this would take a lot of space
  -- and not be very helpful since scene is intended for graphical
  -- output. Therefore we only output the first two values.
  outputLPoints(ps : List PT) : OutputForm ==
    if #ps < 4 then return ps::OutputForm
    bracket([(first ps)::OutputForm, (second ps)::OutputForm, _
             message("....")])

  -- outputLLPoints(ps) is a local function used by coerce(n): OutputForm
  -- the reason for this function is to avoid displaying a long list of
  -- points on the command line as this would take a lot of space
  -- and not be very helpful since scene is intended for graphical
  -- output. Therefore we only output the first two values.
  outputLLPoints(ps : List List PT) : OutputForm ==
    if #ps < 4 then
      return bracket([outputLPoints(x) for x in ps])
    bracket([outputLPoints(first ps), outputLPoints(second ps), _
             message("....")])

  -- outputLIndexes(ps) is a local function used by coerce(n): OutputForm
  -- the reason for this function is to avoid displaying a long list of
  -- indexes on the command line as this would take a lot of space
  -- and not be very helpful since scene is intended for graphical
  -- output. Therefore we only output the first two values.
  outputLIndexes(ps : List NNI) : OutputForm ==
    if #ps < 4 then return ps::OutputForm
    bracket([(first ps)::OutputForm, (second ps)::OutputForm, _
             message("....")])

  -- outputLLIndexes(ps) is a local function used by coerce(n): OutputForm
  -- the reason for this function is to avoid displaying a long list of
  -- indexes on the command line as this would take a lot of space
  -- and not be very helpful since scene is intended for graphical
  -- output. Therefore we only output the first two values.
  outputLLIndexes(ps : List List NNI) : OutputForm ==
    if #ps < 4 then
      return bracket([outputLIndexes(x) for x in ps])
    bracket([outputLIndexes(first ps), outputLIndexes(second ps), _
             message("....")])

  -- output
  -- a full scenegraph could contain a lot of data and so we have
  -- to be careful to restrict the output to what is reasonable on
  -- the command line. Therefore we do not display all subnodes or
  -- the full information from nodes such as IFS.
  coerce(n : %) : OutputForm ==
    s:OutputForm := message("scene ")
    if n.type = "ROOT"::Symbol then
      s := hconcat([s, message("root "),_
             (n.parameters.boundbox)::OutputForm])
    if n.type = "GROUP"::Symbol then
      s := hconcat(s, message("group"))
    if n.type = "LINE"::Symbol then
      s := hconcat([s, message("line "),_
        outputLLPoints(n.parameters.points)])
    if n.type = "SHAPE"::Symbol then
      tp := n.parameters.shpe.shptype
      pt1 : PT := n.parameters.shpe.centre
      pt2 : PT := n.parameters.shpe.size
      s := hconcat([s, message("shape"), _
             message(" type="), tp::OutputForm,_
             message(" pt1="), pt1::OutputForm,_
             message(" pt2="), pt2::OutputForm])
    if n.type = "MATERIAL"::Symbol then
      lw : DF := n.parameters.material.lineWidth
      lc : String := n.parameters.material.lineCol
      fc : String := n.parameters.material.fillCol
      mo : DF := n.parameters.material.matOpacity
      s := hconcat([s, message("material"), _
             message(" lw="), lw::OutputForm,_
             message(" lc="), lc::OutputForm,_
             message(" fc="), fc::OutputForm,_
             message(" mo="), mo::OutputForm])
    if n.type = "TEXT"::Symbol then
      t : String := n.parameters.text.txt
      sz1 : NNI := n.parameters.text.siz
      p : PT := n.parameters.text.pos
      npt : List String := n.parameters.text.np
      s := hconcat([s, message("text="), _
        t::OutputForm, _
        message(" sz="), sz1::OutputForm,_
        message(" p="), p::OutputForm,_
        message(" npt="), npt::OutputForm])
    if n.type = "TRANSFORM"::Symbol then
      tr := n.parameters.trans
      s := hconcat([s,message("transform"),_
           message(" tr="), tr::OutputForm])
    if n.type = "CLIP"::Symbol then
      s := hconcat([s, message("clip "), _
             (n.parameters.boundbox)::OutputForm])
    if n.type = "IFS"::Symbol then
      ix : List List NNI := n.parameters.ifs.inx
      pt : List PT := n.parameters.ifs.pts
      s := hconcat([s, message("ifs"),_
             message(" ix="), outputLLIndexes(ix),_
             message(" pt="), outputLPoints(pt)])
    if n.type = "ARROWS"::Symbol then
      pts : List List PT := n.parameters.arrws.ln
      m : Symbol := n.parameters.arrws.mode
      sz : DF := n.parameters.arrws.size
      s := hconcat([s, message("arrows"), _
          message(" pts="), outputLLPoints(pts),_
          message(" m="), m::OutputForm,_
          message(" sz="), sz::OutputForm])
    if n.type = "ARROW"::Symbol then
      str : String := n.parameters.arrw.st
      ena : String := n.parameters.arrw.en
      ofs : PT := n.parameters.arrw.offset
      md : Symbol := n.parameters.arrw.mode
      sz : DF := n.parameters.arrw.size
      s := hconcat([s, message("arrows"), _
          message(" str="), str::OutputForm,_
          message(" ena="), ena::OutputForm,_
          message(" ofs="), ofs::OutputForm,_
          message(" md="), md::OutputForm,_
          message(" sz="), sz::OutputForm])
    if n.type = "DEF"::Symbol then
      nn : String := n.parameters.nodename.nme
      s := hconcat([s, message("def"), _
          message(" nn="), nn::OutputForm])
    if n.type = "USE"::Symbol then
      nn : String := n.parameters.nodename.nme
      s := hconcat([s, message("use"), _
          message(" nn="), nn::OutputForm])
    if n.type = "NAMEDPOINTS"::Symbol then
      nam : SceneNamedPoints PT := n.parameters.np
      s := hconcat([s, message("namedpoints"), _
          message(" n="), nam::OutputForm])
    s := hconcat([s, message(" #ch="), (#(n.children))::OutputForm])
    s


--Copyright (c) 2010-2016, Martin J Baker.
--All rights reserved.
--
--Redistribution and use in source and binary forms, with or without
--modification, are permitted provided that the following conditions are
--met:
--
-- - Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
--
-- - Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in
-- the documentation and/or other materials provided with the
-- distribution.
--
-- - Neither the name of Martin J Baker. nor the
-- names of its contributors may be used to endorse or promote products
-- derived from this software without specific prior written permission.
--
--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
--IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
--TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
--PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
--OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
--EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
--PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
--PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
--LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
--NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

)if false
\eject
\begin{thebibliography}{99}
[1] Dorst, Fontijne & Mann, 2007 , Geometric Algebra for Computer Science
ISBN 0123694655
This book gives practical information about using Clifford/Grassmann
algebra to represent graphical information.
[2] Doran & Lasenby, 2003 , Geometric Algebra for Physicists,
ISBN 0521480221
[3] User Tutorial:
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/graph/tutorial/}
[4] User Reference:
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/graph/userref/}
[5] Programmers Reference:
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/graph/progref/}
[6] Examples of scenegraph structure:
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/graph/examples/}
[7] X3D & Related Specifications
\url{http://www.web3d.org/x3d/
[8] SVG & Related Specifications
\url{http://www.w3.org/Graphics/SVG/}
\end{thebibliography}
\end{document}
)endif
