Fluryba

I'm made of white porcelain and vertical cork stripes.

Chedeflu

I'm made of vacuum formed carbon fibers, but my middle part is made of 3D printed fur.

Kycigy

My top is made of white marble, and my bottom is made of hand sculpted glass.

Flufahe

I'm mostly made of marble, but my right half is red.

Bafumi

I'm made of pink aluminium.

Psecludi

I'm made of sand.

Mikyjo

I'm made of turned wood, but my top half is burnt.

Clufuva

My bottom half is made of organically grown grass, but my top is made of gold.

Tymyju

I'm mostly made of molded concrete, but my top is made of burnt iron.

About

L’algorithme permet de mettre en place des systèmes chargés de résoudre des problèmes ; d’instruction en instruction, son exécution mécanique répond à l’intention qui justifie son existence.

Pourtant, la mise en réseau d’instructions fait naître des systèmes d’une complexité telle que de nouvelles propriétés émergent spontanément, échappant à l’intention première. L’algorithme devient alors créateur de nouvelles formes, de nouvelles idées.

En cela le designer doit de se confronter à cette émergence afin de susciter de nouvelles esthétiques.

Supershapes utilise l’algorithme pour appliquer à des objets les principes génétiques de la théorie de l’évolution. Décrits en termes de gênes et d’ADN, ces objets se reproduisent numériquement entre eux, témoignent de leur descendance, de mutations génétiques, et prennent au fil des générations leur autonomie formelle.

Supershapes favorise une émergence esthétique au travers d’un système algorithmique créatif.

La relation homme-algorithme devient une symbiose à la fois computationnelle et sensible.

Log

Les notes suivantes ont été prises durant la période de conception du projet, et témoignent des diverses observations concernant les résultats de recherche, les idées et les questionnements qui ont fait le projet.

Souvent prises sur le vif, elles ne sont pas exemptes de fautes.

Il peut y avoir deux stratégies pour la recombination ADN : soit on fait comme Shiffman en scindant les deux ADNs à une position aléatoire et en intervertissant les morceaux (segment swapping), soit pour chaque position de gène en intervertissant ou non (genes swapping).

Dans l'idée les deux fonctionnent sur le même principe (on obtient un croisement des deux ADN parents), mais dans la pratique la méthode de genes swapping permet de faire varier le phénotype de façon beaucoup plus organique (mais peut-être moins efficace dans une logique d'optimisation ?).
Cette méthode est implémentée à partir de GA_supershape :

public Dna recombinate(Dna partner){
  float[] child = new float[this.GENES.length];

  // int r = int(random(this.GENES.length)); // segment swapping
  for(int i=0; ir) child[i] = GENES[i]; // segment swapping
    if(random(1)>.5) child[i] = GENES[i]; // gene swapping
    else child[i] = partner.GENES[i];
  }
  return new Dna(child);
}

Remarque suite à GA_supershape : lorsque le phénotype d'un organisme est trop étendu (couleur + forme), l'utilisateur peut avoir tendance à sélectionner selon la couleur ou la forme, ce qui peut éventuellement atténuer la lisibilité du principe d'évolution ("j'ai une couleur qui me convient, ou une forme qui me convient, mais je n'arrive pas à faire évoluer les deux en même temps").

Peut-être que pour une version plus complexe d'évolution de formes 3D, il faudra passer par un framework totalement différent, qui permettrait de "valider" certains éléments. Une sorte de mix entre un growth et une évolution génétique ? De générations en générations, les formes deviendraient alors plus complexes, mais également plus grandes...

Ça pourrait s'implémenter avec un système de push/pop et de matrices de vecteurs : à chaque évolution, on push le squelette déjà évolué et on repart de ça.

cf des modèles de tree, des abstractions de ce genre.

La question qui va bientôt se poser est de savoir quel intérêt il y a à continuer au bout d'une dizaine de générations : en l'état, cela permet juste de "lisser" la forme en continuant à croiser des organismes de plus en proches.

Il faut amener de la complexité au fil des générations, mais comment ? La réflexion du 2016-04-21 ci-dessus peut être une piste de réponse.


Découverte de houndprojec : peut-être qu'une façon d'amener à une évolution intéressante au fil des générations serait de partir d'un cloud point très serré (mais à length fixe), et pour chaque point faire un point.add(vector), où vector est défini par les gènes. Ainsi les organismes se développent et s'ouvrent dans l'espace au fil des générations, devenant de plus en plus différents, mais gardant tout de même la base de leurs ancêtres.

La bibliothèque toxiclibs est définitivement l'outil qu'il faut utiliser, mais j'ai encore quelques difficultés à m'en servir : il faudra prévoir un ou deux petits sketches juste pour jouer avec les mesh/patch etc.


Le problème d'un système d'addition d'une génération à la suivante tel que décrit ci-dessus est le suivant : pour l'instant, même si un historique des populations précédentes est géré, il n'est pas possible depuis un Organism de remonter l'arborescence de ses ancêtres (l'historique est géré dans class.Population, et non dans class.Organism).

Ce qui signifie que même si la construction du phénotype d'un organisme se fait par addition (cf ci-dessus l'idée des additions de PVector), ça sera l'addition [état initial + ADN], et non [parent direct + ADN].

Il convient donc d'étendre la classe DNA pour y stocker l'ADN parent afin de pouvoir y accéder comme état initial pour la construction du prochain enfant (cette classe se passant bien d'un parent à un enfant). Reste à savoir comment gérer ça sémantiquement.

La technique consistant à appliquer une isosurface à un array de points (aussi appelés metaballs) semble intéressante pour générer des formes 3D à partir d'un paradigme particulaire.

Le problème cependant réside dans le principe de l'isosurface, qui n'est pas capable de conserver l'intégrité structurelle d'une forme (quand les particles sont trop éloignés, l'isosurface se contente de les "entourer", mais ne les rattache pas à l'ensemble). Il faudra donc peut-être implémenter un algo de convex hull 3D, ou une technique d'interpolations des particules afin de toujours les relier à l'ensemble du cloud.


A partir du commit d9dc945bc7b07e47105a79719ff5a4e808665f17, un organisme a connaissance de ses parents directs. De ce fait, il est possible par récursion de remonter l'arbre généalogique complet de n'importe quel organisme.

Il faudra juste tester que cette méthode ne soit pas trop lourde en terme de mémoire. Si c'est le cas, l'inféoder à Population.MAX_HISTORY_LENGTH.

Concernant les workflows éventuels pour la suite, une piste intéressante est celle du combo Processing/Unity3D. Processing gère toute la partie core du GA (population, ADNs, évolution, attribution des phénotypes, etc), et Unity gère l'UI (sélection, actions utilisateurs) ainsi que le rendu temps réel.

Une possibilité serait de faire tourner Unity sur un ordi et Processing sur un autre (ce qui permet d'avoir deux affichages différents). SSH/OSC, peut-être par l'intermédiaire d'un serveur sur raspberry (mais peut-être autant faire tourner le serveur sur le mac-processing).

Une piste possible pour ce qui concerne la partie production/matérialisation : générer procéduralement un guide de fabrication étape par étape pour qu'un humain puisse reproduire l'objet qui a été évolué algorithmiquement.

Ce guide de fabrication comporterait des images et des instructions textuelles, et pourrait ressembler à quelque chose du genre :

  • 1 - imprimer la partie #1
  • 2 - à l'aide d'une scie, couper selon la ligne médiane
  • 3 - imprimer la partie #2
  • 4 - à l'aide d'une perceuse, percer un trou de 6mm sur la marque A
  • 5 - avec de la colle Cyano Acrylate, coller #1 sur #2
  • 6 - enfoncer tige #3 dans trou A

Un tel guide d'assemblage serait intéressant avec des objets évolués génétiquement à partir d'un jeu d'.obj existant (thingiverse). Il faudrait alors travailler sur un modèle de représentation (en tree ?) capable de gérer l'évolution de tels objets.

Les organismes évolués avec cette technique pourrait être construits sur un squelette, avec des merges d'objets importés depuis la bibliothèque de .obj (thingiverse). On fait évoluer génétiquement le squelette, sur lequel on vient poser les parties d'.obj à certains noeuds.

Les gènes codent les directions que prennent les branches du squelette.

Voir Shiv Integer pour référence.

La mutation d'un gêne a tendance à entraîner une modification trop brutale dans le phénotype d'un organisme en comparaison à ces voisins. La méthode actuelle de mutation d'un gêne consiste à le remplacer par un nouveau (new_gene = random(0,1)), ce qui ne fait pas forcément sens.

Il conviendrait de faire muter un gêne en fonction de ce qu'il est déjà : new_gene = constrain(new_gene + random(-mutation_amp, +mutation_amp), 0, 1).

Cette stratégie devrait permettre d'amener des mutations beaucoup plus harmoniques, et peut être court-circuiter en définissant l'amplitude de mutation à 100% (ce qui équivaut à faire new_gene = random(0,1)).

Cette méthode est implémentée dans le framework à partir du commit a2ef38c3bb0ded95eaf409375f36fe89fb14c498.


Que se passe-t-il lorsque le score de fitness d'un organisme est défini par défaut à 0 (et non plus à 1) ?

Comme expliqué à Gaëtan, le score de fitness détermine le nombre d'occurence de l'organisme dans le pool génétique, ce qui permet d'avoir une sélection aléatoire pondérée.

Voir fitness proportionate selection pour le détail de la technique.

Si un organisme a un score égal à 0, alors il ne sera pas présent dans le pool génétique, et n'aura aucune chance d'être croisé. Il n'interviendra alors plus jamais dans l'évolution de la population.

A priori ce n'est pas souhaitable, mais dans certain cas cela peut permettre la suppression définitive d'un organisme (= sélection négative ?)


Essayer un GA_noise, basé sur GA_gradient : différents types de bruits (gaussian, perlin, simplex) avec une répartition basé sur les gradients de GA_gradient, et quelques paramètres de phénotype supplémentaire :

  • scaleX
  • scaleY
  • rotation


Commencement de réflexions autour de la piste inspirée de Shiv Integer. Pour l'instant l'idée serait donc de partir sur une bibliothèque de formes basiques, inspirées de jeux de constructions, qui permettraient de dénoter d'une esthétique assumée.

Intuitivement j'estime cette piste prometteuse, même si j'ai peur que la complexité des formes croisse trop vite d'une génération à la suivante.

Pourquoi des objets en 3D ? Parce que c'est complet (couleur, textures, formes), mais aussi parce que c'est quelque chose qui se présente dans les mêmes dimensions que notre corps : l'objet facilite la transmission.

Des objets emblématiques offrent des points de comparaison, d'appréhension.


Qu'est-ce qu'on veut montrer ?

Tout d'abord, le process en lui-même et la typologie de formes archetypales (une chaise GA, un vase GA, etc...), qui permettent de convoquer le dispositif dans un contexte de design, et d'amener la comparaison entre ce que le spectateur sait sensible, et ce qu'il découvre avec ce GA.

Dans un second temps, la puissance de la symbiose homme-algorigthme et l'émergence (la sérendipité et l'espace des possibles).


Quelle cible ? Designer/non designer ?


Comment croiser un objet qui présente plusieurs phénotypes très différents (forme + couleur + texture par exemple) ?

Une idée intéressante consiste à multiplier les inputs : un input par phénotyphe. Avoir une télécommande par phénotype.


Métaphore de sélection : dans la vraie vie, y a-t-il des interfaces, des scénarios de sélections ?

  • le panier/caddie de supermarché
  • mélange, mixeur
  • mise à l'échelle

Comment valider un objet ? Sélection > 100 % ?

Les ADN ont désormais une longueur dynamique : si un gêne est demandé alors que l'itérateur est au bout de l'ADN, un nouveau gêne aléatoire (random(0,1)) est automatiquement ajouté à la fin de l'ADN.

Cette solution permet d'avoir une plus grande souplesse dans la gestion du génotype, puisque la sélection d'un gêne est désormais implicite : c'est la construction du phénotype avec l'instruction DNA.nextGene() qui détermine quel gêne est utilisé.


Un problème qui peut survenir par contre dans le cas d'organismes avec des ADN de longueurs différentes, c'est de se retrouver avec des phénotypes aux index différents :

ADN organisme A ADN organisme B
G[1] = couleur G[1] = couleur
G[2] = couleur G[2] = couleur
G[3...10] = forme G[3...14] = forme
G[11] = taille G[15] = taille

Dans l'exemple ci-dessus, le croisement de l'organisme A et de l'organisme B est incohérent, puisque le gêne 11 encode pour A un phénotyphe de taille, et pour B un phénotype de forme.

Afin d'éviter ce problème, il convient de déclarer en dernier un phénotype codé sur une longueur variable de segment d'ADN (et donc se limiter à un seul phénotype à longueur de segment variable) :

ADN organisme A ADN organisme B
G[1] = couleur G[1] = couleur
G[2] = couleur G[2] = couleur
G[3] = taille G[3] = taille
G[4...10] = forme G[4...14] = forme

Une dernière solution pourrait être d'attribuer des longueurs de segments maximum, et de toujours se débrouiller pour coder les phénotypes à longueur de segment variables dans cet espace de contraintes :

ADN organisme A ADN organisme B
G[1] = couleur G[1] = couleur
G[2] = couleur G[2] = couleur
G[3...10] = forme G[3...14] = forme
G[11...14] = forme (vide)
G[15] = taille G[15] = taille


A propos du problème de "contraintes physiques" et d'intersection des modèles : actuellement, un point de contact définit uniquement une origine possible pour une pièce : à tel point de contact, je fais partir une nouvelle pièce avec un angle aléatoire.

Dans un espace polaire, cet angle est défini sur une sphère complète, et peut donc rentrer directement dans la pièce de laquelle est tiré le point de contact servant d'origine.
Une possible solution serait non plus de définir simplement un point de contact, mais plutôt une partie de sphère dont le centre définirait le point d'origine de la pièce, et la surface l'espace de possibles pour la génération de l'angle.
Ainsi, la contrainte d'angle d'une pièce par rapport à une autre se fait à la conception des pièces.

Par exemple, si je veux définir un point de contact sur une face d'un cube en étant certain qu'aucune pièce partant de ce point ne rentrera dans le cube, il me suffit de définir une demie-sphère sur la face de ce cube, plutôt qu'un simple point.

À voir comment cela peut se transcrire en terme de *.cpoints et en terme de vecteur.

A priori la génération de l'angle se ferait par l'algorightme suivant :

  1. définir l'origine au centre de la "contact_sphere"
  2. tirer au hasard un vertex de la surface de "contact_sphere"
  3. calculer les angles X, Y, Z entre l'origine et le vertex tiré en b. en utilisant PVector.angleBetween()

Pour le fichier *.cpoints, le parsing est tout de suite plus compliqué : il serait bien de réussir à s'épargner le centre des sphères, qui doit peut-être pouvoir se recalculer ? À voir du côté du format de fichier *.stl également, qui semble être plus procédural...

A la lecture des spécifications du format *.obj (lien), il faudra sans doute grouper chaque contact_sphere pour être capable de les distinguer les unes des autres côté code.

Enfin une petite note : cette solution permet de définir des espaces d'angles possibles, mais n'empêche pas pour autant l'intersection des pièces ; elle les limite, tout au plus.

Idée rapide pour faire des liens/élastiques/tendeurs/pieds triangulés à la Vitra :

  1. origin = random cpoint
  2. target = random cpoint
  3. dist = distance(origin, target)
  4. mesh.scale(dist)
  5. mesh.pointTowards(target)

Si pointTowards ne fonctionne pas ou si c'est la trop compliqué (sachant qu'il accepte un second paramètre optionnel pour spécifier la direction), revenir au système de rotation X/Y/Z en calculant angleBetween(origin,target).


Une autre solution pour cette histoire de "contraintes physiques" pourrait simplement être de sélectionner un point de contact, puis de faire la moyenne de tous les autres points de contacts et de calculer un angle qui s'en éloigne.

Cette méthode peut cependant ne pas du tout fonctionner avec des formes irrégulières ou complexes (un point de contact au fond d'un bol par exemple).