\section{Faceted Solids}

\subsection{Definition of a Solid}

There are two ways to define a solid:
\begin{enumerate}
    \item As a list (table) of facets. A facet is itself a list of 3D points (at least 3) that are coplanar and unaligned, which are the vertices. Facets are assumed to be convex and are oriented by the order of appearance of the vertices. That is, if $A$, $B$, and $C$ are the first three vertices of a facet $F$, then the facet is oriented with the normal vector $\vec{AB}\wedge\vec{AC}$. If this normal vector is directed toward the observer, then the facet is considered visible. The method \cmd{g:Cosine\_incidence(N \fac{, A})} returns the cosine of the angle at point \argu{A} between the vector \argu{N} (which must be unit) and the unit vector directed towards the observer; if this cosine is positive then \argu{N} is directed towards the observer (point \argu{A} is optional EXCEPT in central projection).


In the definition of a solid, the normal vectors to the facets must be directed \textbf{outside} the solid for the orientation to be correct.

    \item In the form of a \textbf{polyhedron}, that is to say a table with two fields, a first field called \emph{vertices} which is the list of vertices of the polyhedron (3D points), and a second field called \emph{facets} which is the list of facets, but here, in the definition of the facets, the vertices are replaced by their index in the \emph{vertices} list. The facets are oriented in the same way as before. This definition corresponds to the \emph{obj} format but without normal vectors. 
\end{enumerate}

For example, let's consider the four points $A=M(-2,-2,0)$, $B=M(3,0,0)$, $C=M(-2,2,0)$, and $D=M(0,0,4)$. We can then define the tetrahedron constructed on these four points:
\begin{itemize}
    \item either as a list of facets: \code{T=\{\{A,B,D\},\{B,C,D\},\{C,A,D\},\{A,C,B\}\}} (pay attention to the orientation),
    \item or as a polyhedron:
\code{T=\{vertices=\{A,B,C,D\},facets=\{\{1,2,4\},\{2,3,4\},\{3,1,4\},\{1,3,2\}\}\}}.
\end{itemize}

\paragraph{Functions for converting between the two definitions}
\begin{itemize}
    \item The function \cmd{ld.poly2facet(P)} where \argu{P} is a polyhedron, returns this solid as a list of facets.
    \item The function \cmd{ld.facet2poly(L \fac{, epsilon})} returns the list of facets \argu{L} as a polyhedron. The optional argument \argu{epsilon} defaults to $10^{-8}$, specifying the accuracy of the comparisons between 3D points. If there are many facets, the computation time can become significant.
\end{itemize}

\subsection{Drawing a Polyhedron: Dpoly}

The function \cmd{g:Dpoly(P, options)} allows you to represent the polyhedron \argu{P} (using the naive painter's algorithm). The argument \argu{options} is a table containing the options, these are, with their default value:
\begin{itemize}
    \item \opt{mode=ld.mShaded}: Sets the representation mode. There are six possible values:
\begin{itemize}
    \item \val{ld.mWireframe}: Wireframe mode, draws both visible and hidden edges.
    \item \val{ld.mFlat}: Draws solid-color faces, as well as visible edges.
    \item \val{ld.mFlatHidden}: Draws solid-color faces, visible edges, and hidden edges.
    \item \val{ld.mShaded}: Draws the faces in shaded color based on their inclination, as well as the visible edges. This is the default mode.
    \item \val{ld.mShadedHidden}: Draws the faces in shaded color based on their inclination, with both visible and hidden edges.
    \item \val{ld.mShadedOnly}: Draws the faces in shaded color based on their inclination, but not the edges.
\end{itemize}
    \item \opt{contrast=1}: This number allows you to accentuate or diminish the shade of the facet colors in the \val{ld.mShaded}, \val{ld.mShadedHidden}, and \val{ld.mShadedOnly} modes.
    \item \opt{edgestyle=<current style>}: This is a string that defines the line style of the edges.
    \item \opt{edgecolor=<current line color>}: This is a string that defines the edge color.
    \item \opt{hiddenstyle=ld.Hiddenlinestyle}: This is a string that defines the line style of hidden edges. By default, this is the value contained in the global variable \varglob{ld.Hiddenlinestyle} (which itself is \val{"dotted"} by default).
    \item \opt{hiddencolor=<current line color>}: This is a string that defines the color of hidden edges.
    \item \opt{edgewidth=<current line width>}: This is the line thickness of edges in tenths of a point.
    \item \opt{opacity=1} : This is a number between 0 and 1 that allows you to set transparency or not on the facets.
    \item \opt{backcull=false}: When this boolean is \true, facets considered invisible (normal vectors not directed towards the observer) are not displayed. This option is useful for convex polyhedra because it reduces the number of facets to draw.
    
    \item \opt{twoside=true}: Boolean that defaults to true, meaning that both sides of the facets (inner and outer) are distinguished; the two sides will not have exactly the same color.
    
    \item \opt{reverse=false} : with the value \true the orientation of the facets is reversed.
    
    \item \opt{color="white"}: String defining the fill color of the facets.
    \item \opt{usepalette=nil}:  this option allows you to specify a color palette for painting the facets as well as a calculation mode, the syntax is: \opt{usepalette=\{palette,mode\}}, where \argu{palette} designates a table of colors which are themselves tables of the form $\{r,g,b\}$ where $r$, $g$ and $b$ are numbers between $0$ and $1$. The argument  \argu{mode} can be :
    \begin{itemize}
        \item one of the strings: \val{"x"}, \val{"y"}, \val{"z"}. In the first case, for example, facets with the minimum x-coordinate at their centroid have the first color in the palette, facets with the maximum x-coordinate at their centroid have the last color in the palette, and for the others, the color is calculated based on the x-coordinate of the centroid by linear interpolation.
        \item a function: \argu{mode}$\colon f \mapsto \mathrm{mode}(f)\in\mathbb R$, where $f$ denotes a facet (a list of 3D points). Facets with the minimum value have the first color in the palette, those with the maximum value have the last color in the palette, and for the others, the color is calculated by linear interpolation.
    \end{itemize}
\end{itemize}

\begin{demo}{Section of a tetrahedron by a plane}
\begin{luadraw}{name=tetra_coupe}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M

local g = ld.graph3d:new{viewdir={10,60},bbox=false, size={10,10}, bg="gray!30"}
local A,B,C,D = M(-2,-4,-2),M(4,0,-2),M(-2,4,-2),M(0,0,2)
local T = ld.tetra(A,B-A,C-A,D-A) -- tetrahedron with vertices A, B, C, D
local plan = {Origin, -vecK} -- sectional plane
local T1, T2, section = ld.cutpoly(T,plan) -- we cut the tetrahedron
-- T1 is the resulting polyhedron in the half-space containing -vecK
-- T2 is the resulting polyhedron in the other half-space
-- section is a facet (it's the cut)
g:Dpoly(T1,{color="Crimson", edgecolor="white", opacity=0.8, edgewidth=8})
g:Filloptions("bdiag","Navy"); g:Dpolyline3d(section,true,"draw=none")
g:Dpoly(ld.shift3d(T2,2*vecK), {color="Crimson", edgecolor="white", opacity=0.8, edgewidth=8})
g:Dballdots3d({A,B,C,D+2*vecK}) -- we drew T2 translated with the vector 2*vecK
g:Show()
\end{luadraw}
\end{demo}

\subsection{Displaying the Face and/or Vertex Numbers of a Polyhedron}

The method \textbf{g:Dpolynames(P,option, opacity)} displays the polyhedron \emph{P} with the option to add to each face its number (which is its position in the \emph{P.facets} list) preceded by the letter \emph{F}, and optionally the number of each vertex (which is its position in the \emph{P.vertices} list) preceded by the letter \emph{V}. The argument \emph{option} can take the values: \emph{"facet"}, \emph{"vertex"}, or \emph{"both"} (which is the default value). The argument \emph{opacity} is a number between $0$ and $1$ which defaults to $0.6$.

\begin{demo}{Visualize the faces and vertices of a polyhedron}
\begin{luadraw}{name=show_facet_number}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M

local g = ld.graph3d:new{viewdir={30,60}, window={-2.5,5,-3,3},size={10,10}}
P = ld.parallelep(Origin, 4*vecI,5*vecJ,3*vecK)
local A, B, C = M(4,2.5,3), M(2,5,3), M(4,5,1.5)
P = ld.cutpoly(P, ld.plane(A,B,C), true) -- we cut P with the plane, and add a facet in place of the section
g:Dpolynames(P) -- we want to see facets and vertices numbers of P
g:Show()
\end{luadraw}
\end{demo}

\subsection{Polyhedron Construction Functions}

The following functions return a polyhedron, that is, a table with two fields: a first field called \emph{vertices}, which is the list of the polyhedron's vertices (3D points), and a second field called \emph{facets}, which is the list of facets. However, in the definition of facets, the vertices are replaced by their index in the \emph{vertices} list.

\begin{itemize}
    \item \cmd{ld.tetra(S, v1, v2, v3)} returns the tetrahedron constructed from vertex \argu{S} (3D point) and the three vectors \argu{v1}, \argu{v2}, \argu{v3} (3D points) assumed to be in the forward direction. The vertices of this tetrahedron are $S$, $S+v_1$, $S+v_2$, and $S+v_3$.

    \item \cmd{ld.parallelep(S, v1, v2, v3)} returns the parallelepiped constructed from vertex \argu{S} (3D point) and the three vectors \argu{v1}, \argu{v2}, \argu{v3} (3D points) assumed to be in the forward direction.

    \item \cmd{ld.prism(base, vector \fac{, open})} returns a prism. The argument \argu{base} is a list of 3D points (one of the two bases of the prism). \argu{vector} is the translation vector (3D point) used to obtain the second base. The optional argument \argu{open} is a Boolean indicating whether the prism is open or not (\false by default). If it is open, only the lateral facets are returned. The \argu{base} must be oriented by the \argu{vector}.

    \item \cmd{ld.pyramid(base, vertex \fac{, open})} returns a pyramid. The argument \argu{base} is a list of 3D points, and \argu{vertex} is the apex of the pyramid (3D point). The optional argument \argu{open} is a Boolean indicating whether the pyramid is open or not (\false by default). If it is open, only the side facets are returned. The base must be oriented with the apex.

    \item \cmd{ld.regular\_pyramid(n, side, height \fac{, open, center, axis})} returns a regular pyramid. \argu{n} is the number of sides of the base, the argument \argu{side} is the length of a side, and \argu{height} is the height of the pyramid. The optional argument \argu{open} is a Boolean indicating whether the pyramid is open or not (\false by default). If it is open, only the lateral facets are returned. The optional argument \argu{center} is the center of the base (\varglob{Origin} by default), and the optional argument \argu{axis} is a direction vector of the pyramid axis (\varglob{vecK} by default).

    \item \cmd{ld.truncated\_pyramid(base, vertex, height \fac{, open})} returns a truncated pyramid; the argument \argu{base} is a list of 3D points; \argu{vertex} is the apex of the pyramid (3D point). The argument \argu{height} is a number indicating the height from the base where the truncation occurs; this is parallel to the plane of the base. The optional argument \argu{open} is a boolean indicating whether the pyramid is open or not (\false by default). If it is open, only the lateral facets are returned. The base must be oriented by the apex.

    \item \cmd{ld.cylinder(A, V, R \fac{, nbfacet, open})} returns a right cylinder of radius \argu{R}, \argu{A} (3D point) is the center of one of the circular bases, and \argu{V} is a non-zero 3D vector such that the center of the second base is the point $A+V$. The optional argument \argu{nbfacet} is $35$ by default (number of lateral facets). The optional argument \argu{open} is a Boolean indicating whether the cylinder is open or not (\false by default). If it is open, only the lateral facets are returned.

    \item \cmd{ld.cylinder(A, R, B \fac{, nbfacet, open})} returns a right cylinder of radius \argu{R}, \argu{A} (3D point) is the center of one of the circular bases, and \argu{B} the center of the second base. The cylinder is right. The optional argument \argu{nbfacet} is $35$ by default (number of lateral facets). The optional argument \argu{open} is a Boolean indicating whether the cylinder is open or not (\false by default). If it is open, only the lateral facets are returned.

    \item \cmd{ld.cylinder(A, R, V, B \fac{, nbfacet, open})} returns a cylinder of radius \argu{R}, \argu{A} (3D point) is the center of one of the circular bases, and \argu{B} the center of the second base, and \argu{V} is a 3D vector normal to the plane of the circular bases (the cylinder can therefore be tilted). The optional argument \argu{nbfacet} is $35$ by default (number of lateral facets). The optional argument \argu{open} is a boolean indicating whether the cylinder is open or not (\false by default). If it is open, only the lateral facets are returned.
    
    \item \cmd{ld.cone(A, V, R \fac{, nbfacet, open})} returns a cone with vertex \argu{A} (3D point), axis  directed by \argu{V} (3D point), and base the circle with center $A+V$ and radius \argu{R} (in a plane orthogonal to \argu{V}). The optional argument \argu{nbfacet} is $35$ by default (number of lateral facets). The optional argument \argu{open} is a boolean indicating whether the cone is open or not (\false by default). If it is open, only the lateral facets are returned.

    \item \cmd{ld.cone(C, R, A \fac{, nbfacet, open})} returns a cone with vertex \argu{A} (3D point), \argu{C} is the circular base center, and \argu{R} is its radius (in a plane orthogonal to the $(AC)$ axis). The optional argument \argu{nbfacet} is $35$ by default (number of lateral facets). The optional argument \argu{open} is a Boolean indicating whether the cone is open or not (\false by default). If it is open, only the lateral facets are returned.

    \item \cmd{ld.cone(C, R, V, A \fac{, nbfacet, open})} returns a cone with vertex \argu{A} (3D point), \argu{C} is the circular base center, \argu{R} is its radius, and the base is in a plane orthogonal to \argu{V} (3D vector). The $(AC)$ axis is therefore not necessarily orthogonal to the circular face (tilted cone). The optional argument \argu{nbfacet} is $35$ by default (number of lateral facets). The optional argument \argu{open} is a Boolean indicating whether the cone is open or not (\false by default). If it is open, only the lateral facets are returned.

    \item \cmd{ld.frustum(C, R, r, V \fac{, nbfacet, open})} returns a right frustum. \argu{C} (3D point) is the center of the circular base of radius \argu{R}, and vector \argu{V} directs the axis of the frustum. The center of the other circular base is point $C+V$, and its radius is \argu{r} (the bases are orthogonal to \argu{V}). The optional argument \argu{nbfacet} is $35$ by default (number of lateral facets). The optional argument \argu{open} is a Boolean indicating whether the frustum is open or not (\false by default). If it is open, only the lateral facets are returned.

    \item \cmd{ld.frustum(C, R, r, V, A \fac{, nbfacet, open})} returns a frustum of a cone. \argu{C} (3D point) is the center of the circular base of radius \argu{R}, the center of the other circular base is point \argu{A}, and its radius is \argu{r}. The bases are orthogonal to vector \argu{V}, but not necessarily orthogonal to axis $(AC)$. The optional argument \argu{nbfacet} is $35$ by default (number of lateral facets). The optional argument \argu{open} is a Boolean indicating whether the frustum is open or not (\false by default). If it is open, only the lateral facets are returned.

    \item \cmd{ld.sphere(A, R \fac{, nbu, nbv})} returns the sphere with center \argu{A} (3D point) and radius \argu{R}. The optional argument \argu{nbu} represents the number of spindles ($36$ by default) and the optional argument \argu{nbv} the number of parallels ($20$ by default).
\end{itemize}

\begin{demo}{Truncated cone, truncated pyramid, oblique cylinder}
\begin{luadraw}{name=frustum_pyramid}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M

local g = ld.graph3d:new{adjust2d=true,bbox=false, size={10,10} }
g:Dfrustum(M(-1,-4,0),3,1,5*vecK, {color="cyan"})
g:Dcylinder(M(-4,4,0),2,vecK,M(-4,2,5), {color="orange"})
local base = ld.map(pt3d.toPoint3d, ld.polyreg(0,3,5))
g:Dpoly(ld.truncated_pyramid( ld.shift3d(base,8*vecI-vecJ-2*vecK), M(5,0,5),4), {mode=4,color="Crimson"})
g:Dcone(M(6,7,-2),3,vecK,M(6,8,5),{color="Pink"})
g:Show()
\end{luadraw}
\end{demo}

\paragraph{Note}: We already have primitives for drawing cylinders, cones, and spheres without using facets. One of the advantages of defining these objects as polyhedra is that we can perform certain calculations on them, such as plane sections.

\begin{demo}{Hyperbola: cone-plane intersection}
\begin{luadraw}{name=hyperbole}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M

local g = ld.graph3d:new{window={-8,6,-9,9},bbox=false, 
    viewdir={"central",45,65}, size={10,10}}
ld.Hiddenlinestyle = "dashed"; ld.Hiddenlines = true
local C1 = ld.cone(Origin,4*vecK,3,35,true)
local C2 = ld.cone(Origin, -4*vecK,3,35,true)
local P = {M(1,-1,-2),vecI} -- sectional plane
local I1 = g:Intersection3d(C1,P) -- intersection between cone C1 and plane P
local I2 = g:Intersection3d(C2,P) -- intersection between cone C2 and plane P
-- I1 and I2 are of the Edges type
g:Dcone(Origin,4*vecK,3,{color="orange"}); g:Dcone(Origin,-4*vecK,3,{color="orange"})
g:Lineoptions("solid","Navy",8)
g:Dedges(I1); g:Dedges(I2) -- drawing of edges I1 and I2
g:Dplane(P, vecK,14,9)
g:Show()
\end{luadraw}
\end{demo}

In this example, cones $C_1$ and $C_2$ are defined as polyhedra to determine their intersection with plane $P$, but not to draw them. The method \cmd{g:Intersection3d(C1, P)} returns the intersection of polyhedron $C_1$ with plane $P$ as a two-field table: one field named \emph{visible} that contains a 3D polygonal line representing the visible edges (segments) of the intersection (i.e., those that are on a visible facet of $C_1$), and another field named \emph{hidden} that contains a 3D polygonal line representing the hidden edges of the intersection (i.e., those that are on a non-visible facet of $C_1$). The method \cmd{g:Dedges()} can be used to draw these types of objects.

\begin{demo}{Cone section with multiple views}
\begin{luadraw}{name=several_views}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M

local g = ld.graph3d:new{window3d={-3,3,-3,3,-3,3}, size={10,10}, margin={0,0,0,0}}
g:Labelsize("footnotesize")
local y0, R = 1, 2.5
local C = ld.cone(M(0,0,3),-6*vecK,R,35,true) -- open cone
local P1 = {M(0,0,0),vecK+vecJ} -- first plane
local P2 = {M(0,y0,0),vecJ} -- second plane
local I, I2
local dessin = function() -- one drawing per view
    g:Dboxaxes3d({grid=true,gridcolor="gray",fillcolor="LightGray"})
    I1 = g:Intersection3d(C,P1) -- intersection entre le cône C et les plans P1 et P2
    I2 = g:Intersection3d(C,P2) -- I1 and I2 are the Edges type
    g:Dpolyline3d( {{M(0,-3,3),M(0,0,3),M(0,0,-3),M(3,0,-3)}, {M(0,0,-3),M(0,3,-3)}},"red,line width=0.4pt" )
    g:Dcone( M(0,0,3),-6*vecK,R, {color="cyan"})
    g:Dedges(I1, {hidden=true,color="Navy", width=8})
    g:Dedges(I2, {hidden=true,color="DarkGreen", width=8})
end
-- In the upper left corner, the view in space, we add the plans to the drawing
g:Saveattr(); g:Viewport(-5,0,0,5); g:Coordsystem(-7,6,-6,5,1); g:Setviewdir("central"); dessin()
g:Dpolyline3d( {M(-3,-3,3),M(3,-3,3),M(3,3,-3),M(-3,3,-3)},"Navy,line width=0.8pt")
g:Dpolyline3d( {M(-3,y0,3),M(3,y0,3),M(3,y0,-3)},"DarkGreen,line width=0.8pt")
g:Dlabel3d( "$P_1$",M(3,-3,3),{pos="SE",dir={-vecI,-vecJ+vecK},node_options="Navy, draw"})
g:Dlabel3d( "$P_2$",M(-3,y0,3),{pos="SW",dir={-vecI,vecK},node_options="DarkGreen,draw"})
g:Restoreattr()
-- top right, projection onto the xy-plane
g:Saveattr(); g:Viewport(0,5,0,5); g:Coordsystem(-6,6,-6,5,1); g:Setviewdir("xOy"); dessin()
g:Restoreattr()
-- bottom left, projection onto the xz-plane
g:Saveattr(); g:Viewport(-5,0,-5,0); g:Coordsystem(-6,6,-6,5,1); g:Setviewdir("xOz"); dessin()
g:Restoreattr()
-- bottom right, projection onto the yz-plane
g:Saveattr(); g:Viewport(0,5,-5,0); g:Coordsystem(-6,6,-6,5,1); g:Setviewdir("yOz"); dessin()
g:Restoreattr()
g:Show()
\end{luadraw}
\end{demo}

\subsection{Reading from an obj file}

The function \cmd{ld.read\_obj\_file(file)}\footnote{This function is a contribution by Christophe BAL.} allows you to read the contents of the file \emph{obj} designated by the string \argu{file}. The function reads the vertex definitions (lines beginning with \verb|v |), and the lines defining the facets (lines beginning with \verb|f |). The other lines are ignored. The function returns a sequence consisting of the polyhedron, followed by a list of six real numbers \code{\{x1,x2,y1,y2,z1,z2\}} representing the 3D bounding box of the polyhedron.

\begin{demo}{Mask of Nefertiti}
\begin{luadraw}{name=lecture_obj}
local ld = luadraw
local P,bbox = ld.read_obj_file("obj/nefertiti.obj")
local g = ld.graph3d:new{window3d=bbox,window={-6,5,-7,7},viewdir={"central",35,65,20},
margin={0,0,0,0}, size={10,10}, bg="LightGray"}
g:Dpoly(P, {usepalette={ld.palAutumn,"z"},mode=ld.mShadedOnly})
g:Show()
\end{luadraw}
\end{demo}

\subsection{Surface in obj format}

Two possible syntaxes:
\begin{enumerate}
    \item \cmd{ld.obj\_surface(f, u1, u2, v1, v2 \fac{, grid})} returns, in the format \emph{obj}, the surface parameterized by the function \argu{f}$\colon(u,v) \mapsto f(u,v)\in \mathbf R^3$. The interval for the parameter $u$ is given by \argu{u1} and \argu{u2}. The interval for the parameter $v$ is given by \argu{v1} and \argu{v2}. The optional parameter \argu{grid} defaults to $\{25,25\}$, and defines the number of points to calculate for the parameter $u$ followed by the number of points to calculate for the parameter $v$ (the values ​​of $u$ and $v$ are equally distributed).

    \item \cmd{ld.obj\_surface(f, mesh)} with \argu{mesh}$=\{\{u_1,\ldots,u_n\}, \{v_1,\ldots,v_m\}\}$ returns the surface parameterized by the function \argu{f}$\colon(u,v) \mapsto f(u,v)\in \mathbf R^3$. The values ​​of the parameters $u$ and $v$ are given by the argument \argu{mesh}; they must be in strictly ascending order, but they are not necessarily equally distributed.
\end{enumerate}

In both cases, the result is not a list of facets but a table with three fields:
\codeln{\{vertices=\{\ldots\}, normals=\{\ldots\}, facets=\{\{\ldots\},\{\ldots\},\ldots\} \}.}
\begin{itemize}
    \item The \emph{vertices} and \emph{facets} fields are identical to the case of polyhedra: lists of vertices (3D points) and a list of facets with vertex numbers, except that here the facets are all \textbf{triangular}.

    \item The \emph{normals} field is a list of unit 3D vectors representing normal vectors to the surface, one per vertex.
\end{itemize}


\subsection{Drawing a List of Facets: Dfacet and Dmixfacet}

There are two possible methods:
\begin{enumerate}
    \item For a solid $S$ in the form of a list of facets (with 3D points), the method is:
\cmdln{g:Dfacet(S, options)}
where \argu{S} is the list of facets and \argu{options} is a table defining the options. These are:
    \begin{itemize}
        \item \opt{mode=ld.mShaded}: Sets the representation mode. The possible values are:
            \begin{itemize}
                \item \val{ld.mWireframe}: Wireframe mode, draws only the edges. When the \opt{usepalette} option is not \nil, the color of each edge is calculated using the palette (in the same way as for facets when they are painted).
                
                \item \val{ld.mFlat} or \val{ld.mFlatHidden}: Draws the faces in a solid color, as well as the edges.   
                  
                \item \val{ld.mShaded} or \val{ld.mShadedHidden}: The faces are drawn in shaded color based on their inclination, as well as the edges.
                
                \item \val{ld.mShadedOnly}: The faces are drawn in shaded color based on their inclination, but not the edges.
            \end{itemize}
        \item \opt{contrast=1}: This number allows you to accentuate or diminish the shade of the facet colors in the \val{ld.mShaded}, \val{ld.mShadedHidden}, and \val{ld.mShadedOnly} modes.
        
        \item \opt{edgestyle=<current style>}: This is a string that defines the line style of the edges.
        
        \item \opt{edgecolor=<current line color>}: This is a string that defines the edge color.
        
        \item \opt{edgewidth=<current line width>}: This is the line thickness of edges in tenths of a point.
        
        \item \opt{opacity=1} : This is a number between 0 and 1 that allows you to set transparency or not on the facets.
        
        \item \opt{backcull=false}: When this boolean is \true, facets considered invisible (normal vectors not directed towards the observer) are not displayed. This option is useful for convex polyhedra because it reduces the number of facets to draw.
        
        \item \opt{twoside=true}: Boolean that defaults to true, meaning that both sides of the facets (inner and outer) are distinguished; the two sides will not have exactly the same color.
        
        \item \opt{reverse=false} : with the value \true the orientation of the facets is reversed.
        
        \item \opt{color="white"}: String defining the fill color of the facets.
        
        \item \opt{usepalette=nil}:  this option allows you to specify a color palette for painting the facets as well as a calculation mode, the syntax is: \opt{usepalette=\{palette,mode\}}, where \argu{palette} designates a table of colors which are themselves tables of the form $\{r,g,b\}$ where $r$, $g$ and $b$ are numbers between $0$ and $1$. The argument  \argu{mode} can be :
        \begin{itemize}
            \item one of the strings: \val{"x"}, \val{"y"}, \val{"z"}. In the first case, for example, facets with the minimum x-coordinate at their centroid have the first color in the palette, facets with the maximum x-coordinate at their centroid have the last color in the palette, and for the others, the color is calculated based on the x-coordinate of the centroid by linear interpolation.
            
            \item a function: \argu{mode}$\colon f \mapsto \mathrm{mode}(f)\in\mathbb R$, where $f$ denotes a facet (a list of 3D points). Facets with the minimum value have the first color in the palette, those with the maximum value have the last color in the palette, and for the others, the color is calculated by linear interpolation.
        \end{itemize}
    \end{itemize}

    \item For multiple facet lists in the same drawing, the method is:
    \cmdln{g:Dmixfacet(S1, options1, S2, options2, \ldots)}
where \argu{S1}, \argu{S2}, \ldots, are facet lists, and \argu{options1}, \argu{options2}, \ldots, are the corresponding options. The options in one facet list also apply to the following ones if they are not changed. These options are identical to the previous method.

This method is useful for drawing multiple solids together, provided there are no intersections between the objects, as these are not handled here.
\end{enumerate}

\begin{demo}[courbeniv]{Example of contour lines on a surface}
\begin{luadraw}{name=courbes_niv}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M, Z = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M, ld.cpx.Z
local cos, sin = math.cos, math.sin
local g = ld.graph3d:new{window3d={0,5,0,10,0,11}, adjust2d=true, size={10,10},
    viewdir={"central",220,60,15,M(2.5,5,5.5)}}
g:Labelsize("footnotesize")
local S = ld.cartesian3d(function(u,v) return (u+v)/(2+cos(u)*sin(v)) end,0,5,0,10,{30,30})
local n = 10 -- number of levels
local Colors = ld.getpalette(ld.palGasFlame,n,true) -- list of 10 colors in table format
local niv, S1 = {}
for k = 1, n do
    S1, S = ld.cutfacet(S,{M(0,0,k),-vecK}) -- section of S with the plane z=k
    ld.insert(niv,{S1, {color=Colors[k],mode=ld.mShaded,edgewidth=0.5}}) -- S1 is the part below the plane and S is above it
end
ld.insert(niv,{S, {color=Colors[n+1]}}) -- inserting the last level
-- niv is a list of the type {facets1, options1, facets2, options2, ...}
g:Dboxaxes3d({grid=true, gridcolor="gray",fillcolor="lightgray"})
g:Dmixfacet(table.unpack(niv))
for k = 1, n do
    g:Dballdots3d( M(5,0,k), ld.rgb(Colors[k]))
end
g:Dlabel("$z=\\frac{x+y}{2+\\cos(x)\\sin(y)}$", Z((g:Xinf()+g:Xsup())/2, g:Yinf()), {pos="N"})
g:Show()
\end{luadraw}
\end{demo}

\subsection{Functions for Constructing Facet Lists}

The following functions return a solid as a list of facets (with 3D points).

\subsubsection{surface()}

Two possible syntaxes:
\begin{enumerate}
    \item \cmd{ld.surface(f, u1, u2, v1, v2 \fac{, grid})} returns the surface parameterized by the function \argu{f}$\colon(u,v) \mapsto f(u,v)\in \mathbf R^3$. The interval for the parameter $u$ is given by \argu{u1} and \argu{u2}. The interval for the parameter $v$ is given by \argu{v1} and \argu{v2}. The optional parameter \argu{grid} defaults to $\{25,25\}$, and defines the number of points to calculate for the parameter $u$ followed by the number of points to calculate for the parameter $v$ (the values ​​of $u$ and $v$ are equally distributed).

    \item \cmd{ld.surface(f, mesh)} with \argu{mesh}$=\{\{u_1,\ldots,u_n\}, \{v_1,\ldots,v_m\}\}$, returns the surface parameterized by the function \argu{f}$\colon(u,v) \mapsto f(u,v)\in \mathbf R^3$. The values ​​of the parameters $u$ and $v$ are given by the argument \argu{mesh}; they must be in strictly ascending order, but they are not necessarily equally distributed.
\end{enumerate}

There are two variants for surfaces:

\subsubsection{cartesian3d()}

The function \cmd{ld.cartesian3d(f, x1, x2, y1, y2 \fac{, grid, addwall})} returns the Cartesian surface with equation $z=f(x,y)$ where \argu{f}$\colon(x,y)\mapsto f(x,y)\in\mathbb R$. The interval for $x$ is given by \argu{x1} and \argu{x2}. The interval for $y$ is given by \argu{y1} and \argu{y2}. The optional parameter \argu{grid} is $\{25,25\}$ by default; it defines the number of points to calculate for $x$ followed by the number of points to calculate for $y$. The parameter \argu{addwall} is \val{0} or \val{"x"}, or \val{"y"}, or \val{"xy"} (\val{0} by default). When this option is set to \val{"x"} (or \val{"xy"}), the function returns, after the list of facets composing the surface, a list of separating facets (walls or partitions) between each "layer" of facets. A layer corresponds to two consecutive values ​​of the parameter $x$\footnote{These partitions are actually planes with the equation $x=$constant}. With the value \val{"y"} (or \val{"xy"}), it is a list of separating facets (walls) between each "layer" corresponding to two consecutive values ​​of the parameter "y"\footnote{These partitions are actually planes with the equation $y=$constant}. This option can be useful with the \cmd{g:Dscene3d()} method (only), because the separating partitions form a partition of space isolating the facets of the surface, which avoids unnecessary intersection calculations between them. This is particularly the case with non-convex surfaces.

For example, here is the code for figure \ref{pointcol}:
\begin{Luacode}
\begin{luadraw}{name=point_col}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M

local g = ld.graph3d:new{window3d={-2,2,-2,2,-4,4}, window={-3.5,3,-5,5}, size={8,9,0}, viewdir={120,60}}
local S = ld.cartesian3d(function(u,v) return u^2-v^2 end, -2,2,-2,2,{20,20}) -- surface of equation z=x^2-y^2
local Tx = g:Intersection3d(S, {Origin,vecI}) --intersection of S with the yOz plane
local Ty = g:Intersection3d(S, {Origin,vecJ}) --intersection of S with the xOz plane
g:Dboxaxes3d({grid=true,gridcolor="gray",fillcolor="LightGray",drawbox=true})
g:Dfacet(S,{mode=ld.mShadedOnly,color="ForestGreen"}) -- surface drawing
g:Dedges(Tx, {color="Crimson", hidden=true, width=8}) -- intersection with yOz
g:Dedges(Ty, {color="Navy",hidden=true, width=8}) -- intersection with xOz
g:Dpolyline3d( {M(2,0,4),M(-2,0,4),M(-2,0,-4)}, "Navy,line width=.8pt")
g:Dpolyline3d( {M(0,-2,4),M(0,2,4),M(0,2,-4)}, "Crimson,line width=.8pt")
g:Show()
\end{luadraw}
\end{Luacode}

\subsubsection{cylindrical\_surface()}

The function \cmd{ld.cylindrical\_surface(r, z, u1, u2, theta1, theta2 \fac{, grid, addwall})} returns the surface parameterized as cylindrical by \emph{r(u,theta), theta, z(u,theta)}. The arguments \argu{r} and \argu{z} are therefore two real-valued functions of $u$ and $\theta$. The interval for $u$ is given by \argu{u1} and \argu{u2}. The interval for $\theta$ is given by \argu{theta1} and \argu{theta2} (in radians). The optional parameter \argu{grid} is $\{25,25\}$ by default; it defines the number of points to calculate for $u$ followed by the number of points to calculate for $\theta$. The parameter \argu{addwall} is $0$ or \val{"v"} or \val{"z"} or \val{"vz"} ($0$ by default). When this option is \val{"v"}  or \val{"vz"}, the function returns, after the list of facets composing the surface, a list of separating facets (walls or partitions) between each "layer" of facets, a layer corresponds to two consecutive values ​​of the angle $\theta$\footnote{These partitions are in fact planes of equation $\theta=$constant}. When this option is set to \val{"z"}  or \val{"vz"}, the function returns, after the list of facets composing the surface, a list of separating facets (walls or partitions) between each "layer" of facets. A layer corresponds to two consecutive values ​​of the dimension $z$\footnote{These partitions are actually planes with the equation $z=$constant}, the values ​​of $z$ are calculated from the values ​​of the parameter $u$ and with the value \argu{theta1}. This is useful when $z$ only depends on $u$ (and therefore not on $\theta$). This option can be useful with the \cmd{g:Dscene3d()} method (only), because the separating partitions form a partition of space isolating the surface facets, which avoids unnecessary intersection calculations between them. This is particularly the case with non-convex surfaces.

\begin{demo}{Surfaces using the \emph{addwall} option}
\begin{luadraw}{name=surface_with_addWall}
local ld = luadraw
local pi, ch, sh = math.pi, math.cosh, math.sinh
local O = ld.pt3d.Origin
local g = ld.graph3d:new{window3d={-4,4,-4,4,-5,5}, window={-10,10,-4,4}, size={10,10}, viewdir={60,60}}
g:Labelsize("footnotesize")
local S,wall = ld.cartesian3d(function(x,y) return x^2-y^2 end,-2,2,-2,2,nil,"xy")
g:Saveattr(); g:Viewport(-10,0,-4,4); g:Coordsystem(-4.5,4.5,-4.5,4.75)
g:Dscene3d(
    g:addWall(wall), -- 2 facet cutouts with this instruction, and 529 facet cutouts without it
    g:addFacet(S,{color="SteelBlue"}),
    g:addAxes(O,{arrows=1}) )
g:Restoreattr()
g:Saveattr(); g:Viewport(0,10,-4,4); g:Coordsystem(-5,5,-5,5)
local r = function(u,v) return ch(u) end
local z = function(u,v) return sh(u) end
S,wall = ld.cylindrical_surface(r,z,-2,2,-pi,pi,{25,51},"zv")
g:Dscene3d(
    g:addWall(wall), -- 13 facet cutouts with this instruction, and more than 17000 facet cutouts without it ...
    g:addFacet(S,{color="Crimson"}),
    g:addAxes(O,{arrows=1}) )
g:Restoreattr()
g:Show()
\end{luadraw}
\end{demo}

\subsubsection{curve2cone()}
The function \cmd{ld.curve2cone(f, t1, t2, S, options)} constructs a cone with vertex \argu{S} (3D point) and the base curve parametrized by \argu{f}$\colon t\mapsto f(t)\in\mathbf R^3$ on the interval defined by \argu{t1} et \argu{t2}. The argument \argu{options} is an table whose fields define the options, which are (with their default value):
\begin{itemize}
    \item \opt{nbdots=15}: minimum number of points on the curve to calculate.
    
    \item \opt{ratio=0}: number representing the homothety ratio (centered at vertex \argu{S}) to construct the other part of the cone, with the value $0$ there is no second part.
     
    \item \opt{nbdiv=0}: positive integer indicating the number of times the interval between two consecutive values ​​of the parameter $t$ can be bisected (dichotomized) when the corresponding points are too far apart.
    
    \item \opt{obj=\false} : with the value \false this function builds a list of facets, with the value \true it builds a table with three fields: \code{\{vertices=\{3D points\}, facets=\{\{index1,\ldots\},\ldots\}, normals=\{3D vectors\}\}}.
\end{itemize}
The function returns a sequence:
\begin{enumerate}
    \item the list of facets or the table in \emph{obj} format, followed by
    \item a 3D polygonal line representing the cone's edges.
\end{enumerate}

\begin{demo}{Elliptical Cone Example}
\begin{luadraw}{name=curve2cone}
local ld = luadraw
local O, M = ld.pt3d.Origin, ld.pt3d.M
local cos, sin, pi = math.cos, math.sin, math.pi
local g = ld.graph3d:new{ window3d={-2,2,-4,4,-3,3}, window={-5.5,5.5,-5.5,5},
    size={10,10}, viewdir="central"}
local f = function(t) return M(2*cos(t),4*sin(t),-3) end -- ellipse in the plane z=-3
local C, bord = ld.curve2cone(f,-pi,pi,O,{nbdiv=2, ratio=-1})
g:Dboxaxes3d({grid=true,gridcolor="gray",fillcolor="LightGray"})
g:Dpolyline3d(bord[1],"red,line width=2.4pt") -- bottom edge
g:Dfacet(C, {mode=ld.mShadedOnly,color="LightBlue"}) -- cone
g:Dpolyline3d(bord[2],"red,line width=0.8pt") -- upper edge
g:Show()
\end{luadraw}
\end{demo}

\subsubsection{curve2cylinder()}

The function \cmd{ld.curve2cylinder(f, t1, t2, V \fac{, options})} constructs a cylinder with axis directed by the vector \argu{V} (3D point) and with a base parameterized by \argu{f}$\colon t\mapsto f(t)\in\mathbf R^3$ on the interval defined by \argu{t1} and \argu{t2}. The second base is the translation of the first with the vector $V$. The argument \argu{options} is an table whose fields define the options, which are (with their default value):
\begin{itemize}
    \item \opt{nbdots=15}: minimum number of points on the curve to calculate.
    
    \item \opt{nbdiv=0}: positive integer indicating the number of times the interval between two consecutive values ​​of the parameter $t$ can be bisected (dichotomized) when the corresponding points are too far apart.
    
    \item \opt{obj=\false} : with the value \false this function builds a list of facets, with the value \true it builds a table with three fields: \code{\{vertices=\{3D points\}, facets=\{\{index1,\ldots\},\ldots\}, normals=\{3D vectors\}\}}.
\end{itemize}
The function returns a sequence:
\begin{enumerate}
    \item the list of facets or the table in \emph{obj} format, followed by
    \item a 3D polygonal line representing the cylinder's edges.
\end{enumerate}

\begin{demo}{Section of a non-circular cylinder}
\begin{luadraw}{name=curve2cylinder}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M
local cos, sin, pi = math.cos, math.sin, math.pi

local g = ld.graph3d:new{ window3d={-5,5,-5,5,-4,4},window={-9,8,-10.5,5.5},
    viewdir={"central",39,64}, size={10,10}}
local f = function(t) return M(4*cos(t)-cos(4*t),4*sin(t)-sin(4*t),-4) end -- curve in the plane z=-3
local V = 8*vecK
local C = ld.curve2cylinder(f,-pi,pi,V,{nbdots=25,nbdiv=2})
local plan = {M(0,0,2), -vecK} -- cutting plane z=2
local C1, C2, section = ld.cutfacet(C,plan)
g:Dboxaxes3d({grid=true,gridcolor="gray",fillcolor="LightGray"})
g:Dfacet(C1, {mode=ld.mShaded,color="LightBlue"}) -- part below the plane
g:Dfacet(g:Plane2facet(plan), {opacity=0.3,color="Chocolate"}) -- draw the plane as a facet
g:Filloptions("fdiag","red"); g:Dpolyline3d(section) -- drawing of the section
g:Dfacet(C2, {mode=3,color="LightBlue"}) -- part of the cylinder above the plane
g:Show()
\end{luadraw}
\end{demo}

\subsubsection{line2tube(); section2tube()}

The function \cmd{ld.line2tube(L, r \fac{, options})} constructs (as a list of facets) a tube centered on \argu{L}, which must be a 3D polygonal line (list of 3D points or list of lists of 3D points). The argument \argu{r} represents the radius of this tube. The argument \argu{options} is an table whose fields define the options, which are (with their default value):
\begin{itemize}
    \item \opt{nbfacet=3}: number of lateral facets of the tube.
    
    \item \opt{close=false}: boolean indicating whether the polygonal line should be closed.
    
    \item \opt{hollow=false}: boolean indicating whether both ends of the tube should be open or not. When the \opt{close} option is set to \true, the \opt{hollow} option is automatically set to \true. 
          
    \item \opt{addwall=0}: number that is $0$ or $1$. When this option is $1$, the function returns, after the  tube, a list of separating facets (walls) between each "section" of the tube, which can be useful with the \cmd{g:Dscene3d()} method (only).
    
    \item \opt{obj=\false} : with the value \false this function returns a list of facets, with the value \true it returns a table with three fields: \code{\{vertices=\{3D points\}, facets=\{\{index1,\ldots\},\ldots\}, normals=\{3D vectors\}\}}.    
\end{itemize}

The function \cmd{ld.section2tube(section, L \fac{, options})} also constructs a tube centered on \argu{L}, which must be a list of 3D points. The argument \argu{section} must be a facet centered on the first point of \argu{L}; it represents a section of the tube to be constructed. The argument \argu{options} is an table whose fields define the options, which are (with their default value):
\begin{itemize}
    \item \opt{nbfacet=3}: number of lateral facets of the tube.
    
    \item \opt{close=false}: boolean indicating whether the polygonal line should be closed.
    
    \item \opt{hollow=false}: boolean indicating whether both ends of the tube should be open or not. When the \opt{close} option is set to \true, the \opt{hollow} option is automatically set to \true.   
        
    \item \opt{addwall=0}: number that is $0$ or $1$. When this option is $1$, the function returns, after the tube, a list of separating facets (walls) between each "section" of the tube, which can be useful with the \cmd{g:Dscene3d()} method (only).
    
    \item \opt{obj=\false} : with the value \false this function returns a list of facets, with the value \true it returns a table with three fields: \code{\{vertices=\{3D points\}, facets=\{\{index1,\ldots\},\ldots\}, normals=\{3D vectors\}\}}. 
\end{itemize}

\begin{demo}{Example with line2tube and section2tube}
\begin{luadraw}{name=line2tube_section2tube}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M

local g = ld.graph3d:new{window={-5,6,-4.5,8}, viewdir={45,60}, margin={0,0,0,0}, size={10,10}}
local L1 = ld.map(pt3d.toPoint3d,ld.polyreg(0,3,6)) -- regular hexagon in the xOy plane, with center O and vertex M(3,0,0)
local L2 = ld.shift3d(ld.rotate3d(L1,90,{Origin,vecJ}),3*vecJ)
local L3 = ld.shift3d(ld.reverse(L1),6*vecK)
L3[6] = L3[5]-2*vecK -- modification of the last point
local section = ld.shift3d({M(2,0,0.5),M(4,0,0.5),M(4,0,-0.5),M(2,0,-0.5)},6*vecK)
local T1 = ld.line2tube(L1,1,{nbfacet=8,close=true}) -- tube 1 closed
local T2 = ld.line2tube(L2,1,{nbfacet=8}) -- tube 2 not closed
local T3 = ld.section2tube(section, L3,{hollow=true})
g:Dmixfacet( T1, {color="Crimson",opacity=0.8}, T2, {color="SteelBlue"}, T3, {color="ForestGreen"} )
g:Show()
\end{luadraw}
\end{demo}

\subsubsection{read\_table3d()}

The function \cmd{ld.read\_table3d(table, options)} interprets the contents of the argument \argu{table}. This function returns a table whose fields contain, depending on the options, facets, vertices, edges, or functions.

\begin{itemize}
    \item The argument \argu{table} represents the values ​​of one (or more) function(s) $f_j\colon (x,y) \mapsto f_j(x,y)\in\mathbb R$ ($1\leqslant j \leqslant n$) on a tile $[x_1;x_2]\times [y_1;y_2]$, with a certain number of values ​​for $x$ in $[x_1;x_2]$, and a certain number of values ​​for $y$ in $[y_1;y_2]$. The \argu{table} is a list of lists of the type:\[\{x_i,y_i,f_1(x_i,y_i),\ldots,f_n(x_i,y_i)\}\]
These lists must be arranged in lexicographical order according to $x$ and $y$ (or according to $y$ and $x$). For example, here is the table representing the function $(x,y)\mapsto x^2+y^2$ on $[-1;1]\times[-1;1]$ with $5$ values ​​per interval (therefore $25$ lists):

\begin{verbatim}
{ { -1.0, -1.0, 2.0 },
{ -1.0, -0.5, 1.25 },
{ -1.0, 0.0, 1.0 },
{ -1.0, 0.5, 1.25 },
{ -1.0, 1.0, 2.0 },
{ -0.5, -1.0, 1.25 },
{ -0.5, -0.5, 0.5 },
{ -0.5, 0.0, 0.25 },
{ -0.5, 0.5, 0.5 },
{ -0.5, 1.0, 1.25 },
{ 0.0, -1.0, 1.0 },
{ 0.0, -0.5, 0.25 },
{ 0.0, 0.0, 0.0 },
{ 0.0, 0.5, 0.25 },
{ 0.0, 1.0, 1.0 },
{ 0.5, -1.0, 1.25 },
{ 0.5, -0.5, 0.5 },
{ 0.5, 0.0, 0.25 },
{ 0.5, 0.5, 0.5 },
{ 0.5, 1.0, 1.25 },
{ 1.0, -1.0, 2.0 },
{ 1.0, -0.5, 1.25 },
{ 1.0, 0.0, 1.0 },
{ 1.0, 0.5, 1.25 },
{ 1.0, 1.0, 2.0 } }
\end{verbatim}

Such tables can also be read from a file using the \cmd{ld.read\_csv\_file()} function, one list per line.

\textbf{Note}: All values ​​in the table must be numeric.

    \item The argument \argu{options} is a table whose fields are the options, which are (with their default values):
    \begin{itemize}
        \item \opt{header=\nil}, a list of strings representing the column names (none by default).

        \item \opt{x=1}, \opt{y=2}, \opt{z=3}: column numbers representing the values ​​of $x$, $y$, and $z$, respectively. When an \opt{header} option has been specified, column names can be used instead of numbers.

        \item \opt{func=\{\}}: list of column numbers (or column names if the \opt{header} option has been specified). Each of the listed columns will be transformed according to $x$ and $y$ (by linear interpolation). The returned table will have a field called \emph{func} containing the list of these functions in order (if there is only one function, then the \emph{func} field will contain that function).

        \item \opt{facets=\true}: With the value \true (default), the returned table will have a field called \emph{facets} containing the list of facets (a facet is a list of 3D points).

        \item \opt{edges=\false}: With the value \true, the returned table will have a field called \emph{edges} containing the outlines of the rectangular facets (3D polygonal lines).

        \item \opt{vertices=\false}: With the value \true, the returned table will have a field called \emph{vertices} containing the 3D points $M(x,y,z)$ of the table.

        \item \opt{triangle=\false}: With the value \true, the facets will be triangular, not rectangular.

        \item \opt{bbox=\false}: With the value \true, the returned table will have a field called \emph{bbox} containing a table of the form $\{x_1,x_2,y_1,y_1,z_1,z_2\}$ representing the bounding box.
    \end{itemize}
\end{itemize}

For an example, we create the file \emph{data3d.csv} with the following code:

\begin{minted}{Lua}
local ld = luadraw
local f = io.open(ld.cachedir.."data3d.csv", "w")
local h = function(x,y) return x^2+y^2 end
local color = function(x,y) return math.cos(math.pi*(x-y)) end
local xvalues, yvalues = ld.linspace(-1,1,21), ld.linspace(-1,1,21)
for _,x in ipairs(xvalues) do
    for _,y in ipairs(yvalues) do
        f:write( x.." "..y.." "..h(x,y).." "..color(x,y).."\n")
    end
end
f:close()
\end{minted}

Reading and using the file:
\begin{demo}{The function \emph{ld.read\_table3d()}}
\begin{luadraw}{name=read_table3d}
local ld = luadraw
local pt3d  = ld.pt3d
local data3d = ld.read_csv_file( ld.cachedir.."data3d.csv", {header=false, sep=" "})
local S = ld.read_table3d(data3d, {bbox=true, func=4})
local color = function(f) -- f is a facet
    local G = pt3d.isobar3d(f)
    return S.func(G.x,G.y)
end
local g = ld.graph3d:new{ window3d=S.bbox, adjust2d=true, size={10,10} }
g:Dboxaxes3d({grid=true, gridcolor="gray", fillcolor="lightgray"})
g:Dfacet( S.facets, {usepalette={ld.palRainbow,color}, edgecolor=ld.Gray, reverse=true})
g:Show()
\end{luadraw}
\end{demo}


\subsubsection{rotcurve()}
The function \cmd{ld.rotcurve(p, t1, t2, axe, angle1, angle2 \fac{, options})} constructs, as a list of facets, the surface swept by the curve parameterized by \argu{p}$\colon t\mapsto p(t)\in \mathbf R^3$ over the interval defined by \argu{t1} and \argu{t2}, by rotating it around \argu{axe} (which is a table of the form \{3D point, 3D vector\} representing an oriented line in space), by an angle ranging from \argu{angle1} (in degrees) to \argu{angle2}. The argument \argu{options} is an table whose fields define the options, which are (with their default value):
\begin{itemize}
    \item \opt{grid=\{25,25\}}: table consisting of two numbers, the first being the number of points calculated for the t parameter, and the second being the number of points calculated for the angular parameter.

    \item \opt{addwall=0}: number equal to $0$, $1$, or $2$. When this option is set to $1$, the function returns, after the surface, a list of separating facets (walls) between each "layer" of facets (a layer corresponds to two consecutive values ​​of the $t$ parameter), and with a value of $2$, it is a list of separating facets (walls) between each rotation "slice" (a layer corresponds to two consecutive values ​​of the angular parameter; this is useful when the curve is in the same plane as the rotation axis). This option can be useful with the \cmd{g:Dscene3d()} method (only).
    
       \item \opt{obj=\false} : with the value \false this function returns a list of facets, with the value \true it returns a table with three fields: \code{\{vertices=\{3D points\}, facets=\{\{index1,\ldots\},\ldots\}, normals=\{3D vectors\}\}}. 
\end{itemize}

\begin{demo}{Example with rotcurve}
\begin{luadraw}{name=rotcurve}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M
local cos, sin, pi = math.cos, math.sin, math.pi
local g = ld.graph3d:new{viewdir={30,60},size={10,10}}
local p = function(t) return M(0,sin(t)+2,t) end -- curve in the plane yOz
local axe = {Origin,vecK}
local S = ld.rotcurve(p,pi,-pi,axe,0,360,{grid={25,35}})
local visible, hidden = g:Classifyfacet(S)
g:Dfacet(hidden, {mode=ld.mShadedOnly,color="cyan"})
g:Dline3d(axe,"red,line width=1.2pt")
g:Dfacet(visible, {mode=5,color="cyan"})
g:Dline3d(axe,"red,line width=1.2pt,dashed")
g:Dparametric3d(p,{t={-pi,pi},draw_options="red,line width=1.2pt"})
g:Show()
\end{luadraw}
\end{demo}

\paragraph{Note}: If the surface orientation does not seem correct, simply swap the parameters \argu{t1} and \argu{t2}, or \argu{angle1} and \argu{angle2}.

\subsubsection{rotline()}

The function \cmd{ld.rotline(L, axe, angle1, angle2 \fac{, options})} constructs, as a list of facets, the surface swept by the list of 3D points \argu{L} by rotating it around \argu{axe} (which is a table of the form \{3D point, 3D vector\} representing an oriented line in space), through an angle ranging from \argu{angle1} (in degrees) to \argu{angle2}. The argument \argu{options} is an table whose fields define the options, which are (with their default value):
\begin{itemize}
    \item \opt{nbdots=25}: number of points calculated for the angular parameter.

    \item \opt{close=false}: boolean indicating whether \argu{L} should be closed.

    \item \opt{addwall=0}: number equal to $0$, $1$, or $2$. When this option is set to $1$, the function returns, after the surface, a list of separating facets (walls) between each "layer" of facets (a layer corresponds to two consecutive values ​​of the $t$ parameter), and with a value of $2$, it is a list of separating facets (walls) between each rotation "slice" (a layer corresponds to two consecutive values ​​of the angular parameter; this is useful when the curve is in the same plane as the rotation axis). This option can be useful with the \cmd{g:Dscene3d()} method (only).
    
   \item \opt{obj=\false} : with the value \false this function returns a list of facets, with the value \true it returns a table with three fields: \code{\{vertices=\{3D points\}, facets=\{\{index1,\ldots\},\ldots\}, normals=\{3D vectors\}\}}.     
\end{itemize}

\begin{demo}{Example with rotline}
\begin{luadraw}{name=rotline}
local ld = luadraw
local pt3d = ld.pt3d
local M = pt3d.M
local g = ld.graph3d:new{window={-4,4,-4,4},size={10,10}}
local L = {M(0,0,4),M(0,4,0),M(0,0,-4)} -- list of points in the yz plane
local axe = {pt3d.Origin, pt3d.vecK}
local S = ld.rotline(L,axe,0,360,{nbdots=5}) -- point 1 and point 5 are confused
g:Dfacet(S,{color="Crimson",edgecolor="Gold",opacity=0.8})
g:Show()
\end{luadraw}
\end{demo}      


\subsection{Edges of a solid}

An "edge" object is a table with two fields: one field named \emph{visible} that contains a 3D polygonal line corresponding to the visible edges, and another field named \emph{hidden} that contains a 3D polygonal line corresponding to the hidden edges.

\begin{itemize}
    \item The method \cmd{g:Edges(P)}, where \argu{P} is a polyhedron, returns the edges of \argu{P} as an "edge" object. An edge of \argu{P} is visible when it belongs to at least one visible face.

     \item The method \cmd{g:Intersection3d(P, plane)}, where \argu{P} is a polyhedron or a list of facets, returns as an "edge" object the intersection between \argu{P} and the plane represented by \argu{plane} (it is a table of the form \{A,u\} where $A$ is a point on the plane and $u$ is a normal vector, so they are two 3D points).

    \item The method \cmd{g:Dedges(edges, options)} allows you to draw \argu{edges}, which must be an "edge" object. The argument \argu{options} is an table whose fields define the options, which are (with their default value):
\begin{itemize}
    \item \opt{hidden=false}: Boolean indicating whether hidden edges should be drawn.
    \item \opt{visible=true}: Boolean indicating whether visible edges should be drawn.
    \item \opt{clip=false}: Boolean indicating whether edges should be clipped by the 3D window.
    \item \opt{hiddenstyle=ld.Hiddenlinestyle}: String defining the line style of hidden edges. By default, this option contains the value of the global variable \varglob{ld.Hiddenlinestyle} (which defaults to \val{"dotted"}).
    \item \opt{hiddencolor=color}: String defining the color of hidden edges. By default, this option contains the same color as the \opt{color} option.
    \item \opt{style=<current line style>}: String defining the line style of visible edges.
     \item \opt{color=<current line color>}: String defining the color of the visible edges.
    \item \opt{width=<current width>}: Number representing the line thickness of the edges (in tenths of a point).
\end{itemize}

    \item \textbf{Complement}:
\begin{itemize}
    \item The function \cmd{ld.facetedges(F)}, where \argu{F} is a list of facets or a polyhedron, returns a list of 3D segments representing all the edges of \argu{F}. The result is not an "edge" object, and is drawn with the \cmd{g:Dpolyline3d()} method. Of course, each edge only appears once in the list.
     \item The function \cmd{ld.facetvertices(F)}, where \argu{F} is a list of facets or a polyhedron, returns the list of all vertices of \argu{F} (3D points).
\end{itemize}
\end{itemize}

\subsection{Methods and functions applying to facets or polyhedra}

\begin{itemize}
    \item The method \cmd{g:Isvisible(F)}, where \argu{F} denotes \textbf{a} facet (list of at least 3 coplanar and non-aligned 3D points), returns true if facet \argu{F} is visible (normal vector directed towards the observer). If $A$, $B$, and $C$ are the first three points of \argu{F}, the normal vector is calculated by performing the vector product $\vec{AB}\wedge\vec{AC}$.

    \item The method \cmd{g:Classifyfacet(F)}, where \argu{F} is a list of facets or a polyhedron, returns \textbf{two} lists of facets: the first is the list of visible facets, and the second, the list of invisible facets.

    \item The method \cmd{g:Sortfacet(F \fac{, backcull})}, where \argu{F} is a list of facets, returns this list of facets sorted from furthest to closest to the observer. The optional argument \argu{backcull} is a boolean that defaults to \false; when it is \true, non-visible facets are excluded from the result (only visible facets are then returned after being sorted). The calculation of a facet's distance is based on its center of gravity. The so-called "painter" technique consists of displaying the facets from furthest to closest, therefore in the order of the list returned by this function (the displayed result, however, is not always correct depending on the size and shape of the facets).

    \item The method \cmd{g:Sortpolyfacet(P \fac{, backcull})}, where \argu{P} is a polyhedron, returns the list of facets of \argu{P} (facets with 3D points) sorted from furthest to closest to the observer. The optional argument \argu{backcull} is a boolean that defaults to \false; when it is \true, invisible facets are excluded from the result, as in the previous method. These two sorting methods are used by the methods for drawing polyhedrons or facets (\cmd{g:Dpoly()}, \cmd{g:Dfacet()} and \cmd{g:Dmixfacet()}).

    \item The method \cmd{g:Outline(P)}, where \argu{P} is a polyhedron, returns the "outline" of \argu{P} as a two-field table. One field, named \emph{visible}, contains a 3D polygonal line representing the "edges" (segments) belonging to a single facet, which is visible, or to two facets, one visible and one hidden; the other field, named \emph{hidden}, contains a 3D polygonal line representing the "edges" belonging to a single facet, which is hidden.

    \item The function \cmd{ld.border(P)}, where \argu{P} is a polyhedron or a list of facets, returns a 3D polygonal line corresponding to the edges belonging to a single facet of \argu{P} (these edges are placed "end to end" to form a polygonal line).

    \item The function \cmd{ld.getfacet(P, list)}, where \argu{P} is a polyhedron, returns the list of facets of \argu{P} (with 3D points) whose number appears in the table \argu{list}. If the argument \argu{list} is not specified, the list of all facets of \argu{P} is returned (in this case, it is the same as \cmd{ld.poly2facet(P)}).

    \item The function \cmd{ld.facet2plane(L)}, where \argu{L} is either a facet or a list of facets, returns either the plane containing the facet or the list of planes containing each of the facets of \argu{L}. A plane is a table of the type \{A,u\} where $A$ is a point on the plane and $u$ is a normal vector to the plane (i.e., two 3D points).

    \item The function \cmd{ld.reverse\_face\_orientation(F)} where \argu{F} is either a facet, a list of facets, or a polyhedron, returns a result of the same nature as \argu{F} but in which the order of the vertices of each facet has been reversed. This can be useful when the orientation of space has been changed.

\begin{demo}{Sphere inscribed in an octahedron with the center projected onto the faces}
\begin{luadraw}{name=sphere_octaedre}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M

local poly = require "luadraw_polyhedrons"
local g = ld.graph3d:new{ window3d={-3,3,-3,3,-3,3}, size={10,10}}
local P = poly.octahedron(Origin,M(0,0,3)) -- polyhedron defined in the luadraw_polyhedrons module
P = ld.rotate3d(P,-10,{Origin,vecK}) -- rotate3d on a polyhedron returns a polyhedron
local V, H = g:Classifyfacet(P) -- V for visible facets, H for hidden
local S = ld.map(function(p) return {ld.proj3d(Origin,p),p[2]} end, ld.facet2plane(V) )
-- S contains the list of: {projected, normal vector} (projected from Origin onto the visible faces)
local R = pt3d.abs(S[1][1]) -- sphere radius
g:Dboxaxes3d({grid=true, gridcolor="gray", fillcolor="LightGray"})
g:Dfacet(H, {color="blue",opacity=0.9}) -- drawing of non-visible facets
g:Dsphere(Origin,R,{mode=ld.mBorder,color="orange"}) -- drawing of the sphere
g:Dballdots3d(Origin,"gray",0.75) -- center of the sphere
for _,D in ipairs(S) do -- segments connecting the origin to the projected
    g:Dpolyline3d( {Origin,D[1]},"dashed,gray")
end
g:Dfacet(V,{opacity=0.4, color="LightBlue"}) -- visible facets of the octahedron
g:Dcrossdots3d(S,nil,0.75) -- drawing of the projections on the faces
g:Dpolyline3d( {M(0,-3,3), M(0,0,3), M(-3,0,3)},"gray")
g:Show()
\end{luadraw}
\end{demo}
\end{itemize}

\subsection{Cutting a solid: cutpoly and cutfacet}

\begin{itemize}
    \item The function \cmd{ld.cutpoly(P, plane \fac{, close})} cuts the polyhedron \argu{P} with the plane \argu{plane} (a ​​table of type \{A,n\} where $A$ is a point on the plane and $n$ is a vector normal to the plane). The function returns three things: the part located in the half-space containing the vector $n$ (in the form of a polyhedron), followed by the part located in the other half-space (still in the form of a polyhedron), followed by the section in the form of a facet oriented by $-n$. When the optional argument \argu{close} is \true, the section is added to both resulting polyhedra, which closes them (\false by default).

\textbf{Note}: When the polyhedron \argu{P} is not convex, the section result is not always correct.

\begin{demo}{Cube cut by a plane (cutpoly), with \emph{close}=false and with \emph{close}=true}
\begin{luadraw}{name=cutpoly}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M

local g = ld.graph3d:new{window3d={-3,3,-3,3,-3,3}, window={-4,4,-3,3},size={10,10}}
local P = ld.parallelep(M(-1,-1,-1),2*vecI,2*vecJ,2*vecK)
local A, B, C = M(0,-1,1), M(0,1,1), M(1,-1,0)
local plane = {A, pt3d.prod(B-A,C-A)}
local P1 = ld.cutpoly(P,plane)
local P2 = ld.cutpoly(P,plane,true)
g:Lineoptions(nil,"Gold",8)
g:Dpoly( ld.shift3d(P1,-2*vecJ), {color="Crimson",mode=ld.mShadedHidden} )
g:Dpoly( ld.shift3d(P2,2*vecJ), {color="Crimson",mode=ld.mShadedHidden} )
g:Dlabel3d(
    "close=false", M(2,-2,-1), {dir={vecJ,vecK}},
    "close=true", M(2,2,-1), {} )
g:Show()
\end{luadraw}
\end{demo}

    \item The function \cmd{ld.cutfacet(F, plane \fac{, close})}, where \argu{F} is a facet, a list of facets, or a polyhedron, does the same thing as the previous function except that this function returns lists of facets and not polyhedra. This function was used in the contour line example in Figure \ref{courbeniv}.
\end{itemize}

\subsection{Clipping Facets with a Convex Polyhedron: clip3d}

The function \cmd{ld.clip3d(S, P \fac{, exterior})} clips the solid \argu{S} (which is a list of facets or a polyhedron) with the convex solid \argu{P} (which is a list of facets or a polyhedron) and returns the resulting list of facets. The optional argument \argu{exterior} is a boolean that defaults to \false. In this case, the part of \argu{S} that is interior to \argu{P} is returned; otherwise, the part of \argu{S} that is exterior to \argu{P} is returned.

\textbf{Note}: The result is not always satisfactory for the exterior part.

\paragraph{Special case}: Clipping a list of facets $S$ (or polyhedron) with the current 3D window can be done with this function as follows:

\begin{center}
\textbf{S = ld.clip3d(S, g:Box3d())}
\end{center}

Indeed, the \cmd{g:Box3d()} method returns the current 3D window as a parallelepiped.

\begin{demo}[clip3d]{Example with clip3d: constructing a die from a cube and a sphere}
\begin{luadraw}{name=clip3d}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M

local g = ld.graph3d:new{window={-3,3,-3,3},size={10,10}, viewdir="central"}
local S = ld.sphere(Origin,3)
local C = ld.parallelep(M(-2,-2,-2),4*vecI,4*vecJ,4*vecK)
local C1 = ld.clip3d(S,C) -- sphere clipped by the cube
local C2 = ld.clip3d(C,S) -- cube clipped by the sphere
local V = g:Classifyfacet(C2) -- visible facets of C2
g:Dfacet( ld.concat(C1,C2), {color="Beige",mode=ld.mShadedOnly,backcull=true} ) -- only visible faces
g:Dpolyline3d(V,true,"line width=0.8pt") -- outline of the visible faces of C2
local A, B, C, D = M(2,-2,-2), M(2,2,2), M(-2,2,-2), M(0,0,2) -- drawing black dots
g:Filloptions("full","black")
g:Dcircle3d( D,0.25,vecK); g:Dcircle3d( (2*A+B)/3,0.25,vecI)
g:Dcircle3d( (A+2*B)/3,0.25,vecI); g:Dcircle3d( (3*B+C)/4,0.25,vecJ)
g:Dcircle3d( (B+C)/2,0.25,vecJ); g:Dcircle3d( (B+3*C)/4,0.25,vecJ)
g:Show()
\end{luadraw}
\end{demo}

\subsection{Clip a plane with a convex polyhedron: clipplane}

The function \cmd{ld.clipplane(plane, P)}, where the argument \argu{plane} is a table of the form \emph{\{A,n\}} representing the plane passing through $A$ (3D point) and normal vector $n$ (non-zero 3D point), and \argu{P} is a convex polyhedron, returns the section of the polyhedron through the plane, if it exists, in the form of a facet (list of 3D points) oriented by $n$.
