{"id":90,"date":"2026-08-15T16:38:52","date_gmt":"2026-08-15T14:38:52","guid":{"rendered":"https:\/\/homelab.nvsd.ch\/?p=90"},"modified":"2026-08-15T16:38:53","modified_gmt":"2026-08-15T14:38:53","slug":"math-art-webgl-geometrie-generative-docker","status":"publish","type":"post","link":"https:\/\/homelab.nvsd.ch\/?p=90","title":{"rendered":"Math Art : quand les droites dessinent des courbes \u2014 du modulo au laboratoire WebGL"},"content":{"rendered":"\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"images\/math-art-hero-homelab.png\" alt=\"Math Art \u2014 du modulo au laboratoire WebGL\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Il suffit parfois d\u2019une figure tr\u00e8s simple pour ouvrir un chantier beaucoup plus vaste.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le point de d\u00e9part de <strong>Math Art<\/strong> est une construction g\u00e9om\u00e9trique compos\u00e9e de points dispos\u00e9s sur un cercle et de segments reliant ces points selon une r\u00e8gle num\u00e9rique. Au premier regard, on ne voit que des droites. Pourtant, lorsque leur nombre augmente, des courbes apparaissent. Lorsque le multiplicateur \u00e9volue, ces courbes se transforment. Et lorsque l\u2019animation acc\u00e9l\u00e8re tout en conservant une partie des images pr\u00e9c\u00e9dentes, la g\u00e9om\u00e9trie commence \u00e0 produire des formes fluides, presque organiques.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ce qui devait \u00eatre une petite exp\u00e9rience math\u00e9matique est progressivement devenu un <strong>laboratoire de g\u00e9om\u00e9trie g\u00e9n\u00e9rative<\/strong> : un moteur WebGL contr\u00f4lable \u00e0 la souris, des param\u00e8tres modifiables en temps r\u00e9el, un syst\u00e8me de persistance visuelle et un export vid\u00e9o directement depuis le navigateur.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dans le Homelab, ce projet est aussi un cas d\u2019\u00e9cole : comment passer d\u2019une id\u00e9e math\u00e9matique \u00e0 une application interactive, la conteneuriser avec Docker, la piloter avec Dockge, l\u2019exposer avec Nginx Proxy Manager et conserver un acc\u00e8s priv\u00e9 via Tailscale.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Objectif du projet :<\/strong> rendre visible une r\u00e8gle math\u00e9matique, permettre de la manipuler, puis documenter aussi bien le ph\u00e9nom\u00e8ne que l\u2019infrastructure qui le rend accessible.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1. De l\u2019observation au laboratoire<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La premi\u00e8re question \u00e9tait presque na\u00efve : <strong>comment une collection de segments droits peut-elle donner l\u2019impression de dessiner une courbe ?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour le comprendre, j\u2019ai commenc\u00e9 par reconstruire la figure \u00e0 partir de ses \u00e9l\u00e9ments les plus simples :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>un cercle de rayon <code>R<\/code> ;<\/li>\n\n\n\n<li><code>n<\/code> points r\u00e9guli\u00e8rement espac\u00e9s ;<\/li>\n\n\n\n<li>une r\u00e8gle associant chaque point \u00e0 un autre point ;<\/li>\n\n\n\n<li>un segment trac\u00e9 entre les deux.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">La premi\u00e8re version pouvait \u00eatre calcul\u00e9e en 2D. Mais tr\u00e8s vite, l\u2019int\u00e9r\u00eat n\u2019\u00e9tait plus seulement de g\u00e9n\u00e9rer une image fixe : il fallait pouvoir modifier <code>n<\/code>, faire \u00e9voluer le multiplicateur <code>m<\/code>, tourner la sc\u00e8ne, zoomer et observer les transitions en temps r\u00e9el.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">C\u2019est l\u00e0 que <strong>WebGL<\/strong> est devenu naturel. Le navigateur ne servait plus simplement \u00e0 afficher une page : il devenait le moteur du laboratoire.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Puis une deuxi\u00e8me observation a chang\u00e9 la direction du projet. En augmentant la vitesse de variation de <code>m<\/code>, certaines transitions produisaient des formes hypnotiques. Les segments semblaient se transformer en filaments ou en surfaces courbes mouvantes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour rendre ce ph\u00e9nom\u00e8ne perceptible, j\u2019ai ajout\u00e9 une <strong>persistance temporelle<\/strong> : au lieu d\u2019effacer compl\u00e8tement l\u2019image pr\u00e9c\u00e9dente, le moteur en conserve une fraction. Les \u00e9tats successifs s\u2019accumulent alors comme une trace lumineuse.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-persistance-mouvement-1024x576.png\" alt=\"Trois \u00e9tats de Math Art montrant la transformation des lignes et l\u2019effet de persistance temporelle\" class=\"wp-image-96\" srcset=\"https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-persistance-mouvement-1024x576.png 1024w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-persistance-mouvement-300x169.png 300w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-persistance-mouvement-768x432.png 768w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-persistance-mouvement-1536x864.png 1536w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-persistance-mouvement.png 1600w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Cette fonction a naturellement conduit \u00e0 l\u2019<strong>export vid\u00e9o<\/strong> : si la transformation est int\u00e9ressante dans le temps, il faut pouvoir la conserver et la comparer.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2. La r\u00e8gle math\u00e9matique de d\u00e9part<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le moteur commence par r\u00e9partir <code>n<\/code> points r\u00e9guli\u00e8rement sur un cercle.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour le point d\u2019indice <code>k<\/code>, l\u2019angle est :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>\\theta_k = \\frac{2\\pi k}{n}<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ses coordonn\u00e9es sont donc :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>P_k = \\left(R\\cos\\frac{2\\pi k}{n},\\;R\\sin\\frac{2\\pi k}{n}\\right)<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On choisit ensuite un multiplicateur <code>m<\/code>. Dans la version discr\u00e8te classique, le point <code>k<\/code> est reli\u00e9 au point :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>k&rsquo; = (m k) \\bmod n<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le <strong>modulo<\/strong> joue ici le r\u00f4le d\u2019une horloge. Lorsque le calcul d\u00e9passe le dernier point, on revient au d\u00e9but du cercle.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Avec <code>n = 10<\/code> et <code>m = 2<\/code>, par exemple :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>0 \u2192 0\n1 \u2192 2\n2 \u2192 4\n3 \u2192 6\n4 \u2192 8\n5 \u2192 0\n6 \u2192 2\n...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pour obtenir une animation parfaitement fluide, Math Art utilise aussi une version continue : au lieu de viser uniquement un sommet discret, l\u2019angle <code>\u03b8<\/code> est envoy\u00e9 vers :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>\\theta \\longmapsto m\\theta \\pmod{2\\pi}<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le point d\u2019arriv\u00e9e reste donc exactement sur le cercle, m\u00eame lorsque <code>m = 2.173<\/code> ou <code>m = 3.624<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-formule-modulo-1024x576.png\" alt=\"D\u00e9monstration visuelle de la multiplication modulo sur un cercle et de l\u2019enveloppe cardio\u00efde pour m \u00e9gal \u00e0 2.\" class=\"wp-image-94\" srcset=\"https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-formule-modulo-1024x576.png 1024w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-formule-modulo-300x169.png 300w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-formule-modulo-768x432.png 768w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-formule-modulo-1536x864.png 1536w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-formule-modulo.png 1600w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3. Pourquoi des droites font-elles appara\u00eetre une courbe ?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">C\u2019est la partie la plus fascinante du projet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Prenons un point du cercle \u00e0 l\u2019angle <code>\u03b8<\/code> et son point cible \u00e0 l\u2019angle <code>m\u03b8<\/code>. La droite passant par deux points d\u2019un m\u00eame cercle, situ\u00e9s aux angles <code>\u03b1<\/code> et <code>\u03b2<\/code>, peut s\u2019\u00e9crire sous la forme :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>x\\cos\\left(\\frac{\\alpha+\\beta}{2}\\right)<br>+y\\sin\\left(\\frac{\\alpha+\\beta}{2}\\right)<br>=R\\cos\\left(\\frac{\\alpha-\\beta}{2}\\right)<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ici :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>\\alpha=\\theta, \\qquad \\beta=m\\theta<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La famille de droites devient donc :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>F(x,y,\\theta)=<br>x\\cos\\left(\\frac{m+1}{2}\\theta\\right)<br>+y\\sin\\left(\\frac{m+1}{2}\\theta\\right)<br>-R\\cos\\left(\\frac{m-1}{2}\\theta\\right)=0<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Une <strong>enveloppe<\/strong> est une courbe tangente \u00e0 chacune des droites de cette famille au voisinage du point correspondant. Pour la trouver, on r\u00e9sout simultan\u00e9ment :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>F(x,y,\\theta)=0<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">et<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>\\frac{\\partial F}{\\partial\\theta}=0<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Apr\u00e8s r\u00e9solution, on obtient :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>x(\\theta)=\\frac{R}{m+1}\\left(m\\cos\\theta+\\cos(m\\theta)\\right)<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>y(\\theta)=\\frac{R}{m+1}\\left(m\\sin\\theta+\\sin(m\\theta)\\right)<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour <code>m = 2<\/code> :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>x(\\theta)=\\frac{R}{3}\\left(2\\cos\\theta+\\cos2\\theta\\right)<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>y(\\theta)=\\frac{R}{3}\\left(2\\sin\\theta+\\sin2\\theta\\right)<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cette param\u00e9trisation d\u00e9crit une <strong>cardio\u00efde<\/strong>, \u00e0 une rotation\/orientation pr\u00e8s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Autrement dit, la courbe n\u2019est pas r\u00e9ellement dessin\u00e9e dans le programme. Le programme ne trace que des segments. <strong>La cardio\u00efde appara\u00eet comme enveloppe de la famille de droites.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">C\u2019est pr\u00e9cis\u00e9ment ce qui donne au projet son int\u00e9r\u00eat : une structure complexe devient visible \u00e0 partir d\u2019une r\u00e8gle extr\u00eamement compacte.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Et lorsque <code>n<\/code> augmente ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Avec peu de points, on distingue surtout les segments ind\u00e9pendants. Lorsque <code>n<\/code> devient grand, les droites \u00e9chantillonnent de plus en plus finement la famille continue. Notre perception rep\u00e8re alors beaucoup plus facilement leur enveloppe.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le GPU permet d\u2019augmenter cette densit\u00e9 tout en conservant une interaction fluide.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. La m\u00eame id\u00e9e, expliqu\u00e9e sans formules<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Imaginons une horloge poss\u00e9dant non pas 12 positions, mais 200.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On num\u00e9rote ces positions de 0 \u00e0 199.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On choisit ensuite une r\u00e8gle tr\u00e8s simple : <strong>multiplier chaque num\u00e9ro par 2 et revenir au d\u00e9but lorsque l\u2019on d\u00e9passe 199<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le point 17, par exemple, est reli\u00e9 au point 34. Le point 80 au point 160. Et si le calcul d\u00e9passe 199, le modulo le ram\u00e8ne \u00e0 l\u2019int\u00e9rieur de notre cercle.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On r\u00e9p\u00e8te l\u2019op\u00e9ration pour tous les points.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Chaque liaison est parfaitement droite. Mais toutes ces droites ne sont pas r\u00e9parties au hasard : leurs directions changent progressivement selon une r\u00e8gle commune. Elles viennent alors <strong>fr\u00f4ler une m\u00eame fronti\u00e8re invisible<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Notre \u0153il voit cette fronti\u00e8re avant m\u00eame que le programme ne la dessine : c\u2019est l\u2019enveloppe.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">C\u2019est un peu comme observer des dizaines de tangentes autour d\u2019un objet invisible. L\u2019objet n\u2019est jamais trac\u00e9 directement, mais sa forme devient \u00e9vidente gr\u00e2ce aux lignes qui l\u2019effleurent.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pourquoi le mouvement semble-t-il organique ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Lorsque <code>m<\/code> varie lentement, chaque famille de lignes se transforme en la suivante.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lorsque <code>m<\/code> varie vite, notre perception ne traite plus chaque \u00e9tat comme une figure compl\u00e8tement ind\u00e9pendante. Et si le moteur conserve volontairement une partie des \u00e9tats pr\u00e9c\u00e9dents, plusieurs g\u00e9om\u00e9tries se superposent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On peut mod\u00e9liser ce principe de persistance par :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<br>I_t=(1-\\alpha)F_t+\\alpha I_{t-1}<br>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">o\u00f9 :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>F_t<\/code> est la nouvelle image calcul\u00e9e ;<\/li>\n\n\n\n<li><code>I_{t-1}<\/code> est l\u2019image persistante pr\u00e9c\u00e9dente ;<\/li>\n\n\n\n<li><code>\u03b1<\/code> contr\u00f4le la dur\u00e9e de la trace.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Avec <code>\u03b1<\/code> faible, les anciennes lignes disparaissent rapidement. Avec <code>\u03b1<\/code> \u00e9lev\u00e9, les transformations laissent des tra\u00een\u00e9es longues. C\u2019est dans cette zone que des spirales, vagues et structures presque fluides deviennent particuli\u00e8rement visibles.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il n\u2019y a donc pas de \u00ab courbe magique \u00bb ajout\u00e9e par-dessus : c\u2019est l\u2019<strong>accumulation temporelle d\u2019une r\u00e8gle g\u00e9om\u00e9trique en \u00e9volution<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5. Quelques rep\u00e8res historiques<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Math Art assemble des id\u00e9es anciennes avec des outils tr\u00e8s contemporains.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La <strong>cardio\u00efde<\/strong> \u00e9tait \u00e9tudi\u00e9e bien avant l\u2019informatique. Le nom est attribu\u00e9 au math\u00e9maticien Johann Castillon dans un travail publi\u00e9 en 1741. La courbe peut appara\u00eetre dans plusieurs constructions g\u00e9om\u00e9triques ; ici, elle \u00e9merge comme enveloppe de cordes associ\u00e9es par la multiplication sur le cercle.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">L\u2019<strong>arithm\u00e9tique modulaire<\/strong> a \u00e9t\u00e9 syst\u00e9matis\u00e9e par Carl Friedrich Gauss dans les <em>Disquisitiones Arithmeticae<\/em>, publi\u00e9es en 1801. Gauss y introduit notamment la notation moderne des congruences.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Deux si\u00e8cles plus tard, un navigateur Web peut appliquer ces id\u00e9es plusieurs dizaines de fois par seconde, les transformer en g\u00e9om\u00e9trie lumineuse et enregistrer leur \u00e9volution en vid\u00e9o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le changement n\u2019est donc pas n\u00e9cessairement dans la formule. Il est dans notre capacit\u00e9 \u00e0 <strong>l\u2019explorer de fa\u00e7on continue et interactive<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">6. Pourquoi WebGL et Three.js ?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Une premi\u00e8re version 2D suffit pour comprendre l\u2019algorithme, mais elle devient vite limit\u00e9e lorsque l\u2019on veut :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>afficher plusieurs centaines ou milliers de segments ;<\/li>\n\n\n\n<li>recalculer la g\u00e9om\u00e9trie en temps r\u00e9el ;<\/li>\n\n\n\n<li>appliquer une rotation de sc\u00e8ne ;<\/li>\n\n\n\n<li>zoomer et d\u00e9placer la cam\u00e9ra ;<\/li>\n\n\n\n<li>utiliser des couleurs par sommet ;<\/li>\n\n\n\n<li>ajouter de la persistance ;<\/li>\n\n\n\n<li>conserver une fr\u00e9quence d\u2019affichage \u00e9lev\u00e9e.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">La version actuelle repose sur <strong>Three.js<\/strong> et son moteur WebGL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Les positions des segments sont stock\u00e9es dans une <code>BufferGeometry<\/code>. Chaque paire de sommets forme un segment rendu par <code>LineSegments<\/code>. Cette approche est adapt\u00e9e \u00e0 une g\u00e9om\u00e9trie recalcul\u00e9e fr\u00e9quemment et \u00e9vite de cr\u00e9er un objet JavaScript distinct pour chaque ligne.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sch\u00e9matiquement :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>n, m, rayon\n     \u2502\n     \u25bc\ncalcul des angles\n     \u2502\n     \u25bc\npositions des extr\u00e9mit\u00e9s\n     \u2502\n     \u25bc\nBufferGeometry\n     \u2502\n     \u25bc\nLineSegments\n     \u2502\n     \u25bc\nWebGLRenderer \u2192 GPU<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La souris agit ensuite directement sur la cam\u00e9ra ou le groupe g\u00e9om\u00e9trique :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>glisser : rotation ;<\/li>\n\n\n\n<li>molette : zoom ;<\/li>\n\n\n\n<li>clic droit + glisser : d\u00e9placement ;<\/li>\n\n\n\n<li><code>Shift<\/code> + molette : modification de <code>m<\/code> ;<\/li>\n\n\n\n<li>double-clic : recentrage.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Cette interaction transforme la figure en <strong>objet d\u2019exploration<\/strong>, et non plus en simple illustration.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">7. Capturer les formes en vid\u00e9o<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L\u2019export vid\u00e9o est devenu important apr\u00e8s avoir observ\u00e9 que certaines transitions \u00e9taient plus int\u00e9ressantes que les images fixes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le navigateur fournit directement les briques n\u00e9cessaires :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const stream = canvas.captureStream(60);\nconst recorder = new MediaRecorder(stream);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>captureStream()<\/code> transforme le contenu vivant du canvas en <code>MediaStream<\/code>. <code>MediaRecorder<\/code> peut ensuite enregistrer ce flux et produire des blocs vid\u00e9o assembl\u00e9s en fichier.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dans Math Art, cela permet d\u2019enregistrer :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>l\u2019\u00e9volution automatique du multiplicateur ;<\/li>\n\n\n\n<li>la persistance ;<\/li>\n\n\n\n<li>les changements de param\u00e8tres ;<\/li>\n\n\n\n<li>les rotations et zooms effectu\u00e9s \u00e0 la souris ;<\/li>\n\n\n\n<li>une s\u00e9quence \u00e0 30 ou 60 images par seconde.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">L\u2019export devient alors une extension naturelle du laboratoire : <strong>observer, trouver une transformation int\u00e9ressante, puis la conserver<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Une prochaine \u00e9tape logique sera un mode <em>S\u00e9quence<\/em> capable de d\u00e9finir pr\u00e9cis\u00e9ment :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>m d\u00e9part     2.000\nm arriv\u00e9e    7.000\ndur\u00e9e        30 s\ncourbe       lin\u00e9aire \/ ease-in-out \/ sinuso\u00efdale\nrotation     0 \u2192 180\u00b0\npersistance  0.70 \u2192 0.92<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On passerait ainsi de l\u2019enregistrement manuel \u00e0 une v\u00e9ritable <strong>composition math\u00e9matique reproductible<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">8. Du navigateur au VPS : le r\u00f4le du Homelab<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le projet reste volontairement l\u00e9ger c\u00f4t\u00e9 serveur. Toute la g\u00e9n\u00e9ration g\u00e9om\u00e9trique est effectu\u00e9e dans le navigateur de l\u2019utilisateur.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le conteneur n\u2019a donc besoin que de servir des fichiers statiques :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM nginx:alpine\nCOPY nginx.conf \/etc\/nginx\/conf.d\/default.conf\nCOPY . \/usr\/share\/nginx\/html\nEXPOSE 80<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le stack Dockge rejoint le r\u00e9seau Docker externe <code>proxy<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>services:\n  math-art:\n    build:\n      context: .\n      dockerfile: Dockerfile\n    container_name: math-art\n    restart: unless-stopped\n    networks:\n      - proxy\n\nnetworks:\n  proxy:\n    external: true<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nginx Proxy Manager peut alors joindre directement :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Scheme:           http\nForward Hostname: math-art\nForward Port:     80<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aucun port h\u00f4te suppl\u00e9mentaire n\u2019est n\u00e9cessaire pour l\u2019acc\u00e8s public.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-architecture-homelab-1024x576.png\" alt=\" Architecture de d\u00e9ploiement Math Art avec Docker, Dockge, Nginx Proxy Manager, r\u00e9seau proxy et Tailscale.\" class=\"wp-image-92\" srcset=\"https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-architecture-homelab-1024x576.png 1024w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-architecture-homelab-300x169.png 300w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-architecture-homelab-768x432.png 768w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-architecture-homelab-1536x864.png 1536w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-architecture-homelab.png 1600w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Acc\u00e8s priv\u00e9 par Tailscale<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Pour le d\u00e9veloppement ou le diagnostic, un port peut \u00eatre li\u00e9 uniquement \u00e0 l\u2019adresse Tailscale du VPS :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ports:\n  - \"&lt;IP_TAILSCALE&gt;:8003:80\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On obtient alors deux chemins ind\u00e9pendants :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PUBLIC\nInternet \u2192 HTTPS \u2192 NPM \u2192 r\u00e9seau proxy \u2192 math-art:80\n\nPRIV\u00c9\nTailscale \u2192 :8003 \u2192 math-art:80<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Cette s\u00e9paration correspond \u00e0 une r\u00e8gle que j\u2019essaie de g\u00e9n\u00e9raliser dans le Homelab : <strong>ne publier directement que ce qui doit r\u00e9ellement l\u2019\u00eatre<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">9. Rebuild propre avec Dockge<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Lorsqu\u2019un stack contient un <code>Dockerfile<\/code>, un simple <em>Start<\/em> ne suffit pas toujours apr\u00e8s une modification du frontend. L\u2019ancienne image peut encore contenir les fichiers pr\u00e9c\u00e9dents.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour forcer une reconstruction compl\u00e8te :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/opt\/homelab\/stacks\/math-art\n\ndocker compose down --remove-orphans\ndocker compose build --no-cache --pull\ndocker compose up -d --force-recreate\n\ndocker compose ps\ndocker compose logs --tail=50<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On peut ensuite contr\u00f4ler directement le contenu servi par Nginx :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker exec math-art ls -lah \/usr\/share\/nginx\/html\/\ndocker exec math-art wget -qO- http:\/\/127.0.0.1\/ | head -n 20<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ce type de v\u00e9rification est utile parce qu\u2019il permet de s\u00e9parer imm\u00e9diatement trois cat\u00e9gories de probl\u00e8mes :<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>le build Docker ;<\/li>\n\n\n\n<li>le conteneur Nginx ;<\/li>\n\n\n\n<li>le reverse proxy ou le cache navigateur.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">10. Une r\u00e9f\u00e9rence technique, mais aussi un outil de recherche<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Math Art n\u2019a pas vocation \u00e0 rester une d\u00e9monstration isol\u00e9e.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il s\u2019inscrit dans un ensemble de projets o\u00f9 les <strong>math\u00e9matiques, la g\u00e9om\u00e9trie, les fractales, la musique et la perception<\/strong> peuvent \u00eatre explor\u00e9es avec des outils num\u00e9riques.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><a href=\"http:\/\/concerto.nvsd.ch\/\">Concerto \u00e0 l\u2019Infini<\/a><\/strong> part des nombres premiers pour produire une \u0153uvre musicale, po\u00e9tique et g\u00e9om\u00e9trique. Sa charte visuelle \u2014 noir profond, cyan, violet, magenta, lignes lumineuses fines et mouvements lents \u2014 fournit d\u00e9sormais un langage commun \u00e0 cet \u00e9cosyst\u00e8me.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><a href=\"https:\/\/fractal.nvsd.ch\/\">Fractal Studio<\/a><\/strong> explore de son c\u00f4t\u00e9 les \u00e9quations it\u00e9ratives et leur mise en mouvement.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Math Art<\/strong> occupe une position diff\u00e9rente : celle d\u2019un laboratoire g\u00e9n\u00e9raliste o\u00f9 une r\u00e8gle peut \u00eatre manipul\u00e9e pour observer ce qu\u2019elle engendre.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-ecosysteme-recherche-1024x576.png\" alt=\"\u00c9cosyst\u00e8me de recherche reliant Math Art, Concerto \u00e0 l\u2019Infini, Fractal Studio et de futurs laboratoires.\" class=\"wp-image-93\" srcset=\"https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-ecosysteme-recherche-1024x576.png 1024w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-ecosysteme-recherche-300x169.png 300w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-ecosysteme-recherche-768x432.png 768w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-ecosysteme-recherche-1536x864.png 1536w, https:\/\/homelab.nvsd.ch\/wp-content\/uploads\/2026\/08\/math-art-ecosysteme-recherche.png 1600w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Cette organisation permet aussi de donner deux lectures au m\u00eame projet :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>dans le <strong>Homelab<\/strong>, il devient une r\u00e9f\u00e9rence technique documentant WebGL, Docker, Dockge, NPM, Tailscale et la d\u00e9marche de d\u00e9veloppement ;<\/li>\n\n\n\n<li>dans un contexte de <strong>formation ou d\u2019exploration<\/strong>, il peut servir \u00e0 aborder les nombres, les formes, l\u2019harmonie, les fractales, la perception et leurs relations.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">La technologie devient alors un support de recherche plut\u00f4t qu\u2019une finalit\u00e9.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">11. Prochaines exp\u00e9riences envisag\u00e9es<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La structure du laboratoire est pens\u00e9e pour accueillir plusieurs exp\u00e9riences, par exemple :<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Exp\u00e9rience 01 \u2014 Multiplication circulaire<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Le moteur actuel : <code>n<\/code>, <code>m<\/code>, enveloppes, persistance et export vid\u00e9o.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Exp\u00e9rience 02 \u2014 Nombres premiers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Mettre en \u00e9vidence les indices premiers, comparer les distributions et cr\u00e9er des transformations sp\u00e9cifiquement pilot\u00e9es par les nombres premiers.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Exp\u00e9rience 03 \u2014 Fibonacci et nombre d\u2019or<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Explorer les angles, spirales et r\u00e9partitions issues de la suite de Fibonacci et du nombre d\u2019or.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Exp\u00e9rience 04 \u2014 Harmoniques<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Relier rapports de fr\u00e9quences, intervalles musicaux et repr\u00e9sentations g\u00e9om\u00e9triques.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Exp\u00e9rience 05 \u2014 Fractales<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Faire communiquer les transformations circulaires avec des syst\u00e8mes it\u00e9ratifs et les outils de Fractal Studio.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Exp\u00e9rience 06 \u2014 S\u00e9quences g\u00e9n\u00e9ratives<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">D\u00e9finir des sc\u00e9narios de param\u00e8tres reproductibles et produire automatiquement des vid\u00e9os haute qualit\u00e9.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">L\u2019objectif n\u2019est donc pas de cr\u00e9er une collection de \u00ab beaux effets \u00bb, mais une <strong>biblioth\u00e8que d\u2019exp\u00e9riences document\u00e9es<\/strong>, chacune partant d\u2019une r\u00e8gle identifiable.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Math Art r\u00e9sume assez bien ce que j\u2019attends aujourd\u2019hui de mon Homelab.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le serveur n\u2019est pas uniquement un endroit o\u00f9 faire tourner des conteneurs. Il devient un espace dans lequel une id\u00e9e peut passer par plusieurs \u00e9tats :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>question\n  \u2193\nformule\n  \u2193\nprototype\n  \u2193\ninteraction\n  \u2193\napplication\n  \u2193\nconteneur\n  \u2193\nd\u00e9ploiement\n  \u2193\ndocumentation\n  \u2193\nnouvelle recherche<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ici, tout est parti de segments trac\u00e9s dans un cercle.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ils ont fait appara\u00eetre une courbe. Puis une animation. Puis des formes mouvantes. Puis une application WebGL. Et finalement un nouveau laboratoire qui pourra accueillir d\u2019autres relations entre nombres, g\u00e9om\u00e9trie, musique et fractales.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">C\u2019est probablement l\u00e0 que le projet devient le plus int\u00e9ressant : <strong>la formule ne constitue pas la conclusion de l\u2019exp\u00e9rience. Elle en est le point de d\u00e9part.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">R\u00e9f\u00e9rences<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Charte graphique \u2014 Concerto \u00e0 l\u2019Infini, Phase 1, version 1.0.<\/strong> Palette, typographies et principes d\u2019interface utilis\u00e9s comme base visuelle du laboratoire.<\/li>\n\n\n\n<li>MacTutor History of Mathematics, <em>Cardioid<\/em> \u2014 https:\/\/mathshistory.st-andrews.ac.uk\/Curves\/Cardioid\/<\/li>\n\n\n\n<li>MacTutor History of Mathematics, <em>Gauss: Disquisitiones Arithmeticae<\/em> \u2014 https:\/\/mathshistory.st-andrews.ac.uk\/Extras\/Gauss_Disquisitiones\/<\/li>\n\n\n\n<li>Three.js documentation, <code>BufferGeometry<\/code>, <code>LineSegments<\/code>, <code>WebGLRenderer<\/code> \u2014 https:\/\/threejs.org\/docs\/<\/li>\n\n\n\n<li>MDN Web Docs, <code>HTMLCanvasElement.captureStream()<\/code> \u2014 https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/HTMLCanvasElement\/captureStream<\/li>\n\n\n\n<li>MDN Web Docs, <code>MediaRecorder<\/code> \u2014 https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/MediaRecorder<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>Math Art, laboratoire WebGL de g\u00e9om\u00e9trie g\u00e9n\u00e9rative bas\u00e9 sur la multiplication modulo sur un cercle.<\/p>\n","protected":false},"author":1,"featured_media":95,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[39,42,40,3,4,38,43,41,7,26,37,36],"class_list":["post-90","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-non-classe","tag-geometrie-generative","tag-art-generatif","tag-cardioide","tag-docker","tag-dockge","tag-mathematiques","tag-mediarecorder","tag-modulo","tag-nginx-proxy-manager","tag-tailscale","tag-three-js","tag-webgl"],"_links":{"self":[{"href":"https:\/\/homelab.nvsd.ch\/index.php?rest_route=\/wp\/v2\/posts\/90","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/homelab.nvsd.ch\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/homelab.nvsd.ch\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/homelab.nvsd.ch\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/homelab.nvsd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=90"}],"version-history":[{"count":10,"href":"https:\/\/homelab.nvsd.ch\/index.php?rest_route=\/wp\/v2\/posts\/90\/revisions"}],"predecessor-version":[{"id":105,"href":"https:\/\/homelab.nvsd.ch\/index.php?rest_route=\/wp\/v2\/posts\/90\/revisions\/105"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/homelab.nvsd.ch\/index.php?rest_route=\/wp\/v2\/media\/95"}],"wp:attachment":[{"href":"https:\/\/homelab.nvsd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=90"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/homelab.nvsd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=90"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/homelab.nvsd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=90"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}