% lua-tikz3dtools-doc.tex
\DocumentMetadata{
	lang         = en
	,pdfversion  = 2.0
	,pdfstandard = {UA-2}
}
\documentclass[letterpaper]{article}

\usepackage{microtype}
\usepackage{mathtools}
\usepackage{unicode-math}
\usepackage{lua-tikz3dtools}
\usepackage{bookmark}

\title{The \luatikztdtools{} package v2.2.0\\\vspace{.5cm}\tiny{}https://github.com/Pseudonym321/TikZ-Animations/tree/master1/TikZ/lua-tikz3dtools}
\author{Jasper Nice}
\date{November 29\textsuperscript{nd}, 2025}

\begin{document}

\maketitle

This work is licensed under the \LaTeX{} Project Public License,
version 1.3c or later.\par
This work was typeset using \TeX{}, the typesetting system 
created by Donald~E.~Knuth, along with various extensions 
and packages developed by the \TeX{} community. I am grateful 
to the vibrant \TeX{} Stack Exchange community for their ongoing 
support and resources. For those interested, my contributions 
can be found at 
\href{https://tex.stackexchange.com/users/319072/jasper}{Jasper}
\par
\vspace{2em}
\hfill\textit{Jasper~Nice}

\tableofcontents

\section{What the heck is a projective transformation?}

Unfortunately, in order to use \luatikztdtools{}, you need
to know how to do matrix multiplications. This can be learned 
in one semester of linear algebra---which is all I currently have.
Linear algebra involves linear transformations, which exclude 
translations and perspective transformations. These linear 
transformations are encoded in \(3\times3\) matrice (for 3D).
This package also uses row-vector convention (because it is 
more convenient to code with), so our vectors are multiplied on the 
left of the transformation matrix. Using a homogeneous component,
these linear transformation matrices can be transformed into 
affine and projective transformation matrices. I suggest the 
mathematical elements for computer graphics book by David Rogers
(I recommend the first edition; it is free on archive.org) for 
learning about projective transformations. Read chapters two and 
three, and you'll be set.

\section{Getting started: drawing a sphere}

Before I drown you in documentation, here are some simple diagrams
to get you started (see the source for the code):

\begin{figure}
\centering
\begin{luatikztdtoolspicture}[
    C1 = {
        {{-2,-2,1,1}}
    }
    ,C2 = {
        {{2,2,-4,1}}
    }
    ,light = {
        {{0,0,-2,1}}
    }
]
    \setobject[
        name = {T}
        ,object = {
            matrix_multiply(
				matrix_multiply(
					euler(pi/3,pi/3,-3*pi/6)
					,matrix_multiply(
						translate(0,0,-2)
						,{
							{1,0,0,0}
							,{0,1,0,0}
							,{0,0,1,-1/3}
							,{0,0,0,1}
						}
					)
				)
				,matrix_multiply(
					xscale(2)
					,yscale(2)
				)
            )
        }
    ]
    \appendsurface[
        ustart = {0}
        ,ustop = {1}
        ,usamples = {18}
        ,vstart = {0}
        ,vstop = {1}
        ,vsamples = {9}
        ,transformation = {T}
        ,x = {sphere(u*tau, v*pi)[1][1]}
        ,y = {sphere(u*tau, v*pi)[1][2]}
        ,z = {sphere(u*tau, v*pi)[1][3]}
        ,fill options = {
            preaction = {
                fill = gray!70!white
                ,fill opacity = 0.7
            }
            ,postaction = {
                draw = black
                ,ultra thin 
                ,line cap = round
                ,line join = round
            }
        }
    ]
    \appendsolid[
        ustart = {-1}
        ,ustop = {1}
        ,usamples = {2}
        ,vstart = {-1}
        ,vstop = {1}
        ,vsamples = {2}
        ,wstart = {-1}
        ,wstop = {1}
        ,wsamples = {2}
        ,transformation = {T}
        ,x = {u}
        ,y = {v}
        ,z = {w}
        ,fill options = {
            preaction = {
                fill = gray!70!white
                ,fill opacity = 0.1
            }
            ,postaction = {
                draw = black
                ,ultra thin 
                ,line cap = round
                ,line join = round
            }
        }
    ]
\end{luatikztdtoolspicture}
\caption{A sphere inside a cube, in perspective}
\label{fig:sphere-cube}
\end{figure}


\begin{figure}
\centering
\begin{luatikztdtoolspicture}[
    C1 = {
        {{-3,-3,0,1}}
    }
    ,C2 = {
        {{3,3,-10,1}}
    }
    ,light = {
        {{0,0,-2,1}}
    }
]
    \pgfmathsetmacro{\param}{0}
    \setobject[
        name = {T}
        ,object = {
            matrix_multiply(
                matrix_multiply(
                    euler(pi/2,pi/4+pi/4*sin(\param),5.25*pi/6+\param)
                    ,translate(0,0,-5)
                )
                ,matrix_multiply(
                    {
                        {1,0,0,0}
                        ,{0,1,0,0}
                        ,{0,0,1,0}
                        ,{0,0,0,1}
                    }
                    ,matrix_multiply(xscale(1/2),yscale(1/2))
                )
            )
        }
    ]
    \setobject[
        name = {I}
        ,object = {
            matrix_inverse(T)
        }
    ]
    \appendsurface[
        ustart = {-2}
        ,ustop = {2}
        ,usamples = {20}
        ,vstart = {-2}
        ,vstop = {2}
        ,vsamples = {20}
        ,transformation = {T}
        ,x = {u}
        ,y = {v}
        ,z = {u^4 + v^4 - 4*u*v + 1}
        ,fill options = {
            preaction = {
                fill = green
                ,fill opacity = 0.2
            }
            ,postaction = {
                draw = blue 
                ,ultra thin
                ,line join = round
            }
        }
        ,filter = {
            abs(matrix_multiply(A,I)[1][3])<2.01
            and abs(matrix_multiply(B,I)[1][3])<2.01
            and abs(matrix_multiply(C,I)[1][3])<2.01
            and abs(matrix_multiply(A,I)[1][2])<2.01
            and abs(matrix_multiply(B,I)[1][2])<2.01
            and abs(matrix_multiply(C,I)[1][2])<2.01
            and abs(matrix_multiply(A,I)[1][1])<2.01
            and abs(matrix_multiply(B,I)[1][1])<2.01
            and abs(matrix_multiply(C,I)[1][1])<2.01
        }
    ]
    \appendsolid[
        ustart = {-2}
        ,ustop = {2}
        ,usamples = {2}
        ,vstart = {-2}
        ,vstop = {2}
        ,vsamples = {2}
        ,wstart = {-2}
        ,wstop = {2}
        ,wsamples = {2}
        ,transformation = {T}
        ,x = {u}
        ,y = {v}
        ,z = {w}
        ,fill options = {
            preaction = {
                fill = none 
                ,fill opacity = 0.5
            }
            ,postaction = {
                draw = none 
                ,ultra thin 
                ,line join = round 
                ,line cap = round
            }
        }
    ]
    \appendcurve[
        ustart = {0}
        ,ustop = {4}
        ,usamples = {2}
        ,x = {0}
        ,y = {0}
        ,z = {u}
        ,transformation = {T}
        ,arrow tip = {true}
        ,draw options = {
            draw
            ,ultra thin 
            ,line cap = round
        }
    ]
    \appendlabel[
        x = {0}
        ,y = {0}
        ,z = {4.3}
        ,transformation = {T}
        ,name = {\(z\)}
    ]
    \appendcurve[
        ustart = {0}
        ,ustop = {4}
        ,usamples = {2}
        ,x = {0}
        ,y = {u}
        ,z = {0}
        ,transformation = {T}
        ,arrow tip = {true}
        ,draw options = {
            draw
            ,ultra thin 
            ,line cap = round
        }
    ]
    \appendlabel[
        x = {0}
        ,y = {4.3}
        ,z = {0}
        ,transformation = {T}
        ,name = {\(y\)}
    ]
    \appendcurve[
        ustart = {0}
        ,ustop = {4}
        ,usamples = {2}
        ,x = {u}
        ,y = {0}
        ,z = {0}
        ,transformation = {T}
        ,arrow tip = {true}
        ,draw options = {
            draw
            ,ultra thin 
            ,line cap = round
        }
    ]
    \appendlabel[
        x = {4.3}
        ,y = {0}
        ,z = {0}
        ,transformation = {T}
        ,name = {\(x\)}
    ]
    \foreach \p in {-4,-3,...,4} {
        \appendcurve[
            ustart = {-4}
            ,ustop = {4}
            ,usamples = {2}
            ,x = {u}
            ,y = {\p}
            ,z = {0}
            ,transformation = {T}
            ,draw options = {
                draw = black!70!white
                ,ultra thin 
                ,line cap = round
                ,densely dashed
            }
        ]
        \appendcurve[
            ustart = {-4}
            ,ustop = {4}
            ,usamples = {2}
            ,x = {\p}
            ,y = {u}
            ,z = {0}
            ,transformation = {T}            
            ,draw options = {
                draw = black!90!white
                ,ultra thin 
                ,line cap = round
                ,densely dashed
            }
        ]
    }
    \appendsurface[
        ustart = {-4}
        ,ustop = {4}
        ,usamples = {2}
        ,vstart = {-4}
        ,vstop = {4}
        ,vsamples = {2}
        ,transformation = {T}
        ,x = {u}
        ,y = {v}
        ,z = {0}
        ,fill options = {
            preaction = {
                fill = gray!70!white
                ,fill opacity = 0.6
            }
            ,postaction = {
                draw = none 
                ,ultra thin
                ,line join = round
            }
        }
    ]
\end{luatikztdtoolspicture}
\caption{A case in volving filtering and partitioning}
\label{fig:filter-partition}
\end{figure}

\begin{figure}
\centering
\pgfmathsetmacro{\i}{15}
\begin{luatikztdtoolspicture}[
    C1 = {
        {{-2,-2,0,1}}
    }
    ,C2 = {
        {{2,2,-4,1}}
    }
    ,light = {
        {{0,0,-2,1}}
    }
]
    \setobject[
        name = {T}
        ,object = {
            matrix_multiply(
                matrix_multiply(
                    euler(pi/2,pi/3,3.5*pi/6+0*2*pi/3)
                    ,translate(0,0,-2+\i/10)
                )
                ,matrix_multiply(
                    {
                        {1,0,0,0}
                        ,{0,1,0,0}
                        ,{0,0,1,0}
                        ,{0,0,0,1}
                    }
                    ,matrix_multiply(xscale(1),yscale(1))
                )
            )
        }
    ]
    \setobject[
        name = {I}
        ,object = {
            matrix_inverse(T)
        }
    ]
    \pgfmathsetmacro{\i}{pi/3}
    \foreach \j in {2,3,1,0,4}{
    \appendsurface[
        ustart = {-2}
        ,ustop = {2}
        ,usamples = {2}
        ,vstart = {-2}
        ,vstop = {2}
        ,vsamples = {2}
        ,transformation = {matrix_multiply(euler(\j+\i,2*\j,\j),T)}
        ,x = {u}
        ,y = {v}
        ,z = {
            \j/10
        }
        ,fill options = {
            preaction = {
                \ifnum\j=0
                fill = red!50!brightness
                \fi 
                \ifnum\j=2
                fill = green!50!brightness
                \fi 
                \ifnum\j=1
                fill = yellow!50!brightness
                \fi 
                \ifnum\j=3
                fill = blue!50!brightness
                \fi 
                \ifnum\j=4
                fill = orange!50!brightness
                \fi 
                ,fill opacity = 0.6
            }
            ,postaction = {
                draw = none 
                ,ultra thin 
                ,line join = round 
                ,line cap = round
            }
        }
        ,filter = {
            abs(reciprocate_by_homogenous(matrix_multiply(A,I))[1][3])<1.01
            and abs(reciprocate_by_homogenous(matrix_multiply(B,I))[1][3])<1.01
            and abs(reciprocate_by_homogenous(matrix_multiply(C,I))[1][3])<1.01
            and abs(reciprocate_by_homogenous(matrix_multiply(A,I))[1][2])<1.01
            and abs(reciprocate_by_homogenous(matrix_multiply(B,I))[1][2])<1.01
            and abs(reciprocate_by_homogenous(matrix_multiply(C,I))[1][2])<1.01
            and abs(reciprocate_by_homogenous(matrix_multiply(A,I))[1][1])<1.01
            and abs(reciprocate_by_homogenous(matrix_multiply(B,I))[1][1])<1.01
            and abs(reciprocate_by_homogenous(matrix_multiply(C,I))[1][1])<1.01
        }
    ]
    }
    \appendsolid[
        ustart = {-1}
        ,ustop = {1}
        ,usamples = {2}
        ,vstart = {-1}
        ,vstop = {1}
        ,vsamples = {2}
        ,wstart = {-1}
        ,wstop = {1}
        ,wsamples = {2}
        ,transformation = {T}
        ,x = {u}
        ,y = {v}
        ,z = {w}
        ,fill options = {
            preaction = {
                fill = gray 
                ,fill opacity = 0.5
            }
            ,postaction = {
                draw = none 
                ,ultra thin 
                ,line join = round 
                ,line cap = round
            }
        }
    ]

    \appendcurve[
        ustart = {-1}
        ,ustop = {1.2}
        ,usamples = {2}
        ,x = {-1}
        ,y = {-1}
        ,z = {u}
        ,transformation = {T}
        ,arrow tip = {true}
        ,draw options = {
            draw
            ,ultra thin 
            ,line cap = round
        }
    ]
    \appendlabel[
        x = {-1}
        ,y = {-1}
        ,z = {1.5}
        ,transformation = {T}
        ,name = {\(z\)}
    ]
    \appendcurve[
        ustart = {-1}
        ,ustop = {1.2}
        ,usamples = {2}
        ,x = {-1}
        ,y = {u}
        ,z = {-1}
        ,transformation = {T}
        ,arrow tip = {true}
        ,draw options = {
            draw
            ,ultra thin 
            ,line cap = round
        }
    ]
    \appendlabel[
        x = {-1}
        ,y = {1.5}
        ,z = {-1}
        ,transformation = {T}
        ,name = {\(y\)}
    ]
    \appendcurve[
        ustart = {-1}
        ,ustop = {1.2}
        ,usamples = {2}
        ,x = {u}
        ,y = {-1}
        ,z = {-1}
        ,transformation = {T}
        ,arrow tip = {true}
        ,draw options = {
            draw
            ,ultra thin 
            ,line cap = round
        }
    ]
    \appendlabel[
        x = {1.5}
        ,y = {-1}
        ,z = {-1}
        ,transformation = {T}
        ,name = {\(x\)}
    ]
\end{luatikztdtoolspicture}
\caption{Intersecting planes}
\label{fig:planes}
\end{figure}

\section{Filtering surfaces: problems and possibilities}

Filtering surfaces works when we don't use perspective.
Currently, due to a bug, perspective breaks the filtering.
I'm open to hear from anyone if they have a fix.

Additionally, the partitioning still has a bug due to degenerate
triangles, so I'm all ears on that too.

\section{Documentation of Commands and Keys}

This section summarizes the main commands and configuration keys of the \luatikztdtools{} package.

This section is ChatGPT generated, and looks OK to me.

\subsection{Setting Objects}

\begin{itemize}
    \item \verb|\setobject[<options>]|  
    Defines a 3D object with a transformation matrix. Options are passed as TikZ keys:
    \begin{itemize}
        \item \texttt{name} — Name of the object.
        \item \texttt{object} — Transformation matrix (default: \texttt{identity\_matrix()}).
    \end{itemize}
\end{itemize}

\subsection{Appending Points and Labels}

\begin{itemize}
    \item \verb|\appendpoint[<options>]|  
    Adds a point in 3D space.
    \begin{itemize}
        \item \texttt{x, y, z} — Coordinates of the point (default: 0,0,0).
        \item \texttt{fill options} — TikZ styling for the point (default: \texttt{fill}).
        \item \texttt{transformation} — Transformation matrix applied to the point (default: identity).
    \end{itemize}

    \item \verb|\appendlabel[<options>]|  
    Adds a label at a 3D position.
    \begin{itemize}
        \item \texttt{x, y, z} — Coordinates of the label (default: 0,0,0).
        \item \texttt{name} — Text of the label (default: \texttt{George}).
        \item \texttt{transformation} — Transformation applied to the label (default: identity).
    \end{itemize}
\end{itemize}

\subsection{Appending Curves, Surfaces, and Solids}

\begin{itemize}
    \item \verb|\appendcurve[<options>]|  
    Adds a parametric 3D curve.
    \begin{itemize}
        \item \texttt{ustart, ustop} — Parameter range for the curve (default: 0 to 1).
        \item \texttt{usamples} — Number of samples along the curve (default: 64).
        \item \texttt{x, y, z} — Parametric functions of the parameter \(u\).
        \item \texttt{transformation} — Transformation matrix applied to the curve.
        \item \texttt{draw options} — TikZ styling.
        \item \texttt{arrow tip/tail, arrow tip/tail options} — Optional arrowheads.
        \item \texttt{filter} — Boolean or Lua condition for selective drawing.
    \end{itemize}

    \item \verb|\appendsurface[<options>]|  
    Adds a parametric 3D surface.
    \begin{itemize}
        \item \texttt{ustart, ustop, vstart, vstop} — Parameter ranges.
        \item \texttt{usamples, vsamples} — Number of samples along \(u\) and \(v\).
        \item \texttt{x, y, z} — Parametric functions of \(u\) and \(v\).
        \item \texttt{transformation} — Transformation matrix.
        \item \texttt{fill options} — TikZ styling for the surface.
        \item \texttt{filter} — Condition to include/exclude surface points.
    \end{itemize}

    \item \verb|\appendsolid[<options>]|  
    Adds a parametric 3D solid (volume).
    \begin{itemize}
        \item \texttt{ustart, ustop, vstart, vstop, wstart, wstop} — Parameter ranges.
        \item \texttt{usamples, vsamples, wsamples} — Sampling resolution.
        \item \texttt{x, y, z} — Parametric functions of \(u, v, w\).
        \item \texttt{transformation} — Transformation matrix.
        \item \texttt{fill options} — TikZ styling for the solid.
        \item \texttt{filter} — Boolean or Lua condition for selective drawing.
    \end{itemize}
\end{itemize}

\subsection{Package Options and Keys}

All keys are accessible through Ti\textit{k}Z's path system, under the family \texttt{/lua-tikz3dtools}.  
Subcategories:
\begin{itemize}
    \item \texttt{/parametric/matrix} — Transformation matrices.
    \item \texttt{/parametric/point} — Individual points.
    \item \texttt{/parametric/label} — Labels in 3D space.
    \item \texttt{/parametric/curve} — Parametric curves.
    \item \texttt{/parametric/surface} — Parametric surfaces.
    \item \texttt{/parametric/solid} — Parametric solids.
\end{itemize}

\section{Matrix Operations and Transformations in Parametric Code}

Again, this part is ChatGPT generated. Note that new objects can be made with the 
\verb|\setobject| command.

Inside all parametric fields of \luatikztdtools{} (for instance in
\verb|\appendcurve|, \verb|\appendsurface|, and filtering conditions), a small
collection of matrix commands is available.  
These functions originate from the internal module \texttt{mm}, but inside
parametric expressions they are used \emph{without} any prefix.

All transformations below return \(4\times4\) matrices acting on homogeneous
row-vectors \((x,y,z,1)\) using the row-vector convention adopted by the
package.

\subsection{Core Matrix Operations}

\begin{description}
  \item[\texttt{matrix\_multiply(A,B)}]  
    Computes the product \(A\cdot B\).  
    All chained transformations are formed using this routine.

  \item[\texttt{matrix\_inverse(A)}]  
    Returns the inverse of a non-singular square matrix using 
    Gauss--Jordan elimination.
\end{description}

\subsection{Standard 3D Transformations}

\begin{description}
  \item[\texttt{xrotation(angle)}]  
    Rotation about the \(x\)-axis by the given angle.

  \item[\texttt{yrotation(angle)}]  
    Rotation about the \(y\)-axis.

  \item[\texttt{zrotation(angle)}]  
    Rotation about the \(z\)-axis.

  \item[\texttt{euler($\alpha,\beta,\gamma$)}]  
    Returns the composed rotation  
    \[
        R_z(\gamma)\,R_y(\beta)\,R_z(\alpha).
    \]

  \item[\texttt{translate(x,y,z)}]  
    Translation by the vector \((x,y,z)\).

  \item[\texttt{xscale(s)}, \texttt{yscale(s)}, \texttt{zscale(s)}]  
    Scaling in the respective coordinate direction.

  \item[\texttt{scale(s)}]  
    Uniform scaling in all coordinates.

  \item[\texttt{scale3(x,y,z)}]  
    General non-uniform scaling by three independent factors.
\end{description}

\medskip

These commands can be freely combined using \texttt{matrix\_multiply} to build
arbitrary affine (and some projective) transformations directly inside
parametric expressions.


\end{document}