\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.