Interpolations linéaire et cosinus appliquées à une face du cube

a marqué ce sujet comme résolu.

Un truc m’échappe toujours dans ce que tu essayes de faire.

Ce n’est pas de l’interpolation au sens "approximer la valeur d’une variable à partir d’un nombre finie d’échantillon dans l’espace independent". Tu connais déjà parfaitement la forme de ce que tu veux (une arête, une face ou un cube) et tu construits plutôt un repère sur cette forme.

Dans tous les cas, tu peux décrire une droite de façon simple comme une combinaison linéaire de ses extrémités ou une face comme une combinaison bilinéaire de ses sommets ou encore un volume comme une combinaison trilinéaire de ses sommets. Typiquement, une arrête sera décrite comme $ax_1+bx_2$. Ensuite, si j’ai bien compris ce que tu essayes de faire, c’est de paramétriser $a$ et $b$ avec une fonction cosinus d’une variable indépendante quelconque. Tu peux toujours l’écrire, mais là où je coince c’est de comprendre pourquoi tu veux le faire. Fondamentalement, tu vas toujours d’écrire une arrête, pourquoi s’embêter avec un cosinus alors qu’il n’y en a pas besoin ?

Je trouve que ce dessin posté par Aloha décrit assez bien le besoin : cube avec interpolation non-linéaire

Pour une arête quelconque, on veut représenter cette arête par une dizaine de points, mais les points ne doivent pas être uniformémént répartis. Ils doivent être relativement denses aux 2 extrémités, et moins denses au centre.

Aloha sait représenter une arête.

Maintenant la difficulté est de représenter une face, avec la même convention.

En fait, il faut s’en tenir à représenter des segments :

  • on sait découper le segment AB avec N points (répartis de façon uniforme, ou non).
  • idem pour le segment DC
  • Soit E un des points sur AB, et soit F le point correspondant sur DC. EF est donc parallèle à AD.
  • pour représenter la face ABCD, il faut donc dessiner les N segments de type EF

On peut dessiner les lignes horizontales comme suggéré ici, ou les lignes verticales, ou les 2, ça donnera le même résultat, puisque chaque ligne est représentée par une suite de quelques points, et que la règle pour distribuer ces points est toujours la même.

+0 -0

Pour une arête quelconque, on veut représenter cette arête par une dizaine de points, mais les points ne doivent pas être uniformémént répartis. Ils doivent être relativement denses aux 2 extrémités, et moins denses au centre.

Je comprends toujours pas l’intérêt du "doivent" (ou plutôt, je vois très bien pourquoi on voudrait se placer sur ces points particuliers dans certains contextes, mais pas ici).

Par ailleurs, j’ai un doute sur la pertinence de la formulation en tant qu’interpolation. Si on prend un carré unitaire et qu’on place un repère calé sur deux côtés du carré, les coordonnées du point $i,j$ seront simplement $x_{ij} = \dfrac 12\left(1+\cos\left(\dfrac{i\pi}{n_x}\right)\right)$ et $y_{ij}=\dfrac 12\left(1+\cos\left(\dfrac{j\pi}{n_y}\right)\right)$ (avec $i,j$ variant entre $0$ et $n_x,n_y$). Généraliser à autant de dimension que l’on veut et/où appliquer une rotation et une homothétie pour se caler sur n’importe quel hypercube sera très simple.

Hello,

Ceci dit, je ne suis pas convaincu par cette méthode. Par exemple, pour un carré ABCD dans cet ordre (AC et BD sont les diagonales), les 2 jeux de coefficients (0.5, 0, 0.5, 0) et (0, 0.5, 0, 0.5) vont conduire au même point, et ça, ce n’est pas très propre. Je ne prend que cet exemple, mais tu vas avoir plein de points en doublon de ce type.

Donc même coté interpolation ’linéaire’, il y a des trucs pas nets dans ce que tu fais. Il faudrait déjà clarifier cela avant d’attaquer un autre type d’interpolation.

elegance

Certes le fait que je teste toutes les combinaisons des poids engendre une contre-performance en terme d’utilisation du CPU et de la RAM (car plusieurs points sont trouvés plusieurs fois). Mais ce n’est pas pour autant que ce n’est pas "très net" comme façon de faire. C’est même ce procédé qui a été suggéré par 2 profs de ma fac, et anecdotiquement je crois que vous êtes plusieurs personnes ici à avoir fait vos études dans cette même fac :p

Un truc m’échappe toujours dans ce que tu essayes de faire.

Ce n’est pas de l’interpolation au sens "approximer la valeur d’une variable à partir d’un nombre finie d’échantillon dans l’espace independent". Tu connais déjà parfaitement la forme de ce que tu veux (une arête, une face ou un cube) et tu construits plutôt un repère sur cette forme.

Dans tous les cas, tu peux décrire une droite de façon simple comme une combinaison linéaire de ses extrémités ou une face comme une combinaison bilinéaire de ses sommets ou encore un volume comme une combinaison trilinéaire de ses sommets. Typiquement, une arrête sera décrite comme $ax_1+bx_2$. Ensuite, si j’ai bien compris ce que tu essayes de faire, c’est de paramétriser $a$ et $b$ avec une fonction cosinus d’une variable indépendante quelconque. Tu peux toujours l’écrire, mais là où je coince c’est de comprendre pourquoi tu veux le faire. Fondamentalement, tu vas toujours d’écrire une arrête, pourquoi s’embêter avec un cosinus alors qu’il n’y en a pas besoin ?

adri1

Exact. Je tiens vraiment à utiliser des interpolations pour mettre en pratique ce que j’ai appris. Et je souhaite utiliser, notamment, l’interpolation cosinus par pure curiosité. Je voudrais réussir à faire ma face avec (si c’est possible) et constater les différences avec une interpolation linéaire.

Après comme l’a dit Elegance dans son dernier message, on pourrait imaginer que je veuille dessiner mes arêtes de manière à ce que les points soient denses aux extrémités et espacés vers le milieu, d’où l’importance qu’aurait le cosinus (et idem pour les faces). Même si ce n’est pas vrai, ça aurait pu être une motivation pour moi.

@adri1

Par ailleurs, j’ai un doute sur la pertinence de la formulation en tant qu’interpolation. Si on prend un carré unitaire et qu’on place un repère calé sur deux côtés du carré, les coordonnées du point i,j seront simplement xij=12(1+cos(iπnx)) et yij=12(1+cos(jπny)) (avec i,j variant entre 0 et nx,ny). Généraliser à autant de dimension que l’on veut et/où appliquer une rotation et une homothétie pour se caler sur n’importe quel hypercube sera très simple.

Oui mais je veux utiliser la technique d’interpolation, pour mettre en pratique ce que j’ai appris. :/

+0 -0

Pour dessiner cette interpolation d’une face du cube par programme, il faut d’abord dessiner à la main ce que tu veux. Cette étape me parait indispensable. En dessinant à la main, tu vas t’obliger à mettre des phrases concrètes sur ton besoin. Et tu devrais logiquement arriver à ce que j’écrivais hier à 11h14.

C’est pas très compliqué de passer de ma formulation à une formulation en interpolation. Déroule les calculs sur une feuille pour un cas particulier, et tu obtiendras un truc qui va ressembler à de l’interpolation. Fondamentalement, il suffit juste de définir les coefficients $a$ et $b$ correctement. En soit, ce n’est d’ailleurs pas un exercice particulièrement intéressant…

+0 -0

Attendez, pour vous mon interpolation cosinus est fausse entre 4 points ? Pourquoi ? D’ailleurs, comment se fait-il que mon interpolation entre 4 points soit bonne lorsqu’elle est linéaire ? C’est donc ma définition de mon interpolation cosinus qui est erronée ? (en d’autres termes : $w′A+(1−w′)B$, avec $w′=(1−cos(w∗π))∗0.5$ serait fausse ?)

Car là d’après vos deux dernières réponses, il y a bien un truc qui cloche avec ce que j’ai fait, et concrètement c’est forcément cette définition puisque pour l’interpolation linéaire tout fonctionne, quel que soit le nombre de points, et que mon interpolation cosinus marche uniquement à 2 points.

Ou alors peut-être que vous me proposez une solution alternative à mon interpolation cosinus mais je voudrais éviter cela :/

Ou encore vous proposez une version différente de mon interpolation cosinus mais là c’est chelou car ça signifierait qu’il existe plusieurs définitions d’une telle interpolation :o

Dites-donc, ça fait beaucoup de possibilités :p

+0 -0

Tu pars dans tous les sens, là. Ton interpolation est bonne en une dimension. Si tu écris $x_i=\dfrac 12\left(1+\cos(i\pi/n)\right)$ et que tu scales ça sur un segment quelconque tu vas retrouver la même chose que ce que tu as écrit.

Je sais pas ce que tu as fait pour une face, mais vue la tronche de tes graphiques, ça a l’air faux.

Encore une fois, prends le temps de dérouler à la main les calculs pour un carré quelconque à partir de ce que j’ai écrit en 2D.

Comme adri1, je pense que tu vas dans tous les sens.

Tu veux dessiner un truc qui est complexe sur le plan mathématique (interpolation cosinus sur un cube). Dans ton premier message, tu as dessiné 2 trucs :

  • Ton résultat, qui est buggé
  • une face d’un cube obtenue par interpolation linéaire.

Mon problème, c’est que selon moi, même ce 2ème dessin est buggé. Il faut avancer par étapes, et il faut que chaque étape soit testée/validée. Dans le cas d’une face de cube par interpolation linéaire, tes différents points devraient dessiner un damier, avec des carrés régulièrement disposés. Ce n’est pas ce que je vois dans le dessin.

Hello, on m’a expliqué que ça venait de deux choses :

  1. Imprécision flottante au niveau des tests vérifiant si la somme des poids d’une interpolation donnée est > 1 mais ça vous ne pouviez pas le savoir puisque je n’ai pas montré le code

  2. Le fait que je faisais les tests mentionnés ci-dessus sur les poids alors qu’ils n’étaient pas passés dans la fonction qui leur applique le cosinus (étourderie de ma part)

Du coup là je pense avoir obtenu un résultat correct. Je vous montrerai le résultat après quelque réflexion supplémentaire.

D’ailleurs je vais relire vos réponses et faire ce que vous m’avez dit, pour comparer avec mon programme actuellement écrit et le résultat obtenu.

Imprécision flottante au niveau des tests vérifiant si la somme des poids d’une interpolation donnée est > 1 mais ça vous ne pouviez pas le savoir puisque je n’ai pas montré le code

Il y a deux choses qui m’étonnent là-dedans. D’une part, que les imprécisions flottantes sur les poids puissent être amplifiées au point d’être visibles sur un graphique, et d’autre part que tu ais besoin de tester que la somme des poids de ton interpolation fait 1. Si elle est bien construite, il n’y a aucune raison que la somme soit autre chose que 1.

Hello adri1, j’ai mangé donc pas encore eu le temps de poursuivre mes tests mais je passe ici et te réponds avant d’être de nouveau occupé. Je repasserai ce soir pour vous montrer le résultat etc.

En fait il y avait deux problèmes :

  1. Le fait que les tests étaient réalisés sur des points interpolés non-transformés avec la fonction cosinus que je vous ai montré engendrait tous ces points chelous qui partaient vers la gauche

  2. Une fois ce problème résolu (en transformant les points interpolés avant l’exécution des tests), j’ai obtenu une face avec les 4 arêtes + les 2 diagonales et c’est tout. C’est là qu’est intervenue l’imprécision des tests (comparaison d’un double avec 1.0). Pour corriger ça, j’ai désormais écrit un test avec une marge du type : abs(1.0-double) < 0.01 , on notera que c’est pas totalement fiable mais bon tant pis.

Depuis, ma face est correctement remplie avec l’interpolation linéaire comme cosinus.

Mais on m’a encore dit que c’était chelou d’utiliser une interpolation cosinus pour interpoler entre 4 points (en l’occurrence les 4 sommets de ma face). Je sais bien que l’interpolation linéaire est plus adaptée (les points interpolés étant régulièrement distribués selon une lattice/un damier comme @elegance l’a dit précédemment), mais pourquoi se refuser d’utiliser l’interpolation cosinus si on en a envie ? Après tout, hormis ce problème d’imprécision due à la virgule "des cosinus", c’est tout à fait réalisable (d’ailleurs je l’ai fait et j’ai hâte de vous montrer).

Edit

J’ai oublié de répondre à ta première question : pourquoi faire le test de SUM(weights) = 1 (ou sum(weights)-1.0 < 0.01) ? Parce que ma façon de faire c’est de tester toute les combinaisons possibles en itérant récursivement sur les poids, comme je l’ai dit page 1 je crois, donc y a des interpolations trouvées qui sont fausses (et je ne parle même pas des points interpolés présents en doublons) :p (certes c’est sale de faire comme ça mais osef)

Je vous enverrai un .JAR ce soir en plus du code (extrait adéquat), de screenshots et d’explications. Comme ça vous pourrez utiliser mon projecteur 3D fait à la main, tourner dans tous les sens mon cube et voir ces fameuses interpolations de faces linéaire + cosinus :D

Re-édit

J’ai vérifié, et j’obtiens bien des points très denses aux 4 vertices, un petit peu de moins en moins en se rapprochant du centre de la face, et on voit un ou deux espèces de cercles au fur et à mesure qu’on se rapproche du ventre, mais y a pas que des cercles non plus. Hâte de vous montrer ça :D C’est obligé que mon interpolation cosinus est réussie !!! C’est juste vraiment obligé

+0 -0

J’ai oublié de répondre à ta première question : pourquoi faire le test de SUM(weights) = 1 (ou sum(weights)-1.0 < 0.01) ? Parce que ma façon de faire c’est de tester toute les combinaisons possibles en itérant récursivement sur les poids, comme je l’ai dit page 1 je crois, donc y a des interpolations trouvées qui sont fausses (et je ne parle même pas des points interpolés présents en doublons) :p (certes c’est sale de faire comme ça mais osef)

Euh… What? o_O Une interpolation, ça se calcule directement. Si ton but est de comprendre les interpolations, il me semblerait plus utile d’écrire un code d’interpolation, pas un code de recherche dans l’espace des combinaisons linéaires…

J’ai oublié de répondre à ta première question : pourquoi faire le test de SUM(weights) = 1 (ou sum(weights)-1.0 < 0.01) ? Parce que ma façon de faire c’est de tester toute les combinaisons possibles en itérant récursivement sur les poids, comme je l’ai dit page 1 je crois, donc y a des interpolations trouvées qui sont fausses (et je ne parle même pas des points interpolés présents en doublons) :p (certes c’est sale de faire comme ça mais osef)

Euh… What? o_O Une interpolation, ça se calcule directement. Si ton but est de comprendre les interpolations, il me semblerait plus utile d’écrire un code d’interpolation, pas un code de recherche dans l’espace des combinaisons linéaires…

adri1

Bein j’avais dit que je voulais interpoler entre n points de k coordonnées à une prof, et je voulais savoir comment faire. Elle m’avait alors répondu que ça dépendait de ce que je souhaitais mettre en place, alors quand je lui ai précisé que c’était pour interpoler entre 4 sommets d’une face en lui proposant de tester toutes les combinaisons possibles de poids, elle a dit que ça lui semblait OK :(

Plus tard je ferai un algo d’interpolation alors.

En attendant, voici les résultats d’interpolations linéaires et cosinus : https://imgur.com/a/oxSiq7l (il serait pratique d’implémenter un visualisateur imgur dites-donc :p ).

En attendant, voici les résultats d’interpolations linéaires et cosinus : https://imgur.com/a/oxSiq7l (il serait pratique d’implémenter un visualisateur imgur dites-donc :p ).

C’est bizarre comme résultat, les points ne sont pas organisés en lignes et colonnes. Tant qu’on voit pas le code pour comprendre ce que ton code fait, ça va être difficile de savoir si c’est juste ou faux, mais vu qu’il y a des amas de points disposés de façon non régulière, je parierais sur le fait qu’il y a encore des erreurs.

Voilà ce que j’obtiens

cosgrid

avec ce code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/usr/bin/env python3

import numpy as np
import matplotlib.pyplot as plt

n_x = 20
n_y = 20

xs = (1 + np.cos(np.arange(n_x + 1) * np.pi / n_x)) / 2
ys = (1 + np.cos(np.arange(n_y + 1) * np.pi / n_y)) / 2

for y in ys:
    plt.plot(xs, y*np.ones(n_x + 1), 'o', ms=1, color='k')
plt.axis('equal')
plt.savefig('cosgrid.png')

Alors bien sûr, tu pourrais avoir une définition un peu différente de ce que entends pas interpolation cosinus qui pourrait changer le résultat, mais comme tu n’as jamais clarifié ce que tu essayais de faire mathématiquement, il est difficile de se faire une idée.

Ici, tu fais une interpolation sur les x, puis une interpolation sur les y, puis tu croises les 2, pour faire une grille.

Aloha souhaite procéder autrement, il souhaite faire des interpolations à partir des 4 points : M = (aA+bB+cC+cD)/(a+b+c+d)

Quand les 4 points sont disposés en trapèze ou en parallélogramme, les 2 méthodes donnent la même chose, donc effectivement, Aloha devrait trouver un dessin proche du tien.

Aloha souhaite procéder autrement, il souhaite faire des interpolations à partir des 4 points: M = (aA+bB+cC+cD)/(a+b+c+d)

Je pense que tu te rends bien compte que réécrire mes expressions sour cette forme est simplissime… :-° Pour adapter ça à un quadrilatère quelconque, il faut bien évidemment déformer le résultat obtenu.

Est-ce qu’un dépôt Git accessible via Github, avec code commenté + docs° + explication + un exécutable JAR vous seraient utiles pour m’aider du coup ? Je peux peut-être faire ça ce soir (au plus tard demain fin d’aprèm).

Il faut aussi que je jette de nouveau un coup d’oeil à vos réponses page 1 car e.g. @adri1 m’a dit de refaire certains calculs pour comprendre des trucs.

Re tout le monde,

Bon je n’aurais pas le temps de créer un Github ce soir , je poste le code ici (dans un premier temps)^^!

Fonction qui teste toutes les combinaisons de poids

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
  def computeAllPossibleInterpolatedPoints(step : Double, points : Seq[Seq[Double]], transform: (Double) => Double) : Seq[Seq[Double]] = {
    var returned_object : Seq[Seq[Double]] = Seq.empty[Seq[Double]]
    recursiveInterpolation(0, Seq.empty[Double])

    def recursiveInterpolation(current_weight_id : Int, building_weights : Seq[Double]) : Unit = {

      (.0 to 1.0 by step).foreach(current_step => {
        if (current_weight_id < points.size - 1) {
          recursiveInterpolation(current_weight_id + 1, building_weights :+ current_step)
        } else {
          val found_solution = (building_weights :+ current_step).map(transform)
          if(Math.abs(found_solution.sum - 1.0) < 0.01) {
            returned_object = returned_object :+ interpolation(found_solution, points)
          }
        }
      })
    }

    returned_object
  }

Comme vous le savez déjà, j’utilise la récursivité. Dans chaque appel récursif, j’itère de 0 à 1 sur chaque poids. Dès qu’une itération est exécutée, l’appel récursif est lancé. Au final ça signifie que je teste bien toutes les combinaisons possibles.

On a déjà dit que c’était gourmand en CPU et RAM et que des solutions alternatives étaient mieux en tout cas pour le CPU et probablement pour la RAM, on a aussi dit que certains points interpolés sont présents en doublons. Mais ces problèmes sont secondaires et ne concernent pas ma question initiale (qui est de savoir pourquoi je n’obtiens pas une jolie face interpolée avec cosinus).

if(Math.abs(found_solution.sum - 1.0) < 0.01) { permet de savoir si j’ai trouvé une combinaison valide (i.e. : dont la somme des poids vaut 1). Attention, ce test est mieux que de tester si la somme est égale à 1 par rapport à l’imprécision due aux chiffres après la virgule, pour l’interpolation cosinus.

transform peut être linear ou cosine.

Fonctions linear et cosine

1
2
3
4
5
6
7
  def linear(weight : Double) : Double = {
    weight
  }

  def cosine(weight : Double) : Double = {
    (1 - Math.cos(weight * Math.PI)) * 0.5
  }

Fonction d’interpolation entre n points de k coordonnées

Cette fonction est utilisée par exemple par la fonction computeAllPossibleInterpolatedPoints.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
  def interpolation(weights: Seq[Double], points: Seq[Seq[Double]], transform: (Double) => Double = null) : Seq[Double] = {
    var transformed_weights = weights
    if(transform != null) {
      transformed_weights = weights.map(transform)
    }
    if(Math.abs(transformed_weights.sum - 1.0) >= 0.01) {
      println("ERROR : `SUM(weights) != 1`. Returning `null`.")
      return null
    }
    if(transformed_weights.exists(weight => BigDecimal(weight).setScale(5, BigDecimal.RoundingMode.HALF_UP).toDouble < 0)
      ||
      transformed_weights.exists(weight => BigDecimal(weight).setScale(5, BigDecimal.RoundingMode.HALF_UP).toDouble > 1)) {
      println("ERROR : `EXISTS(weight) / weight < -1 OR weight > 1`. Returning `null`.")
      return null
    }

    transformed_weights.zip(points).map(
      weight_point => weight_point._2.map(coordinate => weight_point._1 * coordinate)
    ).reduce((point_a : Seq[Double], point_b : Seq[Double]) => point_a.zip(point_b).map(coordinate_points => coordinate_points._1 + coordinate_points._2))
  }
  1. Remarquez que lorsqu’elle est appelée par computeAllPossibleInterpolatedPoints , aucun transform ne lui est passée en paramètre (regardez l’appel dans le code de computeAllPossibleInterpolatedPoints), et c’est bien logique : computeAllPossibleInterpolatedPoints applique déjà dans son code une transformation (soit cosine , soit linear).

  2. On retrouve le test de la somme = 1

  3. On trouve un autre test : tout poids doit être compris entre 0 et 1 tous deux inclus

  4. J’ai écrit une petite succession de map et de reduce qu’on aime bien en Scala. Ce que ça fait, c’est tout bête : on multiplie chaque coordonnée de chaque point par le poids associé au point (l’ensemble des points est en effet en bijection avec l’ensemble des poids au niveau des paramètres, enfin on part de ce principe… à l’utilisateur de cette fonction de s’en assurer). Le point résultant de la somme de ces multiplications poids-coordonnées_de_point est retourné.

Exécutable

Vous pouvez tester ces fonctions avec ceci :

val more_points_0 : Seq[Seq[Int]] = interpolator.computeAllPossibleInterpolatedPoints(0.05, Seq(Seq(22, 22, 22, 1), Seq(33, 33, 33, 1), Seq(1, 1, 1, 1), Seq(52, 52, 52, 1)), interpolator.linear).map(_.map(_.intValue()))

Je viens de choisir ces coordonnées de manière totalement aléatoire parce qu’il faut que j’y aille ! :)

Changez interpolator.linear par interpolator.cosine pour changer d’interpolation. interpolator est une instance de la classe Interpolator qui contient les fonctions ci-dessus.

Merci d’avance !

Eh oui ! Merci d’avance, encore, pour votre aide ! Je vous filerai dès que possible (demain matin et/ou après-midi) un exécutable (projecteur 3D + interpolations de face, l’objectif à terme étant d’utiliser du bruit pour donner une couleur/du volume à chaque face du cube).

Je compte faire un dépôt git auquel on pourra accéder via github.

+0 -0

J’ai pas de JVM (et je n’ai aucune intention de l’installer) donc je n’ai pas pu bricoler avec ton algo.

Après une première lecture, il n’y a pas d’erreur particulière qui me saute aux yeux. Par contre, je me dis que l’erreur que tu autorises sur la somme des poids est en fait plutôt grosse et pourrait expliquer la présence de "tas" de points à certains endroits. Qu’est-ce qui se passe si tu remplaces le test $|1-\sum w|<0.01$ par $|1-\sum w|<10^{-6}$ par exemple ?

Autre remarque, plutôt que d’itérer sur tous les poids possibles de $0$ à $1$, tu pourrais itérer de $0$ à $1-\sum w$ et le dernier poids pourrait être directement mis à $1-\sum w$. Ça t’éviterait de passer par les cas $\sum w>1$ par construction. Ce serait plus efficace d’un point de vue algorithmique, et ça t’éviterait d’avoir à vérifier que $\sum w\approx 1$ puisque tu serais sûr que $\sum w=1$ (tu t’affranchis même du problème d’arrondi au passage).

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte