\section{Coordinates and Transformations}
\label{sec:transforms}

lua-tikz3dtools uses homogeneous coordinates throughout. A point in space is
typically represented as a row vector \verb|Vector:new{x, y, z, 1}|. Transformations
are represented by $4 \times 4$ matrices acting on the right.

\subsection{Homogeneous points and directions}

For day-to-day use, treat the final component as the homogeneous coordinate and
write points with last component equal to $1$. The package automatically performs
the projective divide after applying a transformation to a point or to a sampled
simplex.

The most common constructor forms are:

\begin{Verbatim}
Vector:new{1.2, -0.4, 0.8, 1}
Matrix.identity()
Matrix.translate(Vector:new{0, 0, 2, 1})
\end{Verbatim}

\subsection{Row-vector convention}

This package uses a row-vector convention. If a point \(p\) is transformed by a
matrix \(M\), the package computes \(pM\), not \(Mp\). As a consequence,
composition order is read from left to right:

\[
p(AB) = (pA)B.
\]

So if you want to rotate first and then translate, you should write the
transformation as

\begin{Verbatim}
Matrix.axis_angle(Vector:new{0, 0, 1, 1}, 0.8)
  :multiply(Matrix.translate(Vector:new{2, 0, 0, 1}))
\end{Verbatim}

This is the opposite of the column-vector convention used in many graphics texts.
It is worth keeping this straight early, because most apparent transformation bugs
in user code are really order-of-composition mistakes.

\subsection{Built-in transformation constructors}

The matrix layer provides the following constructors for normal use:

\begin{itemize}[leftmargin=2em]
\item \verb|Matrix.identity()|;
\item \verb|Matrix.translate(Vector:new{dx,dy,dz,1})|;
\item \verb|Matrix.scale_axis(Vector:new{sx,sy,sz,1})|;
\item \verb|Matrix.axis_angle(axis, theta)|;
\item \verb|Matrix.zyzrotation(Vector:new{alpha,beta,gamma})|;
\item \verb|Matrix.shear(kxy, kxz, kyx, kyz, kzx, kzy)|;
\item \verb|Matrix.reflect_axis(axis)|;
\item \verb|Matrix.perspective(Vector:new{px,py,pz,1})|;
\item \verb|Matrix.transform_about(point, transformation)|.
\end{itemize}

The older helper names \verb|xrotation3|, \verb|yrotation3|, \verb|zrotation3|,
\verb|translate3|, \verb|scale3|, and \verb|zyzrotation3| are retained for
compatibility, but the vector-valued constructors are clearer because they keep
the entire transformation family in one style.

\subsection{Transforming about a fixed point}

It is often easier to build a local motion around a distinguished point than to
write the full translation--rotation--translation sequence yourself. The helper
\verb|Matrix.transform_about(point, transformation)| does exactly that.

\begin{Verbatim}
\setobject[
  name = hinge,
  object = {return Vector:new{1, 0, 0, 1}}
]

\setobject[
  name = swing,
  object = {
    return Matrix.transform_about(
      hinge,
      Matrix.axis_angle(Vector:new{0, 0, 1, 1}, 0.7)
    )
  }
]
\end{Verbatim}

\input{figures/05-transform-composition.tex}

\subsection{Perspective}

Perspective is encoded through a projective matrix and the automatic homogeneous
divide that follows matrix application. Small values often suffice. Because the
effect is projective rather than Euclidean, it is usually best to introduce
perspective only after the underlying figure already renders correctly under an
affine transform.

\begin{Verbatim}
transformation = {
  return Matrix.axis_angle(Vector:new{1, 0, 0, 1}, 0.9)
    :multiply(Matrix.axis_angle(Vector:new{0, 0, 1, 1}, 0.4))
    :multiply(Matrix.perspective(Vector:new{0, 0, 0.15, 1}))
}
\end{Verbatim}

When perspective is active, extremely coarse sampling can exaggerate visual
artifacts. If a curve or surface looks jagged only after turning perspective on,
the first adjustment to make is the sample count rather than the sorting logic.