Retour

version-0_15.tex

Télécharger le fichier Fichier PDF
\input $FORMAT/myplain.tex
%\magnification 1200
\def \listingpath {%
   /var/www/syracuse/texpng/jpv/guide_jps/}
\input \listingpath macros-doc.tex
 
\titre {jps2ps~: version 0.15}
 
\centerline {{\sl par Jean-Paul Vignault}}
 
\centerline {{\sl Groupe des Utilisateurs de
Linux Poitevins (GULP)}}
 
\centerline {(|jpv@melusine.eu.org|)}
 
\centerline {\today }
\vskip \titreskip
 
 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%        debut du manuel
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input \listingpath macros_compl-doc.tex
\pageno -1
\egroup
\pageno 1
 
Ce document présente les différences entre la version $0.15$ et la précédente.
 
\paragraphe {Divers}
 
\syntaxe
 
\longref
   {$\{f\}$}
   {currentpathtransform}
   {$-$}
   {applique la transformation $f$ au chemin courant, où $f~: \rset
   ^2\rightarrow \rset ^2$}
 
\longref
   {$array_1$}
   {uniqp}
   {$array_2$}
   {$array_2$ est le tableau obtenu à partir du tableau de points
   $array_1$ en suprimant les points identiques consécutifs}
 
\longref
   {$array_1$ $array_2$}
   {append}
   {$array_3$}
   {$array_3$ est le tableau obtenu en concaténant les tableaux
   $array_1$ et $array_2$} 
 
\longref
   {$string_1$ $string_2$}
   {append}
   {$string_3$}
   {$string_3$ est la chaîne de caractères obtenue en concaténant les
   chapines de caractères $string_1$ et $string_2$} 
 
\endsyntaxe
 
\paragraphe {Chemins continus paramétrés}
 
Le type $chemin$ de la version $0.14$ est repris plus modestement en
type $chemin\ continu$, en attendant une version future plus complète.
\bgroup
\let \ssparagraphe \sparagraphe
\sinput \datapath diff_parampath.tex
\egroup
\paragraphe {Commandes 3d}
 
On rappelle que la position de la caméra est donnée par le point
$CamPos$, et que son orientation est définie par les 2~vecteurs
$CamVec$ et $CamUp$ qui déterminent la ligne de visée et le plan de
visée. 
 
Pour faciliter le positionnement de la caméra, on dispose maintenant
de la commande |SetCamView| qui oriente la caméra vers un point fixé
et qui recalcule le vecteur $CamUp$ pour avoir une base orthonormale
du plan de visée.
\exempledble {camview_01} {.4}{.5} 
\exempledble {camview_02} {.4}{.5} 
\exempledble {camview_03} {.4}{.5} 
 
De plus quelques nouvelles commandes sont venues enrichir la librairie 3d~:
 
\syntaxe
 
\longref
   {$M$}
   {SetCamView}
   {$-$}
   {Oriente la visée de la caméra vers le point $M (x, y , z)$ et
   recalcule tous les vecteurs nécessaires}
 
\longref
   {$xmin$ $xmax$}
   {setxrange3d}
   {$-$}
   {affecte les variables $xmin3d$ et $xmax3d$ définissant
   l'intervalle de travail sur l'axe $Ox$ en $3d$}
 
\longref
   {$ymin$ $ymax$}
   {setyrange3d}
   {$-$}
   {affecte les variables $ymin3d$ et $ymax3d$ définissant
   l'intervalle de travail sur l'axe $Oy$ en $3d$}
 
\longref
   {$zmin$ $zmax$}
   {setzrange3d}
   {$-$}
   {affecte les variables $zmin3d$ et $zmax3d$ définissant
   l'intervalle de travail sur l'axe $Oz$ en $3d$}
 
\longref
   {$-$}
   {qplanxz}
   {$-$}
   {effectue un quadrillage du plan $xOz$}
 
\longref
   {$-$}
   {qplanyz}
   {$-$}
   {effectue un quadrillage du plan $yOz$}
 
\longref     
   {$[$ $A_0$ $\ldots $ $A_{n}$ $]$ $f$}    
   {papply3d}    
   {$[$ $b_0$ $\ldots $ $b_n$ $]$ ou $-$}    
   {construit un nouveau tableau en répétant, pour $i$ variant de $0$
   à $n$, l'opération suivante~: déposer le point $A_i$ puis
   exécuter $f$. Si à la fin de cette opération le tableau est
   vide, alors il est enlevé de la pile.}  
 
\longref
   {$[$ $A_0$ $\ldots $ $A_{n}$ $]$}    
   {isobarycentre3d}
   {$G$}
   {le point $G$ est le barycentre du système $[(A_0, 1) ;
   \ldots ; (A_n, 1)]$}
 
\longref
   {$[$ $A$ $a$ $B$ $b$ $]$}    
   {barycentre3d}
   {$G$}
   {le point $G$ est le barycentre du système $[(A, a) ; (B, b)]$}
 
\longref
   {$M$ $A$ $\alpha $}
   {hompoint3d}
   {$M'$}
   {$M'$ est l'image de $M$ par l'homothétie de centre $A$ et de
   rapport $\alpha $}
 
\longref
   {$M$ $A$}
   {sympoint3d}
   {$M'$}
   {$M'$ est l'image de $M$ par la symétrie de centre $A$}
 
\longref
   {$M$ $u$}
   {translatepoint3d}
   {$M'$}
   {$M'$ est l'image de $M$ par la translation de vecteur $\vec u$}
 
\longref
   {$x$ $y$ $z$  $k_1$ $k_2$ $k_3$}
   {scaleOpoint3d}
   {$k_1x$ $k_2y$ $k_3z$}
   {opère une \og dilatation\fg \ des coordonnées du point $M (x, y,
   z)$ sur les axes $Ox$, $Oy$ et $Oz$ suivant les facteurs $k_1$,
   $k_2$ et $k_3$}
 
\longref
   {$M$ $\alpha_x$ $\alpha_y$ $\alpha_z$}
   {rotateOpoint3d}
   {$M'$}
   {$M'$ est l'image de $M$ par la rotation de centre $O$ et d'angles
   respectifs $\alpha_x$ $\alpha_y$ $\alpha_z$ sur les axes $Ox$,
   $Oy$, $Oz$}
 
\longref
   {$x$ $y$ $z$}
   {3dto2d}
   {$X$ $Y$}
   {calcule les coordonnées du point projeté sur l'écran pour la
   représentation 3d. cette commande est synonyme de la commande
   |CamView|} 
 
\endsyntaxe
 
\paragraphe {Solides}
 
Ce paragraphe présente un ensemble de macros dont le but est la
manipulation et la représentation de solides convexes. Un effort
particulier a été fait pour respecter la philosophie de la
programmation \og orientée objet\fg , avec contrôle de typage et
messages d'erreurs adéquats.
 
Ce travail est basé sur les travaux de Manuel Luque
\footnum {|melusine.eu.org/syracuse/mluque/pst-v3d/|}
et de Christophe Poulain 
\footnum {|melusine.eu.org/syracuse/poulecl/macros/|} 
ainsi que
le cours d'infographie du Prof. Daniel Thalmann dans la section 
\og \sl Computer Graphics\fg \ du {\sl Virtual Reality Lab\/}
\footnum {|vrlab.epfl.ch/teaching/teaching\_index.html|}
. 
 
\sparagraphe {Le type {\sl solid}}
 
Le type $solid$ est un type à plusieurs composantes. Les deux
composantes essentielles sont~:
 
\item {$\bullet $} Un tableau des coordonnées (3d) des sommets du
solides. Ce tableau est indexé à partir de l'indice~$0$.
 
\item {$\bullet $} Un tableau des $faces$ du solide. Ce tableau est
indexé à partir de l'indice~$0$. 
 
Et l'on désigne par $face$ un tableau des indices des sommets de la
face considérée, avec la convention importante suivante~: les {\bf sommets
sont rangés dans l'ordre trigonométrique} si l'on regarde la face
considérée depuis l'extérieur du solide.
 
\sparagraphe {Solides précalculés}
 
Certains solides simples sont précalculés~: le cube, le cylindre, le
cône, le tronc de cône, la sphère, le tétraèdre, l'octaèdre,
l'icosaèdre, et le dodécaèdre.
 
\def \listingpath {%
   /var/www/syracuse/texpng/jpv/guide_jps/}
$$
   \epsillustrate {solide_01.ps}
$$
 
Voici la liste des commandes associées~:
\syntaxe
\longref
   {$a$}
   {newcube}
   {$solid$}
   {crée un nouveau cube, de type $solid$, de centre $O$,  d'arête $a$}
 
\longref
   {$z_0$ $r$ $z_1$}
   {newcylindre}
   {$solid$}
   {crée un nouveau cylindre, de type $solid$, d'axe $Oz$, de rayon
   $r$, allant du plan $z = z_0$ jusqu'au plan $z = z_1$}
 
\longref
   {$z_0$ $r$ $z_1$}
   {newcone}
   {$solid$}
   {crée un nouveau cône, de type $solid$, d'axe $Oz$, de rayon
   $r$, allant du plan $z = z_0$ jusqu'au plan $z = z_1$}
 
\longref
   {$z_0$ $r_0$ $z_1$ $r_1$}
   {newtronccone}
   {$solid$}
   {crée un nouveau tronc de cône, de type $solid$, d'axe $Oz$, de
   rayon de base $r_0$ (sur le plan  $z = z_0$) et de rayon au sommet
   $r_1$ (sur le plan $z = z_1$)}
 
\endsyntaxe
 
$$
   \epsillustrate {solide_02.ps}
$$
 
\syntaxe
\longref
   {$r$ $\{mode\}$}
   {newsphere}
   {$solid$}
   {crée une nouvelle sphère, de type $solid$, de centre $O$, de rayon
   $r$. Le paramètre $mode \in \{0, 1, 2, 3, 4\}$ est optionnel; il
   indique le niveau de résolution souhaité ($0 = $ mini, $4$ = maxi)}
 
\longref
   {$-$}
   {newtetraedre}
   {$solid$}
   {crée un nouveau tétraèdre régulier, de type $solid$}
 
\longref
   {$-$}
   {newoctaedre}
   {$solid$}
   {crée un nouvel octaèdre  régulier, de type $solid$, de centre $O$}
 
\longref
   {$-$}
   {newicosaedre}
   {$solid$}
   {crée un nouvel isocaèdre régulier, de type $solid$, de centre $O$}
 
\longref
   {$-$}
   {newdodecaedre}
   {$solid$}
   {crée un nouveau dodécaèdre régulier, de type $solid$, de centre $O$}
 
\endsyntaxe
 
Pour la sphère, plusieurs modes de résolution sont proposés par un
argument optionnel. Le mode par défaut est stocké dans la variable
$defaultsolidmode$. Ceci permet de préparer son image en basse
résolution (mode $0$), et de ne calculer la résolution souhaitée,
lourde en temps de calcul, qu'au dernier moment.
 
\` A terme, il est prévu de proposer un tel mode pour le cylindre, le
cône et le tronc de cône.
 
\syntaxe
\Longref
   {}
   {defaultsolidmode}
   {mode de résolution par défaut pour les solides}
   {2}
 
\endsyntaxe
 
\sparagraphe {Dessiner un solide}
 
Pour dessiner un solide, on dispose des commandes |drawsolid| et
|drawsolid*|, cette dernière réalisant en plus le coloriage des faces
visibles. 
 
\syntaxe 
 
\longref
   {$solid$}
   {drawsolid}
   {$-$}
   {dessine le solide $solid$}
 
\longref
   {$solid$}
   {drawsolid*}
   {$-$}
   {dessine le solide $solid$ avec coloriage des faces visibles}
\endsyntaxe
 
On dispose de plus d'un booléen permettant d'activer ou non la
représentation des arêtes cachées.
 
\syntaxe
\Longref
   {}
   {aretescachees}
   {booléen indiquant à la procédure |drawsolid| si l'on doit ou
   non représenter les arêtes cachées}
   {$true$}
 
\endsyntaxe
 
Par défaut, et comme à l'habitude, la couleur de coloriage des faces
est définie par $fillstyle$. Pour une scène non éclairée (voir plus
loin), 2~autres méthodes sont prévues pour spécifier la couleur d'une
face donnée~:
 
\item {$\bullet $} on spécifie, face par face, la couleur souhaitée
avec la commande |solidputfcolors|. La couleur est spécifiée par une
chaîne de caractères.
 
\item {$\bullet $} on spécifie la couleur souhaitée en fonction du
nombre de côtés des faces. On utilise pour cela la commande
|solidputncolors|.
 
Pour chacune de ces méthodes, on transmet comme argument un tableau
comportant des chaînes de caractères désignant la couleur
souhaitée. Ce tableau est ensuite stocké comme un constituant du
solide. Pour un solide donné, on obtient ces tableaux avec les
commandes |solidgetfcolors| et |solidgetncolors|. L'élement d'indice
$i$ du tableau $fcolor$ correspond à la face d'indice $i$ du solide,
et l'élement d'indice $i$ du tableau $ncolors$ correspond à la
couleurs des faces à $3 + i$ côtés.
 
Dans l'exemple ci-dessous, on dessine un cube tronqué, dont on précise
que les faces triangulaires (à 3~côtés) doivent êtres colorées en
bleu. On définit ensuite un nouveau cube, que l'on stocke dans la
variable $moncube$, dont on définit la couleur pour chacune des
$6$~faces. On représente ensuite 2~instances de ce cube, l'une ayant
subi une rotation de $120°$ autour de l'axe $Oz$.
 
Dans cet exemple, on pourra remarquer un phénomène étonnant au premier
abord~: dans la variable $moncube$ est stocké le cube de centre $O$ et
d'arête~$2$. On fait ensuite subir à ce solide une rotation de $20°$
autour de l'axe $Oz$, puis une translation de vecteur $\vec k$
(vecteur unitaire dirigeant l'axe $Oz$). La variable $moncube$ désigne
alors, non pas le cube d'origine, mais le cube transformé. Ce
phénomène s'explique par le fait que pour les objets complexes (les
solides, mais aussi les matrices, les tableaux, etc...), ce n'est pas
l'objet que postscript dépose sur la pile, mais une référence à
l'objet (un {\sl pointeur\/} comme l'on dit en C, en Pascal ou en
ADA). Or les transformations effectuées agissent sur l'objet
lui-même... 
 
Si l'on veut éviter ce phénomène, et si l'on souhaite avoir 2~solides
identiques, mais avec des instances séparées, on utilisera la commande
|dupsolid| qui construit une nouvelle instance, copie conforme de
celle passée en argument.
 
\exempledble {solide_03} {.4}{.5} 
 
\syntaxe
\longref
   {$solid$ $array$}
   {solidputncolors}
   {$-$}
   {affecte au solide $solid$ le tableau $array$ en tant que tableau
   des couleurs des faces à $n$ côtés}
 
\longref
   {$solid$}
   {solidgetncolors}
   {$array$}
   {le tableau $array$ est le tableau  des couleurs des faces à $n$
   côtés pour le solide $solid$} 
 
\longref
   {$solid$ $array$}
   {solidputfcolors}
   {$-$}
   {affecte au solide $solid$ le tableau $array$ en tant que tableau
   des couleurs des faces}
 
\longref
   {$solid$}
   {solidgetfcolors}
   {$array$}
   {le tableau $array$ est le tableau des couleurs des faces pour le
   solide $solid$}  
 
\endsyntaxe
 
\sparagraphe {Générer un solide}
 
Un solide peut être {\sl monoface}, auquel cas il sera visible du côté
de sa normale, et invisible du côté \og arrière\fg . Il peut également
être {\sl biface}, autrement dit aplati.
 
Plusieurs méthodes pour générer un solide~:
 
\item {$\bullet $} On crée un tableau des coordonnées des sommets,
puis le tableau des faces, qui est lui-même un tableau de tableaux,
ces derniers contenant, pour une face donnée, les indices des sommets
de la face, rangés par ordre trigonométrique lorsque l'on regarde le
solide de l'extérieur. On dépose alors les 2~tableaux (sommets et
faces) sur la pile et on invoque la fonction |generesolid|.
 
\exempledble {solide_gen_02} {.3}{.6} 
 
\item {$\bullet $} 
On construit un tableau des coordonnées 2d des sommets d'une face 
sur le plan $xOy$, puis on invoque l'une des fonctions |generemonoface|,
|generebiface|, |genereprismedroit| ou |genereprisme|.
 
\exempledble {solide_gen_01} {.3}{.6} 
 
\item {} {\bf Remarque~:} Pour cette dernière méthode, le tableau
décrivant la face initiale peut-être obtenue du chemin continu courant
en utilisant la commande $currentcpathpointstable$. Par exemple, voici
un prisme droit généré à partir de la courbe $y = 2\sin x$~:
\exempledble {solide_gen_04} {.3}{.6} 
 
En résumé~:
\syntaxe
\longref
   {$array_1$ $array_2$}
   {generesolid}
   {$solid$}
   {construit un solide dont le tableaux des sommets est $array_1$ et
   le tableau des faces est $array_2$}
 
\longref
   {$array$}
   {generemonoface}
   {$solid$}
   {construit un solide monoface, dans le plan $xOy$, à partir du
   tableau de points $array$. Les points sont en 2d, et rangés de
   manière à décrire l'orientation de la face (sens trigonométrique
   $\Rightarrow $ face de normale $Oz$, dans le sens des $z$ croissants)}
 
\longref
   {$array$}
   {generebiface}
   {$solid$}
   {construit un solide biface, dans le plan $xOy$, à partir du
   tableau de points $array$. Les points sont en 2d, et rangés de
   manière à décrire l'orientation de la face (sens trigonométrique
   $\Rightarrow $ face de normale $Oz$, dans le sens des $z$ croissants)}
 
\longref
   {$array$ $z_0$ $z_1$}
   {genereprismedroit}
   {$solid$}
   {construit un prisme droit d'axe $Oz$ à partir du
   tableau de points $array$. Les points sont en 2d, et rangés de
   manière à décrire l'orientation de la face (sens trigonométrique
   $\Rightarrow $ face de normale $Oz$, dans le sens des $z$
   croissants). La base du prisme est sur le 
   plan $z = z_0$ et sa face supérieure sur le plan $z = z_1$}
 
\longref
   {$array$ $z_0$ $z_1$ $\vec u$}
   {genereprisme}
   {$solid$}
   {construit un prisme oblique d'axe $(O, \vec u)$ à partir du
   tableau de points $array$. Les points sont en 2d, et rangés de
   manière à décrire l'orientation de la face (sens trigonométrique
   $\Rightarrow $ face de normale $Oz$, dans le sens des $z$
   croissants). La base du prisme est sur le 
   plan $z = z_0$ et sa face supérieure sur le plan $z = z_1$}
 
\endsyntaxe
 
\sparagraphe {Opérations sur les solides}
 
La commande |solidtransform| à qui l'on passe en argument une
transformation $\rset ^3 \rightarrow \rset ^3$, applique cette
tranformation à chacun des sommets du solide considéré. Ceci permet
notamment, par le biais des commandes |translatepoint3d| et
|rotate0point3d|, de positionner un solide dans l'espace, mais aussi,
par l'intermédiaire de |scaleOpoint3d|, de changer ses dimensions. 
 
\syntaxe
\longref
   {$solid$ $\{f\}$}
   {solidtransform}
   {$solid$}
   {applique la transformation $f$ au solide $solid$, $f$ étant une
   application $\rset ^3 \longrightarrow \rset ^3$}
 
\endsyntaxe
 
Pour les cubes, on dispose de la commande |tronque_cube|, qui permet
d'obtenir un nouveau solide en coupant les $8$~coins du cube d'origine
suivant une proportion que l'on transmet en argument ($2 \Rightarrow $
on coupe au milieu de l'arête, $3\Rightarrow $ au tiers, $4\Rightarrow
$ au quart, etc...)
 
\syntaxe
\longref
   {$solid_1$ $n$}
   {tronque\_cube}
   {$solid_2$}
   {$solid_2$ est le solide obtenu en coupant chaque coin du
   parallélépipède $solid_1$. $n$ est un réel supérieur à 2 indiquant
   la proportion à respecter pour la tronquature}
 
\endsyntaxe
 
\sparagraphe {\' Eclairage par une source lumineuse ponctuelle}
 
Il est possible d'éclairer la scène par une source ponctuelle d'une
couleur et d'une intensité donnée. Dans ce cas, seule cette dernière
est prise en compte pour le coloriage des faces des solides
présents. L'intensité de la couleur d'une face dépend de l'inclinaison
de la face et de sa distance par rapport à la source lumineuse.
\exempledble {solide_light} {.3}{.6} 
 
\syntaxe
\longref
   {$array$}
   {setlight}
   {$-$}
   {Affecte la couleur de la source lumineuse ponctuelle dans l'espace
   RGB ou CYMK, $array$ étant un tableau de 3 ou 4 réels de
   l'intervalle $[0; 1]$}
 
\longref
   {$\{f\}$}
   {setlight}
   {$-$}
   {\' Exécute la fonction $f$ dans un |gsave .. grestore|, y récupère
   la définition de la couleur dans l'espace RGB, puis
   l'affecte à la couleur de la source lumineuse ponctuelle.}
 
\longref
   {$i$}
   {setlightintensity}
   {$-$}
   {Affecte l'intensité de la source lumineuse ponctuelle}
 
\longref
   {$x$ $y$ $z$}
   {setlightsrc}
   {$-$}
   {Affecte la position de la source lumineuse ponctuelle}
 
\Longref
   {}
   {lightintensity}
   {Intensité de la source lumineuse ponctuelle}
   {$1$}
 
\endsyntaxe
 
\sparagraphe {Boîte à outils}
 
Ci-dessous une liste des autres fonctions et procédures disponibles
concernant le type $solid$~:
 
\syntaxe 
 
\longref
   {$-$}
   {newsolid}
   {$solid$}
   {dépose le solide nul sur la pile}
 
\longref
   {$solid$}
   {emptysolid}
   {$bool$}
   {$bool$ vaut $true$ si le solide est vide, $false$ sinon}
 
\longref
   {$any$}
   {issolid}
   {$bool$}
   {$bool$ vaut $true$ si $any$ est de type $solid$, $false$ sinon}
 
\longref
   {$solid$ $i$}
   {solidgetsommetsface}
   {$array$}
   {$array$ est le tableau des sommets de la face d'indice $i$ du solide $solid$}
 
\longref
   {$solid$ $i$ $j$}
   {solidgetsommetface}
   {$S$}
   {$S$ est le sommet d'indice $i$ de la face d'indice $j$ du solide $solid$}
 
\longref
   {$solid$ $i$}
   {solidgetsommet}
   {$S$}
   {$S$ est le sommet d'indice $i$ du solide $solid$}
 
\longref
   {$solid$}
   {solidgetpointstable}
   {$array$}
   {$array$ est le tableau des sommets du solide}
 
\longref
   {$solid$}
   {solidgetfaces}
   {$array$}
   {$array$ est le tableau des faces du solide}
 
\longref
   {$solid$ $i$}
   {solidgetface}
   {$array$}
   {$array$ est le tableau décrivant la face $i$ du solide (tableau
   d'indices de sommets)}
 
\longref
   {$solid$ $array$}
   {solidputpointstable}
   {$solid$}
   {}
 
\longref
   {$solid$ $array$}
   {solidputfaces}
   {$array$}
   {}
 
\longref
   {$solid$ $i$}
   {solidfacevisible?}
   {$bool$}
   {$bool$ vaut $true$ si la face d'indice $i$ du solide $solid$ est visible}
 
\longref
   {$solid$ $i$}
   {solidnormaleface}
   {$\vec u$}
   {$\vec u$ est un vecteur normal à la face d'indice $i$ du solide
   $solid$. Le vecteur est orientaté vers l'extérieur du solide}
 
\longref
   {$solid$ $i$}
   {solidcentreface}
   {$G$}
   {le point $G$ est le centre de la face d'indice $i$ du solide $solid$}
 
\longref
   {$solid$}
   {solidnombresommets}
   {$n$}
   {nombres de sommets du solide $solid$}
 
\longref
   {$solid$}
   {solidnombrefaces}
   {$n$}
   {nombres de faces du solide $solid$}
 
\longref
   {$solid$}
   {solidnumsommets}
   {$-$}
   {affiche en face de chaque sommet du solide $solid$ son indice dans
   le tableau des sommets} 
 
\longref
   {$solid$}
   {dupsolid}
   {$solid$ $solid$}
   {crée une nouvelle instance du solide déposé sur la pile} 
 
\endsyntaxe
 
\bye
 
\longref
   {}
   {xplanmappoint}
   {}
   {}
 
\longref
   {}
   {yplanmappoint}
   {}
   {}
 
\longref
   {}
   {zplanmappoint}
   {}
   {}