\subsection{Le module \emph{luadraw\_cvx\_polyhedra\_nets}}

Ce module ajoute une méthode graphique à la classe \emph{ld.graph3d}, ainsi que plusieurs fonctions dans l'espace de noms \emph{luadraw}, mais il ne renvoie rien.

\subsubsection{La fonction de base}

Le module \emph{luadraw\_cvx\_polyhedra\_nets} permet de \og déplier\fg\ un polyèdre \textbf{convexe} afin d'en obtenir un patron. La fonction réalisant le dépliage est :

\cmdln{ld.unfold\_polyhedron(P, options)}

L'argument \argu{P} doit être un polyèdre convexe. \writeoptions :
\begin{itemize}
    \item \opt{opening=1}, valeur comprise entre $0$ et $1$ représentant le "taux" d'ouverture. Avec la valeur $1$ le polyèdre est totalement déplié, les facettes renvoyées par la fonction seront donc toutes dans un même plan. Avec la valeur $0$, la fonction renvoie les facettes du polyèdre sans modification.
    
    \item \opt{root=1}, numéro de la facette du polyèdre qui servira de racine, car la fonction met le polyèdre sous la forme d'un arbre en déterminant pour chaque facette quelles sont les voisines (facettes adjacentes) ainsi que les arêtes communes et les angles.
    Cette option permet de choisir la facette qui servira de point de départ.
    
    \item \opt{model=nil}, liste de listes de numéros de facettes pour imposer un modèle de patron, par exemple :\par \hfil\opt{model=\{ \{1,6\},\{1,3\},\{1,4\},\{1,5,2\}\}},\hfil\par la sous-liste  \emph{\{1,5,2\}} signifie que la facette $1$ est l'ancêtre de la facette $5$, et que la facette $5$ est l'ancêtre de la facette $2$, c'est à dire que les facettes $5$ et $1$  sont adjacentes, et la facette $5$ tournera autour de son arête commune avec la facette $1$ (même chose $5$ et $2$). Pour que le modèle soit cohérent, toutes les facettes du polyèdre SAUF une (qui sera la facette \opt{root}), doivent avoir un et un seul ancêtre; si dans le polyèdre les facettes $1$ et $5$ ne sont pas adjacentes, la fonction s'arrête et affiche une erreur dans le terminal. Lorsque l'option \opt{model} vaut \nil (valeur par défaut), l'algorithme calcule lui-même un modèle cohérent.
    
    \item \opt{to2d=false}, booléen qui permet d'obtenir une version 2D du patron dans le repère du plan de l'écran. Avec la valeur \true, l'option \opt{opening} prend automatiquement la valeur $1$ et les facettes renvoyées par la fonction auront des sommets exprimés en nombres complexes.
    
    \item \opt{tabs=false}, booléen qui permet d'ajouter ou non des languettes au patron dans la version 2D.  Avec la valeur \true, l'option \opt{to2d} prend automatiquement la valeur \true également, les facettes renvoyées par la fonction auront des sommets exprimés en nombres complexes dans le repère de l'écran, et la fonction renvoie en plus une ligne polygonale 2D représentant des languettes pour certaines arêtes (celles-ci sont déterminées automatiquement).
    
    \item \opt{tabs\_wd=0.2}, valeur numérique représentant l'épaisseur des languettes lorsque l'option \opt{tabs} a la valeur \true.
    
    \item \opt{tabs\_lg=0.5}, valeur numérique entre $0$ et $1$, permettant de déterminer la longueur du petit côté des languettes  celle-ci est égale à la longueur de l'arête (qui est le grand côté) multipliée par \opt{tabs\_lg} (lorsque l'option \opt{tabs} a la valeur \true).
    
    \item \opt{rotate=0}, lorsque l'option \opt{to2d} a la valeur \true, le dessin est tourné d'un angle égal à \opt{rotate} (en degrés) autour de son centre. Dans la version 3D, le dessin est tourné d'un angle égal à \opt{rotate} (en degrés) autour de l'axe passant par le centre de gravité de la facette \opt{root} et orienté par un vecteur normal à cette facette dirigé vers l'extérieur du polyèdre.
\end{itemize}

La fonction renvoie en résultat une table constituée des champs suivants :

    \begin{itemize}
        \item Le champ \emph{facets} : qui contient la liste des facettes, avec les sommets en 2D (nombres complexes) si l'option \opt{to2d} vaut \true, ou sommets en 3D (points 3D) dans le cas contraire.
        
        \item Le champ \emph{tree} : qui est une liste de la forme : \par
        \hfil\emph{\{ \{ancestor, n1, n2, angle, vertices\}, \ldots\} }\hfil\par
    chaque élément de cette liste représente une facette avec pour chacune d'elles les informations suivantes :
        \begin{itemize}
            \item \emph{ancestor} : numéro de la facette ancêtre, c'est son rang dans la liste \emph{tree} (la facette qui a servi de racine a pour ancêtre le numéro $0$ qui ne correspond à aucune facette).
            
            \item \emph{n1, n2} : numéro des sommets la facette ancêtre représentant l'arête commune.
            
            \item \emph{angle} : angle en degré avec la facette ancêtre.
            
            \item \emph{vertices} : liste des sommets (points 3D) de la facette.
        \end{itemize}
        
        \item Le champ \emph{bounds} : qui contient sous la forme d'une liste, la bounding box des facettes (soit en 2D soit en 3D)
        \item Lorsque l'option \opt{tabs} a la valeur \true, alors il y a deux champs supplémentaires dans le résultat :
            \begin{itemize}
                \item Le champ \emph{tabs} : qui contient une ligne polygonale 2D (liste de listes de nombres complexes) représentant les languettes, ceci uniquement lorsque l'option \opt{tabs} a la valeur \true.
                
                \item Le champ \emph{twins} : qui contient une liste de la forme \emph{\{ \{\{a1,b1\}, \{a2,b2\}\}, \ldots\}} représentant la liste des paires d'arêtes jumelles (les arêtes jumelles sont confondues lorsque le polyèdre est refermé), \emph{a1}, \emph{b1}, \emph{a2}, \emph{b2}, sont des nombres complexes représentant les extrémités des arêtes dans le patron du polyèdre version 2D. Cette liste est calculée uniquement lorsque l'option \opt{tabs} a la valeur \true.
            \end{itemize}
    \end{itemize}
    
\subsubsection{La méthode de dessin}

Celle-ci est la méthode \cmd{g:Dpolyhedron\_net(P, options)} où \argu{P} désigne un polyèdre convexe. Les options sont celles de la fonction précédentes, à celles-ci s'ajoutent :

    \begin{itemize}
        \item Dans le cas d'un patron 2D (lorsque l'option \opt{to2d}, ou l'option \opt{tabs}, a la valeur \true):
            \begin{itemize}
                \item \opt{facet\_name=false}, avec la valeur \true le numéro des facettes (précédé de la lettre 'F') sera affiché au centre de chaque facette.
                
                \item \opt{edge\_name=false},  avec la valeur \true le numéro des arêtes (précédé de la lettre 'e') sera affiché au centre de chaque arête, ce qui permet de repérer les arêtes jumelles et donc les facettes voisines.
                
                \item \opt{tabs\_options=""}, chaîne représentant des options de dessin TikZ pour les languettes si l'option \opt{tabs} a la valeur \true.
                
                \item \opt{facet\_options=""}, chaîne représentant des options TikZ de dessin pour la méthode \cmd{g:Dpolyline()} qui dessinera les facettes.                
            \end{itemize}
        \item Dans le cas d'un patron 3D il y a uniquement en plus :
            \begin{itemize}
                \item \opt{facet\_options=\{\}}, liste d'options de dessin pour la méthode \cmd{g:Dfacet()} qui dessinera les facettes. 
            \end{itemize}
    \end{itemize}

Le dessin est accompagné d'un affichage dans le terminal de la bounding box 2D de celui-ci.


\subsubsection{Exemples}

Dans cet exemple, on affiche le patron par défaut, version 2D,  d'un parallélépipède $P$ avec les languettes hachurées, le numéro des facettes (ce numéro est le rang dans la liste \emph{P.facets}) ainsi que le numéro des arêtes afin de voir celles qui doivent être collées ensemble:

\begin{demo}{Patron d'un parallélépipède}
\begin{luadraw}{name=parallelep_net}
local ld = luadraw
local cpx, pt3d = ld.cpx, ld.pt3d
local Origin, vecI, vecJ, vecK = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK

local g = ld.graph3d:new{viewdir={30,60},window={-8.5,8,-5,5},bbox=false, size={10,10}}
require 'luadraw_cvx_polyhedra_nets'
P = ld.parallelep(Origin, 4*vecI,5*vecJ,3*vecK)
g:Dpolyhedron_net(P, {tabs=true, tabs_options="pattern=north west lines, pattern color=gray",
  facet_options="fill=Orange!30", facet_name=true, edge_name=true})
g:Show()
\end{luadraw}
\end{demo}

Le patron par défaut ici correspondrait à l'option \opt{model=\{\{1,3\},\{1,4\},\{1,5\},\{1,6\},\{3,2\}\}}\footnote{L'algorithme prend la première facette, puis cherche ses voisines, puis les voisines de la première voisine, etc.}, mais on peut vouloir imposer un autre modèle, par exemple, avec le même parallélépipède:

\begin{demo}{Patron imposé d'un parallélépipède}
\begin{luadraw}{name=parallelep_net2}
local ld = luadraw
local cpx, pt3d = ld.cpx, ld.pt3d
local Origin, vecI, vecJ, vecK = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK

local g = ld.graph3d:new{viewdir={30,60},window={-9,9,-5,5},bbox=false,size={10,10}}
require 'luadraw_cvx_polyhedra_nets'
P = ld.parallelep(Origin, 4*vecI,5*vecJ,3*vecK)
g:Dpolyhedron_net(P, {model={{4,6,1,3,2,5}},tabs=true, 
  tabs_options="pattern=north west lines, pattern color=gray", 
  facet_options="fill=Orange!30", facet_name=true, 
 edge_name=true, rotate=-90})
g:Show()
\end{luadraw}
\end{demo}

Voici un exemple avec un parallélépipède tronqué que l'on déplie à moitié:

\begin{demo}{Parallélépipède tronqué à demi déplié}
\begin{luadraw}{name=parallelep_net3}
local ld = luadraw
local cpx, pt3d = ld.cpx, ld.pt3d
local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M

local g = ld.graph3d:new{window={-9,15,-9,9,0.6,0.6},bg="lightgray", viewdir={30,60}, margin={0,0,0,0}} 
require 'luadraw_cvx_polyhedra_nets'
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) -- P est tronqué avec le plan
g:Shift3d(M(0,-4,5))
g:Dpolynames(P,"facet") -- cette méthode montre les numéros des facettes de P
-- demi-ouverture de P
g:Shift3d(M(0,0,-11))
g:Dpolyhedron_net(P,{opening=0.5, facet_options={color="Crimson", opacity=0.7, edgecolor="Gold", edgewidth=8}})
-- patron 2D
g:Shift(10)
g:Dpolyhedron_net(P,{tabs=true, tabs_options="pattern=north west lines, pattern color=gray", 
 facet_name=true, rotate=90})
g:Show()
\end{luadraw}
\end{demo}

\paragraph{NB :} Les fonctions \cmd{ld.unfold\_polyhedron()} et \cmd{g:Dpolyhedron\_net()} s'appliquent à tout polyèdre convexe, mais elles ne donneront pas le résultat escompté avec un polyèdre non convexe.

\subsubsection{La fonction \emph{unfold\_tree()}}

Il peut être utile de récupérer l'arbre fabriqué par la fonction \cmd{ld.unfold\_polyhedron()} afin d'éviter de recalculer celui-ci plusieurs fois, lors d'une animation par exemple. La fonction \cmd{ld.unfold\_tree(tree, opening \fac{, num})} permet aussi de déplier le polyèdre. L'argument\argu{tree} est l'arbre fourni par la fonction \cmd{ld.unfold\_polyhedron()}, l'argument optionnel \argu{opening} est un nombre entre $0$ et $1$ qui représente le taux d'ouverture ($1$ par défaut), l'argument optionnel \argu{num} est le numéro de la facette que l'on souhaite ouvrir (et toute la descendance de la facette tournera de la même façon), lorsque cet argument est omis, toutes les facettes tournent.

\paragraph{Exemple d'animation :}

\begin{Luacode}
\begin{luacode*}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK

ld.nbimages = 70
-- images creation
local g = ld.graph3d:new{ viewdir={"central",30,60}, bg="gray", size={10,10}, margin={0,0,0,0} }
-- declarations
local poly = require 'luadraw_polyhedrons'
require 'luadraw_cvx_polyhedra_nets'
local p = ld.linspace(0,1,36)
local T = ld.linspace(0,360, ld.nbimages+1)
local P = poly.dodecahedron(Origin, -2*vecI)
local net = ld.unfold_polyhedron(P)
local tree = net.tree
-- create the image number k, this function must be global
function ld.makeframe(k) -- do not modify this line
    local r = k
    if k > 36 then r = 72-k end
    local P1 = ld.rotate3d( ld.unfold_tree(tree,p[r]), T[k],{Origin,vecK})
    g:Dfacet(P1, {color="Crimson", edgecolor="Gold", edgewidth=8})
    -- send image number k
    g:Sendtotex()  -- do not modify
    g:Cleargraph() -- do not modify
end
\end{luacode*}
\end{Luacode}

Le code \TeX\ (avec le paquet \emph{animate}) :

\begin{TeXcode}
\newcommand*\nb{\directlua{tex.print(luadraw.nbimages)}}
\newcommand*\makeframe[1]{\directlua{luadraw.makeframe(#1)}}%

\begin{animateinline}[poster=first,controls,loop]{8}
\multiframe{\nb}{ik=1+1}{%
\makeframe{\ik}%
}%
\end{animateinline}
\end{TeXcode}

\begin{luacode*}
local ld = luadraw
local pt3d = ld.pt3d
local Origin, vecI, vecJ, vecK = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK

ld.nbimages = 70
-- images creation
local g = ld.graph3d:new{ viewdir={"central",30,60}, bg="gray", size={10,10}, margin={0,0,0,0} }
-- declarations
local poly = require 'luadraw_polyhedrons'
require 'luadraw_cvx_polyhedra_nets'
local p = ld.linspace(0,1,36)
local T = ld.linspace(0,360, ld.nbimages+1)
local P = poly.dodecahedron(Origin, -2*vecI)
local net = ld.unfold_polyhedron(P)
local tree = net.tree
-- create the image number k, this function must be global
function ld.makeframe(k) -- do not modify this line
    local r = k
    if k > 36 then r = 72-k end
    local P1 = ld.rotate3d( ld.unfold_tree(tree,p[r]), T[k],{Origin,vecK})
    g:Dfacet(P1, {color="Crimson", edgecolor="Gold", edgewidth=8})
    -- send image number k
    g:Sendtotex()  -- do not modify
    g:Cleargraph() -- do not modify
end
\end{luacode*}

\newcommand*\nb{\directlua{tex.print(luadraw.nbimages)}}
\newcommand*\makeframe[1]{\directlua{luadraw.makeframe(#1)}}%


Le résultat :

\begin{minipage}{0.9\textwidth}
\begin{center}
\captionof{figure}{Dépliage d'un dodécaèdre}
\begin{animateinline}[poster=first,controls,loop]{8} %palindrome
\multiframe{\nb}{ik=1+1}{%
\makeframe{\ik}%
}%
\end{animateinline}
\end{center}
\end{minipage}
