Registre Ontologique

Comme annoncé en petit comité tout récemment, j’entreprend de recommencer le projet Folk·o·pedia. Ce sujet n’a de difficulté que de rassembler la connaissance, toute la connaissance folklorique… Et pour ce faire, je souhaite utiliser, me baser sur, ou m’inspirer du principe d’ontologie, de modèle ontologique et de triplet RDF, comme dit en 2019.

Cette année là, un modèle DB avait été conçus, correspondant et faisable, et un système a même été développé, tel un driver pour la suite du développement. Mais malheureusement ça n’aura pas été plus loin. Qu’à cela ne tienne, nous revoilà plus beau, plus malin, plus fort, plus motivé que jamais.

On récapépètte depuis le zébu. Suite à de nouvelles rencontres et d’événements, le projet évolue un chouilla plus avec l’ajout d’objets, même si la notion existait déjà, l’idée est plus aboutie (collection/fonds et métadonnées). De plus on reprend la base même des données, de leur structuration, de leur relation ou fonctionnement. On reprend tout.

Brainstorming SPO, problématiques et performance.

SPO

Tout part de là en fait. Ce fameux triplet, que l’on peut comparer à une base de registre (cf Windows par exemple). Un quelque chose qui a un attribut qui a une valeur (ou plusieurs). Soit le Sujet – Prédicat – Objet. Et il faut savoir que O est soit une valeur finie (un prénom, une date, un nombre, …), soit un lien vers un S.

Donc on peut imaginer qu’une entité me représentant (S) qualifie mon surnom (P) avec la valeur « Killan » (O), mais il y a un second surnom (P) de valeur « Pôlebreak » (O). Donc un ensemble de valeurs pour un même prédicat.

Là où certain se chiffonnent déjà le caberlot, c’est comment savoir quel prédicat est le premier, le principal, etc. Et la réponse est simple : on ne le sait pas.

Historisation

Un autre aspect des données sur un tel site est la vie de la valeur elle-même. Si un document a été révisé il y aura des versions, pourtant il s’agit de la même entité, du même sujet. Si une personne change de prénom, il ne s’agit pas d’ajouter un nouveau prénom mais de remplacer sa valeur avec l’historisation de « l’avant ». Ainsi, si on demande à Folkopedia de nous donner l’état des lieux en 2006, l’on pourra constater que des groupes existent ou ne sont pas encore là, que certains avaient un autre nom, un autre lieu de rassemblement, l’état du comité de gestion (qui et à quel rôle), etc. L’historisation pourra ainsi permettre de voyager dans le temps.

D’un autre côté, l’historisation a également pour but de contrôler les modifications faites par les contributeurs, pour revenir en arrière en cas d’erreur par exemple (principe d’audit et de contrôle par les pairs).

On peut également définir qu’une information n’est pas pertinente à historiser, tel un statut temporaire, la participation à un événement, une demande de contact, …

Visibilité

Un aspect important également avec toutes ces données, est de savoir ce que l’on partage et à qui. Cela peut-être le partage de son email pour être joignable, l’affichage de son deuxième prénom, ou pour un groupe, de donner accès à un document, ce dernier pourrait n’être disponible que pour les membres. Ainsi quelque soit la donnée, celle-ci bénéficie d’une visibilité.

Cette visibilité peut être parmi une liste proposée (publique, privée, membre inscrit, mes amis, les amis de mes amis, …) ou définie manuellement : visible par tel ou tel personne, tel ou tel groupe.

Métadonnées

Suite aux points mentionnés ci avant, de comment gérer les valeurs multiples pour un même prédicat, ou d’historisation, et en ajoutant la notion d’audit et de visibilité, la question du comment est bien légitime.

Une première possibilité en suivant le modèle SPO, est que la valeur (O) est elle-même un (S) contenant la valeur elle-même, ses métadonnées, c’est-à-dire la visibilité et son audit (qui a modifié et quand). Le soucis sera au niveau de la performance, pour chaque valeur (O) il faudra chercher son S correspondant et les prédicats résultants.

Si nous avons 6176 inscrits en ce moment, que l’on imagine une centaine de paramètres à renseigner et que chacun ai été révisé 3 fois, en tenant compte du modèle de métadonnées, nous sommes à 7.4M de lignes encodées (max.), sans ajouter quoique ce soit de spécial. Soit, si je souhaite consulter le profile d’une personne j’aurais un maximum de 100*4 requêtes individuelles (pour chaque O cherche le S et ses valeurs de prédicat). Et ce à chaque consultation. Actuellement, sur Comitards, il s’agit d’un lot de 5-10 requêtes.

Une seconde possibilité est de détourner le modèle S¨PO pour aplatir le besoin fixe (audit, visibilité), et tant qu’on y est, vu qu’on parle de table de données, O est un soucis, il ne peut en une même colonne correspondre à la fois à une valeur et à une clé étrangère vers S, il nous faut donc 2 O nullable. Nous aurions donc quelque chose du style :

S, P, O1, O2/valeur, qui, quand, visibilité

O1 est un lien vers un S potentiel, O2 est la valeur (string) et le reste tel qu’attendu. Donc nous réduisons la complexité de 7.4M à 1.8M en simplifiant le principe d’historisation.

Reste que nous n’avons pas résolu la question de quel surnom est avant l’autre. Métadonnée en plus ? Une colonne ordre dans la solution #2 ?

S, P, SL?, valeur, qui, quand, visibilité, ordre

O1 renommé SL (S link, l’enfant de ce prédicat, ou laisser O, à voir) en optionnel (?).

Multiple façons d’historiser

Si on prend une valeur finie tel qu’un prénom, la solution précédente fonctionne très bien.

S1, surnom, lien:h1, pseudoActuel, S1, 2008-01-01, publique, 1
h1, valeur, null, pseudoAvant, S1, 2006-03-04, privé, 1

S1, surnom, null, secondPseudo, S1, 2010-08-12, publique, 2

On peut également imaginer que la partie historisée se trouve dans une autre table pour les besoins de performances.

Mais quand est-il si on parle d’un document, ayant différents auteurs dans le temps, signataires, contributeurs, versions, etc. ? Imaginez un exemple avec un groupe folkorique quelconque (GFQ) ayant un document :

GFQ, document, GFQ-doc#1, null, GFQ, 2020-11-01, restreint, 1

GFQ-doc#1, url, null, "url du document sur serveur", GFQ, 2020-11-01, hérité, null
GFQ-doc#1, titre, null, "titre du document", GFQ, 2020-11-01, hérité, null
GFQ-doc#1, signataire, S1, null, GFQ, 2020-11-01, hérité, 1
GFQ-doc#1, signataire, S2, null, GFQ, 2020-11-01, hérité, 2
GFQ-doc#1, signataire, S3, null, GFQ, 2020-11-01, hérité, 3
GFQ-doc#1, auteur, S1, null, GFQ, 2020-11-01, hérité, 1
GFQ-doc#1, datePublication, null, "2020-10-05", GFQ, 2020-11-01, hérité, 1

Notez que « restreint » devrait à son tour être une clef vers un S (type connu ou personnalisé). L’url du document à un ordre à null car non pertinent et une visibilité héritée (à voir, ça reste éditable). La date de publication est un string qui sera interprété par la connaissance du modèle, donc permet la flexibilité.

Maintenant que se passe-t-il si le document change ? On ne peut pas tout simplement changer chaque valeur individuellement, la notion de groupe est importante. Bien que s’il s’agit d’une typo dans le titre cela fonctionne. Faut-il encore qu’il y ai un besoin d’historiser le changement.

Ce qu’il va nous falloir, c’est que pour un même S:GFQ-doc#1, il nous faudra une version de plus. On pourrait encapsuler GFQ-doc#1 dans une coquille permettant de regrouper les versions du même document. Ou établir un lien entre les versions comme une chaine entre document, mais là encore côté performance ce n’est pas idéal.

Une autre solution encore est de considérer l’ajout d’un document en le qualifiant spécifiquement de version antérieur d’un autre. Il faudra faire le tri ceci dit.

Évidemment tout ceci se discute, c’est un point de vue et les contributeurs devront se poser la question et le système les aidera dans leur démarche. Il faudra bien sur confronter d’avantages de cas de figure et amender le principe. Et nous n’avons pas parlé des informations additionnelles que chaque élément peut porter (prédicats).

Pour ma part je pense que l’encapsulation est une idée intéressante au seul moment ou une nouvelle version arrive. Ainsi, de manière polymorphique, un document est son information directe ou indirecte via l’encapsulation. Ce sera au système de gérer.

Modèle et extensions

Nous en parlions plus haut, au niveau des types de données sur base d’un stockage en string. Les utilisateurs ne vont pas « coder » leurs données, c’est une interface qui assistera les utilisateurs en fonction de leur choix et les guidera dans le méandres des possibles.

Pour ce faire, NOUS devons avoir une vue claire de ces possibles, ce que chaque entité peut avoir comme prédicat, autoriser l’ajout de prédicat avec validation collégiale, faire vivre le modèle et ses usages.

Prenons un exemple avec un couvre-chef, il aura un nom. Si nous l’étendons et définissons la penne, celle-ci aura d’avantages de paramètres, tel que la longueur de la visière, la couleur du feutre, son origine, sa date. Mais là encore on peut parler d’un comité de baptême décernant la penne et ayant sa version dudit chapeau, avec son liseret, ses pins de base, le folklore attaché à la cérémonie, etc. Enfin, l’utilisateur du même comité de baptême, déclarera posséder un tel couvre-chef et sa penne aura sa propre vie, respectant ou non les valeurs du comité.

Ainsi par cet exemple on observe le principe d’extension du modèle. Le modèle permet de définir un objet, qui, quand il sera instancié, permettra à l’utilisateur d’avoir des informations à remplir. Évidemment il faut aussi imaginer la variance possible dans le temps, avant on faisait ça, mais aujourd’hui… etc. De même que le modèle peut être à respecter obligatoirement (couleur, longueur), alors que certains paramètres peuvent varier, comme une suggestion, on met le pins ici mais tu l’as mis là car ton parrain fait autrement etc. .

On aura donc besoin d’un système permettant la définition des modèles et ainsi définir le type de prédicat, leur pluralité, leur valeur par défaut, le côté obligatoire. tel un patron de vêtement, une recette de cuisine, vous gardez votre liberté.

Dans l’ensemble d’une entité (S), un prédicat pourra alors nous dire de quel modèle l’entité est issu. Ce qui permettra de construire le formulaire de saisie, d’apporter l’assistance à l’utilisateur, de contrôler la mécanique et fonctionnement, voire même de définir la validation par les pairs.

Autogestion

Vous avez donc compris la taille du possible, la quantité d’informations potentielles et nous n’avons parlé ici que de quelques cas. Seul je ne saurais, ni ne veux, gérer ça, c’est à la communauté et aux contributeurs motivés que cela revient. Donc il faut définir, à l’image de StackOverflow, une mécanisme adéquate. C’est à dire, un moyen de mesurer l’implication ET la justesse de participation d’un utilisateur pour lui donner tel ou tel droit.

Évidemment vos données vous regarde, mais vos apports seront confirmés ou infirmés, un jugement par vos pairs pour donner crédit ou informer d’une erreur. L’idée restant que l’information proposée sur le site doit être de la meilleure qualité qu’il soit (comme Wikipedia).

De plus, vos titres de guindaille seront utilisés pour savoir à qui demander une validation d’accès à un groupe, ainsi il faudra que X personnes valide et ainsi le titre est validé et la personne à alors accès aux données du groupe et peut s’enorgueillir d’en faire partie. Comme pour les tags sur les contributions, certains seront officiels mais les nouvelles propositions feront l’objet de vote.

Ainsi vous pouvez imaginer, qu’au travers d’une structure SPO, tout ceci, et plus encore, seront encodés. Il va de soit qu’une recherche sur la performance et un détournement de modèle quant à son implémentation seront nécessaire.

Suppression et historique

Un soucis se pose quand on parle de suppression dans un contexte historisé. Doit-on réellement supprimer la donnée ou juste rompre l’accès ? Mais si l’entité est liée et à son tour supprimée, comment gérer l’ensemble fantomatique ? Empêcher une suppression peut être une solution, si après un délai et/ou si lié à d’autre.

S, P, SL?, valeur, qui, quand, visibilité, ordre, supprimé, supprimé par qui

On pourrait ajouter alors cette information pour connaître sa disponibilité, mais le plus évident en terme de performance sera de déplacer le contenu dans une table dédiée. Alors, suite à un acte spécifique une liaisons sera établie pour aller les chercher, par exemple restaurer une donnée.

Mise en cache

De but en blanc, l’idée est simple, au lieu de faire des centaines de requêtes sur un sujet à chaque consultation, on stock le résultat en mémoire pour aller le chercher et rendre l’opération plus légère et rapide. Mais, nous avons une notion de visibilité, ce qui nous oblige à en tenir compte et à ne stocker que la partie publique, et peut-être à mettre en cache des segments par visibilité.

Publier le schéma

À l’image de schema.org, il nous faudra également un aplatissement du modèle, à des fins de documentation, de contrôle et d’échange.

Dans cette optique et en nous basant sur les données existante de Comitards, nous pourrons créer des modèles et une moulinette pour migrer l’information, ensuite confronter les cas d’usages et les extensions.

Conclusion

Comme vous le voyez, il ne s’agit pas d’un « yapluka », mais d’une procédure empirique qui se confronte à chaque étape à des questions de « comment » on va gérer telle ou telle situation. Cependant, la direction semble claire, les impératifs sont définit, la notion de performance en cible également.

La prochaine étape sera de modéliser cette base de données et d’y décrire le principe de modèle, ce qui pourra être testé par des cas d’usages.

En comparaison du modèle de DB de 2019, plus éclaté, il reste valide, et c’est peut-être vers quoi on ira une fois que l’on aura les données plus en mains avec cette recherche de performance. Ou c’est via la notion de cache que le salut se fera.

Radiation de pixels, quand la 2d est 3D et vice versa

Suite des articles visant à améliorer l’illumination de la scène, après une couleur diffuse correctement éclairée grâce aux normales et aux spéculaires, il restait, invariablement, que l’image entière était éclairée uniformément concernant l’intensité, pour une même direction de normales.

Mise à plat de la scène pour valider le dégradé de l’intensité.

Les calculs et les informations transmises étaient insuffisantes pour calculer la valeur de chaque pixel et, surtout, rien ne nous permet de savoir où le pixel se situe dans l’espace, nous permettant de faire varier son illumination sur base d’une distance altérée par sa position.

Je ne suis pas 3D

N’oubliez pas que ce que vous voyez n’est pas de la 3D, mais une succession d’images plates représentant « un quelque chose » en 3D, et c’est cet enchevêtrement savamment orchestré qui vous dit que l’image est 3D. Tout n’est qu’illusion Mr. Anderson ! Et votre cerveau vous trompe, et c’est le but !

Je l’ai déjà dit dans les précédents articles, mais c’est là tout le problème, comment donner une information 3D à une image plate ? Certes nous avons les normales pour nous donner la direction, mais qu’en est-il de la position ? Comment puis-je savoir que le haut de mon bloc est une surface au Z équivalant, variant sur X et Y ?

Explication du process depuis le début.

Reprenons depuis le Zébut

Un Tile est l’image représentant une zone, une unité de notre grille. Celle-ci a ses coordonnées X et Y. La distance entre leur position sera de 1 (1x, 1y ou 1x1y).

L’éclairage d’un Tile est soumis à la propagation de la lumière dans le subset de la map courante (cela nous rappelle de vieux souvenir [2018 !]). La propagation reste importante pour savoir quel Tile est impliqué dans l’éclairage (optimisation).

Un subset reprend une partie des éléments du monde concernés par le rendu à l’écran.

On parlera donc de la distance (parcourue ici) entre le Tile que l’on veut éclairer par rapport à la source lumineuse et sa force.

Une lumière peut donc éclairer sur une distance (d) un objet proportionnellement à sa force (S) à cette distance et selon l’intensité (i) de cette lumière. Nous obtenons alors le facteur (f) d’éclairage du pixel (rgb aux coordonnées uv).

f = (1 - (d * (1 / S))) * i

On obtient une valeur (float) allant de 0 à 1, qui sera notre facteur d’éclairage du Tile.

Ça c’était avant

Ce qu’il nous manquait jusque là c’est d’avoir toutes les coordonnées au niveau du shader pour effectuer ce calcul de distance au niveau du pixel.

On calculera ainsi notre distance autrement, c’est-a-dire sur base des coordonnées du Tile (t) et de la lumière (l), sans oublie l’intensité (i).

d = √((l.x - t.x)² + (l.y - t.y)²) * i

Donc on propage, on définit pour chaque Tile concernés les lumières impliquées, on ajoute les informations manquantes jusqu’alors et … ? Ben jusque là rien de nouveau sauf une distance plus correspondante à l’origine mais pas par rapport aux obstacles rencontrés entre la source et l’élément éclairé, car ça, je suis sur que vous les aviez oubliés.

On ne traverse pas un mur, la lumière « contourne » par rebond etc. Donc la propagation reste la bonne manière mais ne donnera pas le bon résultat en isométrique, et j’espère que vous comprenez pourquoi avec toute cette suite d’articles ahah. En bref, on a pas la possibilité de savoir comment la lumière est arrivée à chaque pixel, il faudrait calculer pour chaque objet un pixel de rebond (surement sa normale), balayer ainsi dans tous les sens et à chaque contact : éclairer ce pixel et modifier le rayon pour le faire repartir (couleur, angle, puissance, …), bref on réinventerai un rendu par projection de rayons en 2D sur base d’information 3D stockée dans les textures. Non !

Spatiale 3D dans de la 2D

Ce qu’il nous manque à cette étape est le moyen de faire évoluer notre formule de calcul de la distance pour inclure la position du pixel rendu de notre texture.

Nous avions vu plus haut que le Tile se définit par une position X et Y, ici en sont centre, sur la partie supérieur pour représenter le sol. Nous ne parlerons pas de Z ici mais l’idée sera la même une fois que j’aurais réussi à ramener l’information jusqu’au shader.

Si un Tile est à une distance de 1 de ses voisins alors il a à ses extrémités un delta variable de [-0.5, 0.5] en X et en Y. Z étant, lui, soumis à un facteur d’élévation définit par la scène, ici de 8 pixels, et donc aura une idée différente de sa distance, du moins c’est ainsi que l’élévation avait été définie au tout début.

Nous avions jusque là 3 textures : la diffuse (l’image elle-même), la normale (direction d’éclairage) et le spéculaire (carte de où appliquer le spéculaire). Et bien nous allons en ajouter une nouvelle.

Suivant la même idée que pour la texture de normales, nous réutiliserons notre définition RGB de nos repères : X en rouge, Y en vert et Z en bleu. Pour chaque pixel de l’image nous définirons ainsi sa position relative par une valeur RGB. Ne pas oublier que le centre ne sera pas 0, car nous avons besoin des deltas négatif, ainsi nous commencerons au centre à 128,128,128.

Un programme, un plugin, ou une théorie pour le faire de manière automatique n’existant pas, on va devoir y mettre de l’huile de coude.

Ainsi comme vous pouvez le voir, si on veut s’aider, nous pourrons compter sur les gradient, un bon dégradé bien réfléchi qui sera combiné à une couche supplémentaire. Nous commencerons par le XY et on surcouchera par le Z. Un total de 2h nécessaire avant d’arriver au résultat que voici.

Notre formule de distance peut donc être complétée par cette nouvelle information (pp).

d = √((l.x - (t.x + pp.x))² + (l.y - (t.y + pp.y))²) * i

Ce qui nous donnera sans plus attendre ce premier résultat :

Wow que se passe-t-il ? On a notre dégradé * fête * cependant nous avons un drôle d’aspect sur certain Tile. Notre algo utilise la force (S) pour se propager, mais comme vous l’avez vu, la propagation est plus courte que le segment calculable en ligne droite donc nous arrivons trop court, nous n’avons pas assez s’électionné de Tiles pour notre impact. L’idée serait surement de refaire un calcul sur base de la distance au lieu de calculer le nombre de saut, mais pour le workaround j’ai ajouté +2 à la force (S).

On en profite pour restaurer le calcul du spéculaire maintenant que nous avons de vraies informations et ce foutu éclair blanc au coffre s’en va !

Il y a bien évidemment des effets de bords, comme le fait que la distance parcourue n’étant plus impliquée, si vous fermez la porte, le Tile de l’autre côté sera éclairé comme si vous étiez à côté au lieu d’un faible éclairage (la lumière fait le tour du mur). Les tables clignotent et là je ne sais pas encore pourquoi. C’est plus joli mais moins cohérent. On en revient à l’idée des rebonds, du vecteur qui doit bouger selon l’idée mais rendre chaque Tile cohérent entre eux, là je ne vois pas comment.

J’ai quand même regardé comment ajouter la distance à la formule mais le résultat empirique n’est pas terrible.

Ajouter la distance assombrit la scène

Et en fait si on déplace la lumière selon la distance parcourue dans la même direction, on éloigne alors virtuellement notre Tile voisin. Et ainsi le résultat tiens compte de la distance. Ceci dit l’éclairage en a pris un coup, il y a surement quelque chose à faire avec ça.

lightPos += lightDirection * lights[i].distance;

Pour bien faire il faudra savoir si la distance correspond à un voisin, et ainsi différencier si notre voisin a une distance de 4 ou de 1 et appliquer l’éloignement que dans ce cas. Reste à trouver comment faire.

Et ensuite ?

Comme toujours, quelle est l’étape suivante et comment adresser les nouveaux problèmes, ou ceux mis de côté ? Plus j’avance dans TARS et plus les limites, personnelles et techniques se montrent. Il est grisant de se dire que j’ai relevé un défi qui, selon Google, n’a pas été adressé ou n’est actuellement plus référencé. Il faut aussi se rendre compte de l’effort considérable que cela demanderait si l’on voulait faire un jeu entier ainsi. Et là encore nous n’avons exploré que la lumière (par rapport à l’existant réalisé).

C’est le cœur serré que je me dis que j’ai clôturé cette étape, et qu’il faut, car le temps n’est pas infini, et que je n’ai pas que ce projet à faire, passer à autre chose… Quand je dis « passer à autre chose« , ce n’est certainement pas abandonner Nahyan, certes non ! Mais explorer (enfin) une autre piste que celle de tout faire moi-même, même si je n’ai pas fort le choix. Je ne vais pas en dire plus pour le moment, j’ai déjà commencé à explorer d’autres possibilités et vous raconterais le moment venu.

Cela fait 7 ans que TARS existe, avec une belle pause au milieu, et 2-3 belles phases de développement, comme le passage au WebGL pour adresser les performances sur l’éclairage première version, ou encore la refonte des classes communes pour mieux gérer les données et factoriser le code générique. Et enfin ici, le nouveau système d’éclairage, mais in fine rien de plus, nous n’avons toujours pas vu « Le temple du Prisme« , un HéroQuest ou un Liebesstein.

Qui sait que j’y toucherai par défi encore 😉

Je spécule un scapulaire

J’ai décidé de me pencher sur la question du spéculaire, c’est à dire ajouter un éclat de lumière à certains pixels. Quand on regarde ce que l’on a déjà fait et ce que l’on a trouvé sur le net cela semble facile, du coup, challenge !

Une fois que l’on a calculé la valeur de notre pixel, on a la valeur diffuse, ce qui sera la couleur affichée. Sur base des données que l’on a déjà, comme la lumière (position, vecteur), la normale de notre pixel et l’atténuation (facteur de notre lumière), alors il nous suffit d’une petite formule.

On crée un vecteur (vec3) représentant le point de vue (le nôtre), on le combine au vecteur de la lumière, on normalise, on fait le produit scalaire avec la normale du pixel, on restreint la valeur dans l’intervalle [0, 1] et on l’augmente par un exposant (32, qui peut être de 2 à n suivant l’effet voulu), enfin on l’ajoute à la couleur de manière équivalente aux 3 couleurs RGB pour le tendre vers le blanc, mais on pourrait appliquer un modificateur suivant cette documentation d’Unreal Engine.

Le résultat est blanc ! Sans dégradé ni distinguo, juste blanc ! Là commence une bataille en heures sur des variantes de la formule ou des valeurs des vecteurs. Je vous épargne, c’était « impossible », dans le sens où encore une fois, notre contexte n’est pas le contexte classique 3D et que mes notions de super matheux sont fictives.

Ce blanc est dû à un vecteur, celui du point de vue, et selon ce que l’on met, c’est tout noir ou tout blanc, et comme j’ai chipoté longtemps sans versionner les résultats, je ne peux que vous le raconter. Ensuite j’ai recomposé la couleur diffuse et isolé le spéculaire. Notre ennemi c’est Z, il est imposé à 1 et nous ne gérons pas la hauteur relative actuellement, donc tout est à 1 mais du coup seule la face de « sol » (les normales 0,0,1 soit bleues) sont violemment surchargées.

Démonstration de l’effet spéculaire incorrect pour les normales verticales 0,0,1/bleue

Comme pour la couleur diffuse il nous faudra manipuler le concept. J’en ai été à lire au sujet des produits scalaire et normalisation de vecteur et refaire les calculs à la main, ce qui a aidé, un peu quand même. Ce que j’ai obtenu est un nombre toujours en dessous de 1 (et > 0) ce qui fait qu’élever un 0,x à la puissance 32, le réduira toujours plus (0,0…00x).

La solution, remultiplier ce résultat par 255 pour le renvoyer dans l’espace de valeurs RGB. C’est empirique, mais le résultat donne quelque chose d’intéressant, donc pour une fois qu’on approche d’un truc passable…

Spéculaire sur le coffre, sur sa partie « métallique »

J’en ai profité pour faire quelques textures de normales, ce qui prends, à la main et par pixel, un temps fou. Et vu qu’on fait des modifications de shader et du système de rendu, pourquoi ne pas faire un truc qui n’était pas dans la liste et ajouter une texture de spéculaire, c’est-à-dire une image représentant les zones où appliquer l’effet spéculaire à notre image. Par exemple sur le coffre, ne prendre que les parties métallique.

Ainsi j’ai créé cette image, en utilisant la couche rouge qui peut varier de 0 à 255, ce qui se ramène à une valeur [0,1] permettant de multiplier le résultat spéculaire calculé, tel un facteur spécifique au pixel.

Ainsi, en une seule texture additionnelle et en utilisant les couches RGB de l’image, ainsi que l’alpha (transparence), nous pouvons stocker jusqu’à 4 valeurs permettant d’ajouter des modificateurs visuels à notre texture de base, tel que l’information de hauteur qui nous manque, le spéculaire dont on a parlé, peut-être une information d’ombrage, plutôt que de l’avoir dans l’image rendue, telle qu’actuellement. C’est à voir et investiguer mais ça reste intéressant.

Évidemment charger une énième texture méritait une factorisation côté sprite. On ajoute un type de texture (diffuse, normale, speculaire) en enum et on rend tout le monde meilleur. C’est encore fort « recherche et développement », pas très fini et très améliorable, tout est discutable, mais le résultat apparait 🙂 et ça, c’est bien !

Dernière amélioration, qui se voit sur les murs, ceux en position Est ou Sud, c’est à dire ceux que l’on voit mais qui n’appartiennent pas à notre bloc courant, mais également ceux en Nord et Ouest, mais cette fois sur le bloc courant, ces 4 cas, ne recevaient pas la lumière adéquatement.

Dans le cas du mur à gauche de la porte au début de la vidéo (donc un mur Est, sur le bloc x:1,y:3) on marche à côté sur x:2,y(2,3,4). Le mur était noir car la source de lumière partait de x:2,y3 (par exemple si on est à droite du mur avant la porte), le chemin de la lumière passait à x2:y4, x1:y:4, x1:y:3 et comme c’est un cas Est (ou Sud), ceux-ci ne prennent pas la lumière car « ils tournent le dos » à la case. Sauf que si je suis à côté du mur, d’une case adjacente, je m’attends à ce qu’il reçoive la lumière.

Dans le cas du mur Nord ou Ouest (le mur rouge face au coffre par exemple), vers la fin de la vidéo, coffre fermé, le squelette marche devant, si nous sommes dessus, le mur est noir, du fait que l’origine de la lumière et le mur sont sur la même position et annulent par conséquent leurs vecteurs.

La solution a été de déplacer virtuellement la coordonnée de la lumière par rapport à la zone du bloc occupée (N, S, E, O), ainsi l’effet est correcte.

On est loin d’avoir fini, il reste des textures de normales à faire (et c’est looong), des affinages sur le shader (encore et toujours), et sinon le reste de la liste :

  • Ajouter le tri des lumières, sur l’intensité j’imagine dans un premier temps, et réfléchir au cumul/fusion ensuite,
  • L’axe Z, les hauteurs et peut être les occlusions (on peut rêver),
  • L’éclairage global de la scène (facultatif), c’est déjà elle qui pilote, donc ça devrait être facile.

Des aNormales

Suite à toute cette énergie de réactivation sur TARS, j’en suis venu à me remettre à rêver et à me demander quels résultats j’aimerai présenter à d’éventuels joueurs (un jour).

Nous sommes dans un univers 2D à l’apparence 3D, donc nous appliquons des principes, des formules, propre à l’univers 2D pour afficher un monde qui se veut 3D. Cependant, et j’en ai déjà parlé précédemment, la lumière est un calvaire. Alors vous me direz que les autres s’en sortent plutôt bien, certes, mais ils « trichent intelligemment », avec un décors en un morceau et plat sur lequel on peut ajouter les éléments qui vont vous immerger (ombre plate rotative sur sol par exemple ou effet de lumière par un calque d’obscurité); alors que TARS se veut proposer une gestion de la hauteur [Z] ce qui est super casse-couille on ne va pas se mentir, mais permet de donner beaucoup plus cette impression de « vraie » 3D.

J’ai donc repensé à mon éclairage et à notre élément de bloc :

Le bloc test

L’image, et c’est important, est rendue uniformément sur base de données calculées au changement dans la scène (déplacement, mouvement de caméra), c’est à dire que virtuellement on va tenir compte d’éléments éclairant, calculer qui est soumis à ces lumières et appliquer un modificateur de couleur, in fine. S’en suit une application par le shader qui va prendre cette information et la mélanger à la couleur d’origine de chaque pixel, en gros.

MAIS, les flancs de ce bloc, ou les côtés des arbres, coffre, murs ou tables, sont éclairés de la même façon que le sol ou le reste de l’image, on n’augmente pas le relief du bloc lui-même mais plutôt sur une vue d’ensemble de la scène.

L’idée est donc de renforcer la notion 3D de l’élément, son volume que l’on devrait percevoir. On le « lit » grâce aux lignes de l’image, notre cerveau comprend la forme, son relief, mais son éclairage est plat. Ce problème existe depuis de nombreuses années dans l’univers des jeux 3D, rappelez vous ces murs plats à la texture de mur, lisse alors que l’image nous indique un relief. Je ne vais pas vous révéler un grand scoop, mais depuis on utilise différentes techniques, comme le displacement mapping ou, dans notre cas, le normal mapping.

Le but du normal mapping est de pouvoir prendre une surface texturée et de vous donner l’impression, par son éclairage, qu’il est en relief. Une application serait de prendre un modèle très détaillé et donc très couteux au rendu, de faire une texture détaillée, de simplifier le maillage drastiquement, et d’appliquer cette texture détaillée, là on a un modèle simple avec un beau niveau de détails, une belle économie de calcul, mais la lumière ? Comment ce modèle va-t-il être éclairé ? Grâce à la texture détaillée, elle donnera, après traitement, des informations permettant de savoir dans quelle direction chaque pixel est orienté, la normal map; et au moment du rendu, grâce à un shader, la texture détaillée sera éclairée pixel par pixel correctement, le résultat sera bluffant et rapide. Pour les détails, internet regorge de ressources à ce sujet, ici je synthétise :).

Ce que la théorie nous apprends sur les textures de normales et leurs directions (source OpenGameArt)
La texture de normales de notre bloc

Alors moquez vous, c’est fait avec paint car je n’avais rien d’autre sous la main. Et, pour accélérer l’article et son mystère, vous l’aurez vite remarqué mais ça n’a pas l’air de coller à la théorie (on y reviendra, moment de solitude). En lisant la théorie vous lirez que l’on va utiliser les valeurs RGB de chaque pixel pour donner une direction spatiale au pixel (son vecteur, sa normale), RGB devient XYZ (la suite plus tard), donc j’ai transposé la théorie à mon plan isométrique, et avec mon cube la couleur est sans appel, on a un X pur (255,0,0 = rouge) sur le franc droite, un Y pur (0,255,0 = vert) sur le flanc gauche et un Z bien plat au dessus (0,0,255 = bleu).

On modifie le code pour charger, stocker, référencer et définir notre texture de normales et l’envoyer jusqu’à notre shader existant (ça a déjà pris pas mal de boulot).

Premier essai de contrôle pour voir que la texture est bien là

Mouai, il y a une couille dans le pâté. Les objets « non cube de sol » ont la texture aussi. Vous le verrez à la déformation (les arbres). Sinon on voit que le coffre éclaire toujours en rouge (la petite part de tarte rouge et bleue).

Premier essai en ne tenant pas compte des objets qui n’ont pas de texture de normales.

Maintenant que j’ai les bonnes infos (si seulement) je me dit qu’on peut tenter d’appliquer ce que les grands esprits ont fait avant moi, je vous épargne le code et les modifs TARS.

Premier essai de rendu avec normales

On a donc une scène qui ne veut pas prendre les flancs des blocs en compte et cumule trop de lumière avec un assombrissement au niveau du coffre qui est étrange. Le reste est noir, non impacté par une lumière, et là on se dit qu’une lumière globale serait intéressant, mais ça ne l’est que dans l’esprit de la scène (un extérieur boisé), donc à laisser à la discrétion de chaque scène.

On isole tout et on essaye de comprendre ce que l’on a

Je retire tout, je laisse le sol et notre héro porteur de la lumière (voq) et on va tenter de manipuler notre shader pour vérifier, car on ne peut pas débugger un shader « pas à pas », malheureusement.

On remet le coffre pour valider la seconde source de lumière
On discrimine les affectés pour afficher et contrôler la portée des effets

Bon, qu’est-ce qu’on essaye de faire ? On a une liste de lumières qui affecte l’éclairage et la couleur de chaque pixel d’un élément rendu (ici notre bloc), ce qui se calcul avec la valeur normalisée de notre pixel de la texture de normales (à la même position bien sur) que l’on veut comparer au vecteur de la source lumineuse (une à une) pour en déduire l’impacte d’éclairage (un peu, beaucoup, passionnément, …) et ainsi composer, lumière après lumière, la couleur finale de notre pixel venant de la texture. Ce sont les normales de la texture de normales qui donneront l’effet de variation d’intensité des flancs.

La théorie 3D nous parle de matrice de l’univers, mais nous sommes à plat, et donc on ne peut pas appliquer simplement la formule, on doit la tordre, et je ne suis pas matheux, alors ça se complique. De plus, comme dit plus haut, on a fait une erreur, notre image représente une dimension isométrique et la texture de normal a été faite en ce sens, après tout, comparer des vecteurs ça devrait rester équivalent. Encore un point qui ne nous aide pas avec les formules (voir les sources en fin d’articles pour les curieux).

Différents tests et autant d’échecs. J’avoue, là, j’ai fait une pause de 2 jours après 2 jours dessus. Il me manque un truc mais je ne sais pas quoi. Les couleurs sont violentes ou noires, les flancs ne se colorient pas comme attendu, tout disparait (noir), … Foutu vecteurs.

Troisième essai où l’impacte des flancs se montre

On arrive enfin à quelque chose, mais en contre partie, les objets sans texture de normales sont noirs, peu grave, ça viendra plus tard. Le truc ? Tout reprendre et reconstruire le shader avec les 2 principes communs aux articles parcouru : normalisation et produit scalaire.

Pour faire simple, on définit notre pixel de texture et notre pixel de la texture de normales (texel), on a 2 vec3 (rgb), on normalise le texel, on crée un vec3 à 0.0 pour notre couleur résultante (donc noir par défaut, cf. la mise en noir cité plus haut), on boucle sur la liste des lumières et pour chaque on calcule un facteur lumière qui est le résultat du produit scalaire de notre normale et de la position de la lumière par rapport au bloc, ce qui nous donne un coefficient que l’on va utiliser pour multiplier le pixel de la texture, la couleur de la lumière, la force de notre lumière avec ce coefficient, ceci ajouté à notre vec3 couleur. Au final on indique d’utiliser ce résultat sans oublier l’alpha de la texture d’origine.

Comme vous le voyez ça clignote, c’est dû à l’ancienne manière de faire le passage de la lumière d’un tile vers un autre, en passant un delta de 0 à 50% jusqu’à changer de tile, pour se faire on ajoute un seconde lumière pour la différence du delta sur l’autre tile. Ce qui nous amène à constater ce qu’il nous manque encore :

  • Transformer le delta et l’envoyer au shader pour le gérer là, sinon nous en revenons au changement sec d’éclairage de tile pendant le déplacement, au lieu du côté doux que l’on a développé.
  • Gérer la hauteur de l’éclairage (Z), mais cela va demander à TARS de passer l’information et d’en tenir compte.
  • J’ai pensé à l’occlusion et au barrage mais ça me parait compliqué à ce stade.
  • Ajouter un éclairage global ? Ça peut-être intéressant pour sa différence.
  • Créer les textures de normales pour les autres objets.
  • Un des articles parle de la lumière spéculaire et je me demande si nous saurions l’ajouter nous aussi.

Voilà donc la semaine de travail sur les normales, avec, heureusement, un résultat encourageant.

Sources

Et si on revenait un peu à TARS

Mélange des origines avec la map de test

Petit point de situation suite à la mise à jour Angular 17. J’ai donc repris le POC TARS et sa carte de départ, j’ai nettoyé quelques POCs (CodingPark et le début du système de particules) et … ça ne marche plus :/ ?

Plus d’une fois j’ai eu le malheur d’un craquage complet avec un beau message d’erreur… et là je valide le fait que le message d’erreur est effectivement amélioré. Alors merci chère mémoire de ne pas m’aider à me rappeler pourquoi j’ai coder ça ainsi et merci les 4 ans passés à évoluer et donc ne plus écrire ou penser de la même manière. Ceci dit j’étais quand même dans le pétrin.

Je dirais principalement que le fait de ne plus compter sur les effets de bords du typage aide pas mal à avoir des soucis, du coup merci le mode stricte ^^. Puis surtout, en terme de documentation c’est tellement plus confortable.

C’est là aussi que l’on voit que l’on code en « happy path » et sans gestion rigoureuse d’erreur sur projet perso. Le « je sais ce que je fais car je l’ai fait » c’est bien, mais 4 ans plus tard… comment dire, ça laisse à désirer. Du coup « oui », TARS a des lacunes. J’ai tenté de dire que le sol était un sol et il n’aimait pas, imaginez ma surprise… et il fallait lui dire que c’était une « image »… logique ! Et pourquoi ? Du fait que la description de l’élément ne répondait pas au standard ISO(métrique) demandé, mais sans gestion d’erreur ça explose ailleurs et tu peux chercher longtemps pourquoi.

Comme le fait de cibler un élément d’un tableau sur son index, de modifier le tableau et d’avoir une erreur d’accès à une méthode sur une instance… tellement évident… heureusement ça c’est le côté POC pour générer la map et tester le moteur, l’idée étant de passer par l’éditeur ensuite qui lui ne fera pas d’erreur. Mais son opérateur, ça…

Ah et en passant, tant qu’à faire de l’amélioration générale, n’oubliez pas que A || B n’est pas pareil que A ?? B (petit lien sympa). Et que les ternaires A ? A : B peuvent devenir A ?? B.

Mais aussi que les pipes RxJs peuvent s’agrémenter de code perso et ainsi pouvoir charger un Object { a: 1, b: 2 } vers un tableau [{ name: a, value: 1 }, {...}].

Ou encore qu’on ne peut pas retourner la déclaration d’un enum pour accéder à ses clefs (return SO0), mais qu’il faudra retourner ses clefs directement (return Object.values(SO0)).

Enfin voilà, en gros, tout est une bonne grosse question de type et de ce que l’on peut faire avec. Il y aurait encore à en dire, mais au final je n’ai pas retenu ces directions, et sans les notes il sera dur de vous en montrer plus.

Il y a bien sûr encore pas mal de boulot, je vais repasser progressivement, à temps perdu, dans l’ensemble du POC et de TARS lui-même, faire un coup de clean et de typage massif, gestion d’erreur(s), et surement de refactorisation plus ou moins importante, par exemple la suppression du mode 2D, ce qui aiderait quelques petite performances; mais aussi le fait rencontré que le validateur de chemin n’ait pas l’information disponible de la différence de hauteur/niveau/élévation entre 2 Coords en comparaison, et qu’il sera difficile en l’état d’y arriver. Du coup, on reparle de théorie du système, doit-il gérer Z ou pas, gros débat (ombre portée dynamique, lumière, changement de niveau (hauteur), …).

Quand on pense qu’au départ de ce moment je voulais juste reprendre le dev de l’éditeur…

Y replonger, bien que difficile, chronophage et casse-gueule avec le temps et la mentalité différente aujourd’hui, est amusant, un petit challenge sur le côté, et surtout : il fonctionne, pour ce qu’il est, ses limites et possibilités.

Je me tâte toujours, est-ce que Nahyan peut être exploré en isométrique ou doit-on absolument le voir en 3D ?

PrimeVue : un datatable qui scroll, mais en joli.

PrimeVue, c’est chouette mais c’est fait avec les pieds :/ Plus on explore un truc, plus on voit ce qui se cache derrière une belle apparence et un beau lot de composants. Personne n’est parfait et l’amélioration est continue, mais si vous regardez comment c’est codé, il y a dû y avoir quelques stagiaires au niveau architectural : des ponts de composants pas fait avec un réel impacte derrière (filtre de tableau (regardez la comparaison de date…) ou combinaison de solution des input), ou encore ici le scroll du tableau (datatable encore lui) qui ne peut pas être designé naturellement comme scrollpanel).

Qui dit problème, dit recherche de solution : mettons un scrollpanel autour d’un datatable, ça sera joli.

    <ScrollPanel class="w-100">
        <DataTable ...

Premièrement il faudra dire au DataTable d’être scrollable et avec une hauteur en flex, tout en le contredisant avec une belle injonction css. Et de l’autre, comme attendu, préciser au ScrollPanel sa hauteur.

    <ScrollPanel class="w-100 table-normal-height">
        <DataTable scrollable scroll-height="flex" ...
.p-datatable.p-datatable-scrollable .p-datatable-wrapper { overflow: inherit }

.table-normal-height {
    height: var(--content-height); /* La hauteur qu'il vous faut */
}
Pour rappel : en vert c’est le padding.

Outre le fait que ça fonctionne, vous aurez un effet de bord, dû au fait du code CSS ci-dessous qu’ils ont appliqué. Vous n’aurez pas la fin de la dernière ligne du tableau et vous pourrez danser sur votre tête, rien n’y fera.

Mais d’où vient ce 18px arbitraire et à quoi bon. Dans notre cas d’un tableau en flex : aucun. J’imagine donc qu’ils ont eu un cas nécessitant ce bricolage, mais nous ça nous emmerde. Du coup, on va reset localement.

.p-scrollpanel-wrapper .p-scrollpanel-content {
    width: 100%;
    height: 100%;
    padding: 0;
}

Plus de padding (en vert) visible et le contenu (en bleu) est complet avec la scrollbar également bien positionnée. Tadaaa.

En bonus, pour ceux dont le design importe, voici ma surcouche esthétique de la scrollbar. Basé sur TailwindCSS pour les couleurs.

.p-scrollpanel .p-scrollpanel-wrapper {
    border-right: 9px solid var(--gray-400)
}
.p-scrollpanel .p-scrollpanel-bar {
    background-color: var(--red-800);
    opacity: 1;
    transition: background-color .2s;
}
.p-scrollpanel .p-scrollpanel-bar:hover {
    background-color: var(--red-600);
}

En gros, en premier on définit la largeur de la zone de scroll et la couleur de fond. Ensuite, la couleur de la barre de défilement avec une transition pour le hover. Et donc enfin l’effet de survol (hover) avec une couleur de réaction plus vive.

En ce début de 2014

Un accident de voiture me retient chez moi, laisser passer un piétons n’est pas sans danger quand la personne derrière ne fait pas attention…

L’occasion de mettre tout un tas de chose en ordre, comme son site, ses projets, le ménage…

Ainsi, Badawok 7.1 a été testé sur dev.comitards avec succès, juste 2-3 broutilles corrigées (cf le site tech).

Ensuite, j’ai eu l’occasion de dessiner le côté de ma future borne d’arcade, un future article y sera consacré.

J’ai également eu l’occasion de suivre une formation chez Alsacréations à Strasbourg sur le CSS3 et prochainement sur l’HTML5. Je vous la recommande ! Ainsi que ses marchés de Noël ;).

Les domaines de beabuzz sont tombés, le projet n’a donc de souvenir que la seconde partie de Badawok. Merci Bertrand M. pour cette aventure, qui, pour moi, n’est pas terminée :), même si différente !

De mon côté, daaboo, continue son changement de structure, progressivement. Tout va bien de ce côté là et je reprendrais surement tous mes sites sur un seul nouveau serveur.

Côté site web daaboo, le projet fusionnel daaboo/killan/tech reste sur la table, manque une idée finalisée et ça sortira 🙂 reste à savoir quand…

Ceci est une révolution…

Nous sommes chez Sivit depuis +6ans, je ne retrouve même plus la date du premier contact, c’est dire. Personnellement, je n’avais rien à leur reprocher, bon service et offre correcte, nous ce qu’on voulait, c’était développer notre offre d’hébergement, mails, domaines et de conception web, ainsi que des petits outils très en vogue à l’époque comme les compteurs, livre d’or etc. Ok ça c’est mort depuis un sacré bout de temps, les temps changent.

Depuis le rachat de Sivit par Nerim, j’ai personnellement senti une différence tant dans la qualité du service que dans leur communication.

S’en suit depuis quelques mois une séries de soucis avec nos serveurs qui n’avaient pas vraiment bougés pour dire d’en mériter. On a tout d’abord cru a une attaque par empoisonnement de cache DNS, puis a des tentatives de hack par les mails et au final plus assez de ressource sur la machine, veuillez prendre l’offre double ou quadruple… non merci, c’est pas 2 sites qui pètent un VDS même s’il est vrai que le VDS en question est pas terrible vu la concurrence.

C’est là que j’ai trouvé, grâce a des avis éclairés (encore merci à vous), une solution. Je vais changer ma manière d’opérer et aller vers cette fameuse « virtualisation ».

Il faut dire que passer des soirées et week-end entier pour de la config… à un moment donné on a autre chose à faire quand celles-ci ne vous apportent plus d’ennuis que de satisfaction. Revoir mon mode opératoire était donc indispensable, j’avais même pensé tout simplement arrêter cette partie de mon activité.

Ça plus les soucis passés ça faisaient beaucoup pour un seul homme en après journée… mais voilà qu’une nouvelle voie s’ouvre à moi comme dit ci-avant et donc on va reprendre le taureau par les cornes et affronter la bête.

J’ai jusque fin des contrats des 2 serveurs daaboo, soit +- juin, pour tout migrer, reconfigurer, adapter largement et offrir mieux pour vivre mieux.

Vous vous imaginez donc largement que tous les projets et sites sont actuellement en stand-by, le temps de mettre tout ça au clair le plus rapidement possible. Ça coûtera un peu, c’est sur, le recouvrement coûte toujours quelque chose, mais c’est pour un mieux.

Certifié JQuery par le W3School

Certifié JQuery par le W3School

http://www.refsnesdata.no/certification/w3certified.asp?id=5065887

Faut pas se leurrer, c’est juste une certification qui prouve qu’on connait la base de JQuery, surtout axé AJAX et sélecteur de base. Rien de très complexe, poussé ou même en rapport avec la version courante, voire même quelle version utilisée pour le cross-browser de maintenant. (cf V 1.8, 1.9 et 2 + version de compatibility et .browser parti sur gitHub)

Mais bon, ça fait quand même plaisir :). C’est 70 questions en 70 minutes.

J’ai fait 81% la première fois et 87% la seconde :).

Noyé Joël et Ânesse Molle

Reprise de Comitards (v2), Be a Buzz remanié et j’espère le DVD OQR.

Ça ça serait bien, et si je pouvais tenter une expérience Web 3D (Nahyan) ça serait encore mieux !

Il y a aussi le site ‘maison’ (bihin-vinders.be) qu’il serait bien de construire petit à petit.