\chapter{Building a scene}

\section{The scene model}

lua-tikz3dtools is easiest to use when one thinks in terms of a scene rather
than a sequence of immediate drawing commands. A point, curve, surface, or
triangle appended to the scene is not drawn at once. Instead, it is stored as
part of a temporary collection of simplices. When \verb|\displaysimplices|
is finally called, that collection is processed and emitted.

This distinction is important because it affects how one reasons about order.
The order in which objects are appended is not generally the order in which
they appear on the page. The package reserves the right to partition and sort
the geometry before producing the final paths.

\section{Named objects and reusable expressions}

The command \verb|\setobject| binds a Lua value into the expression
environment. In practice, this is the natural way to give a name to a matrix,
vector, or reusable geometric object. Once defined, that name becomes
available to later expressions in the same document session.

\begin{verbatim}
\setobject[
	name=spin,
	object={Matrix.zrotation3(pi/6):multiply(
		Matrix.xrotation3(pi/8)
	)}
]

\appendtriangle[
	A={Vector:new{0,0,0,1}},
	B={Vector:new{1,0,0,1}},
	C={Vector:new{0,1,0,1}},
	transformation={spin:multiply(Matrix.translate3(0,0,0.5))},
	fill options={fill=ltdtbrightness, draw=black}
]
\end{verbatim}

One should not be shy about naming intermediate objects. A manual source file
becomes significantly easier to read when rotations, translations, reference
points, and clipping vectors are given names rather than repeated as raw
expressions.

\section{Transformations}

Transformations are expressed with the package's \verb|Matrix| type. The
helpers currently exposed by the package are \verb|Matrix.identity3()|,
\verb|Matrix.translate3(dx,dy,dz)|, \verb|Matrix.scale3(sx,sy,sz)|,
\verb|Matrix.xrotation3(theta)|, \verb|Matrix.yrotation3(theta)|,
\verb|Matrix.zrotation3(theta)|, and
\verb|Matrix.zyzrotation3(alpha,beta,gamma)|.

The package uses a row-vector convention. A point is multiplied on the left by
its transformation matrix. Consequently, composed transformations read from
left to right in the order in which they are applied to the point. If one
writes \verb|Matrix.scale3(...):multiply(Matrix.translate3(...))|, the point
is first scaled and then translated.

\manualnote{This left-to-right composition rule is one of the first things to
verify when a figure appears in the wrong place. Many users come to the
package expecting the opposite convention from other graphics systems.}

\begin{figure}[tbp]
    \centering
    \begin{tikzpicture}
        \setobject[name=rot, object={Matrix.zrotation3(pi/4)}]
        \setobject[name=shift, object={Matrix.translate3(3,1,0)}]
        \appendtriangle[
            A={Vector:new{0,0,0.01,1}},
            B={Vector:new{1.5,0,0.01,1}},
            C={Vector:new{0.75,1.3,0.01,1}},
            fill options={fill=blue!25, draw=blue!70!black, thick}
        ]
        \appendtriangle[
            A={Vector:new{0,0,0.02,1}},
            B={Vector:new{1.5,0,0.02,1}},
            C={Vector:new{0.75,1.3,0.02,1}},
            transformation={rot},
            fill options={fill=green!25, draw=green!60!black, thick}
        ]
        \appendtriangle[
            A={Vector:new{0,0,0.03,1}},
            B={Vector:new{1.5,0,0.03,1}},
            C={Vector:new{0.75,1.3,0.03,1}},
            transformation={rot:multiply(shift)},
            fill options={fill=red!25, draw=red!70!black, thick}
        ]
        \appendlabel[
            v={return Vector:new{0.5,-0.5,0,1}},
            text={original}
        ]
        \appendlabel[
            v={return Vector:new{-1,0.8,0,1}},
            text={rotated}
        ]
        \appendlabel[
            v={return Vector:new{3,2.6,0,1}},
            text={rotated$+$translated}
        ]
        \displaysimplices
    \end{tikzpicture}
    \caption{Transformation order for a simple object.  The blue triangle is
    in its base position, the green one has been rotated by~$\pi/4$ about the
    $z$-axis, and the red one has been rotated and then translated.
    Because the package uses row-vector convention, composed matrices read
    left to right.}
\end{figure}

\section{Filters}

Filters are small Lua predicates supplied through the \verb|filter| key. They
are evaluated after tessellation but before the occlusion sort. This gives the
user a convenient way to discard simplices that fall outside a desired region.

The filter environment depends on the simplex type. Points and labels expose
\verb|A|. Line segments expose \verb|A| and \verb|B|. Triangles expose
\verb|A|, \verb|B|, and \verb|C|. Each of these is a \verb|Vector| object in
homogeneous coordinates. A filter should return \verb|true| for simplices that
are to remain and \verb|false| for simplices that are to be discarded.

\begin{verbatim}
\appendsurface[
	ustart=-1, ustop=1, usamples=24,
	vstart=-1, vstop=1, vsamples=24,
	v={return Vector:new{u,v,0.3*(u^2-v^2),1}},
	fill options={fill=ltdtbrightness, draw=black, very thin},
	filter={
		return A[3] >= 0 and B[3] >= 0 and C[3] >= 0
	}
]
\end{verbatim}

This style of predicate is intentionally direct. It keeps the clipping rule in
an algebraic form that can be inspected at a glance. In more elaborate scenes,
it is often better to define the relevant vectors or matrices once with
\verb|\setobject| and then write the filter in terms of those named objects.

\begin{figure}[tbp]
    \centering
    \begin{tikzpicture}
        \setobject[
            name=view,
            object={Matrix.xrotation3(-pi/5):multiply(Matrix.zrotation3(pi/6))}
        ]
        \appendlight[v={return Vector:new{1,0.5,1.5,1}}]
        \appendsurface[
            ustart=-1.2, ustop=1.2, usamples=28,
            vstart=-1.2, vstop=1.2, vsamples=28,
            v={return Vector:new{2*u, 2*v, 0.6*(u*u-v*v), 1}},
            transformation={view},
            fill options={fill=ltdtbrightness, draw=black, very thin},
            filter={
                return A[3] >= 0 and B[3] >= 0 and C[3] >= 0
            }
        ]
        \displaysimplices
    \end{tikzpicture}
    \caption{A saddle surface with the region below the~$xy$-plane removed
    by a symbolic filter.  The predicate
    \texttt{return A[3] >= 0 and B[3] >= 0 and C[3] >= 0} discards any
    triangle whose projected vertices have negative $z$-coordinates.
    Filtering acts on the tessellated simplices, not on the symbolic
    surface.}
\end{figure}

Filters become significantly more powerful when they operate in a
coordinate system other than world space.  A common technique is to define
an inverse map that projects each simplex centroid back into the parameter
domain of the surface, and then test membership in a region described in
that domain.  This lets one cut elaborate shapes from a surface even when
the corresponding world-space boundary would be impractical to express
analytically.

The following example demonstrates this approach on a torus.  The object
\verb|torusinverse| maps a world-space point back to the torus parameters
$(u,v)\in[0,2\pi)^2$, and the filter discards any triangle whose centroid
is too close to a reference point in parameter space.  A second surface
fills the excised region with a contrasting colour, making the boundary
clearly visible.

\begin{verbatim}
\setobject[
    name = {torusinverse},
    object = {
        function(p)
            local x, y, z = p[1], p[2], p[3]
            local theta = atan2(y, x)
            if theta < 0 then theta = theta + tau end
            local phi = atan2(sqrt(x^2+y^2) - 3, z)
            if phi < 0 then phi = phi + tau end
            return Vector:new{theta, phi, 0, 1}
        end
    }
]
\appendsurface[
    ...,
    filter = {
        torusinverse(
            A:hadd(B):hadd(C):hscale(1/3)
                :multiply(viewinverse)
        ):hdistance(Vector:new{2,2,0,1}) > 0.5
    }
]
\end{verbatim}

\begin{figure}[tbp]
    \centering
    \begin{tikzpicture}
        \setobject[
            name = {view},
            object = {
                Matrix.zyzrotation3(pi/2, pi/2+0.1, 3.75*pi/6)
            }
        ]
        \setobject[
            name = {viewinverse},
            object = {view:inverse()}
        ]
        \setobject[
            name = {torusinverse},
            object = {
                function(p)
                    local x, y, z = p[1], p[2], p[3]
                    local theta = atan2(y, x)
                    if theta < 0 then theta = theta + tau end
                    local phi = atan2(sqrt(x^2 + y^2) - 3, z)
                    if phi < 0 then phi = phi + tau end
                    return Vector:new{theta, phi, 0, 1}
                end
            }
        ]
        \setobject[
            name = {holecentre},
            object = {Vector:new{2, 2, 0, 1}}
        ]
        \setobject[
            name = {holeradius},
            object = {0.5}
        ]
        \appendlight[v={return Vector:new{1, 1, 1, 1}}]
        % Patch filling the excised region
        \appendsurface[
            ustart = {0}, ustop = {tau}, usamples = {20},
            vstart = {-1}, vstop = {0.5}, vsamples = {4},
            v = {return Vector:new{
                3*cos(cos(u)/2+2)
                    + Vector.hsphere3(cos(u)/2+2, sin(u)/2+2, 1+v)[1],
                3*sin(cos(u)/2+2)
                    + Vector.hsphere3(cos(u)/2+2, sin(u)/2+2, 1+v)[2],
                Vector.hsphere3(cos(u)/2+2, sin(u)/2+2, 1+v)[3],
                1
            }},
            transformation = {view},
            filter = {false},
            fill options = {
                preaction = {fill opacity=1, fill=gray},
                postaction = {draw=red, line width=0.2pt,
                    line join=round, line cap=round}
            }
        ]
        % Main torus with hole
        \appendsurface[
            ustart = {0}, ustop = {tau}, usamples = {20},
            vstart = {0}, vstop = {tau}, vsamples = {14},
            v = {return Vector:new{
                3*cos(u) + Vector.hsphere3(u, v, 1)[1],
                3*sin(u) + Vector.hsphere3(u, v, 1)[2],
                Vector.hsphere3(u, v, 1)[3],
                1
            }},
            transformation = {view},
            fill options = {
                preaction = {fill opacity=1, fill=ltdtbrightness},
                postaction = {draw, line width=0.2pt,
                    line join=round, line cap=round}
            },
            filter = {
                torusinverse(
                    A:hadd(B):hadd(C):hscale(1/3)
                        :multiply(viewinverse)
                ):hdistance(holecentre) > holeradius
            }
        ]
        \displaysimplices
    \end{tikzpicture}
    \caption{A torus with an excised region defined in parameter space.
    The named function \texttt{torusinverse} maps the centroid of each
    triangle back to the torus parameters~$(u,v)$; triangles whose
    image lies within a disc of radius~$0.5$ around the point~$(2,2)$
    are discarded.  A second, coarser surface fills the hole with a
    contrasting colour to make the boundary visible.  This technique
    generalises to any surface for which an inverse parametrization can
    be written.}
\end{figure}

\section{Lighting and final rendering}

Directional lights are appended with \verb|\appendlight|.  During rendering,
each triangle's normal is compared to the light directions, and the package
defines a TikZ color called \verb|ltdtbrightness| from the resulting average
intensity.  The falloff is linear in the angle between the triangle normal and
the light direction: a triangle facing the light is bright, and one
perpendicular to it is dark.

In practical use, this means that surface fill options often take the form
\verb|fill=ltdtbrightness| together with a draw style.  If no lights are
present, the brightness resolves to black.  Lights are cleared after each
call to \verb|\displaysimplices|, just as the scene geometry itself is
cleared.

Labels are handled differently from other scene elements.  They pass through
filtering but are emitted after the rest of the geometry.  A label should
therefore be thought of as an annotation layer rather than as an occluded
object.

\begin{figure}[tbp]
    \centering
    \begin{tikzpicture}
        \setobject[
            name = {view},
            object = {Matrix.xrotation3(-pi/4):multiply(Matrix.zrotation3(pi/5))}
        ]
        \appendlight[v={return Vector:new{1, 0.8, 1.5, 1}}]
        % A torus
        \appendsurface[
            ustart = {0}, ustop = {tau}, usamples = {28},
            vstart = {0}, vstop = {tau}, vsamples = {16},
            v = {return Vector:new{
                (2 + 0.7*cos(v))*cos(u),
                (2 + 0.7*cos(v))*sin(u),
                0.7*sin(v),
                1
            }},
            transformation = {view},
            fill options = {
                preaction = {fill=ltdtbrightness},
                postaction = {draw=black, line width=0.1pt,
                    line join=round}
            }
        ]
        \displaysimplices
    \end{tikzpicture}
    \caption{A torus illuminated by a single directional light.  The
    colour \texttt{ltdtbrightness} is computed per-triangle from the angle
    between the triangle normal and the light direction.  Regions facing
    the light appear white, while those perpendicular to it approach
    black.}
\end{figure}