◆ Portail Antalaha

Bibliothèques › Zdog

Modélisation

Concepts

Formes enfants

Les modèles Zdog sont construits avec des formes. On positionne une forme avec translate. Ces positions sont relatives. Par exemple, lorsque des formes sont ajoutées à une Illustration, elles sont positionnées par rapport à l'origine de l'Illustration.

let zCircle = new Zdog.Ellipse({
  addTo: illo,
  translate: { z: 40 }, // z +40 par rapport à illo
  // ...
});

let xRect = new Zdog.Rect({
  addTo: illo,
  translate: { x: 40 }, // x +40 par rapport à illo
  // ...
});

let yTri = new Zdog.Polygon({
  addTo: illo,
  translate: { y: -60 }, // y -60 par rapport à illo
  // ...
});

Une forme peut être ajoutée comme enfant d'une autre forme. Une forme enfant est positionnée par rapport à son parent.

let zCircle = new Zdog.Ellipse({
  addTo: illo,
  translate: { z: 40 }, // z +40 par rapport à illo
  // ...
});

let xRect = new Zdog.Rect({
  addTo: zCircle,
  translate: { x: 40 }, // x +40 par rapport à zCircle
  // ...
});

let yTri = new Zdog.Polygon({
  addTo: xRect,
  translate: { y: -60 }, // y -60 par rapport à xRect
  // ...
});

translate est une transformation, tout comme rotate et scale. Les formes enfants héritent des transformations de leurs parents (waouh, c'est profond).

let zCircle = new Zdog.Ellipse({
  addTo: illo,
  scale: 1.5, // échelle à 150 %
  translate: { z: 40 },
  rotate: { z: -Zdog.TAU/8 }, // rotation de 45° dans le sens antihoraire
  // ...
});

let xRect = new Zdog.Rect({
  addTo: zCircle,
  translate: { x: 40 },
  rotate: { x: Zdog.TAU/8 }, // rotation inverse
  // ...
});

let yTri = new Zdog.Polygon({
  addTo: xRect,
  translate: { y: -60 },
  // ...
});

Les formes enfants et leurs transformations cumulatives vous permettent de construire des modèles complexes.

Ancres

Une Anchor (ancre) est une forme invisible. Utilisez-en une pour appliquer des transformations sans afficher de forme.

let zAnchor = new Zdog.Anchor({
  addTo: illo,
  scale: 1.5,
  translate: { z: 40 },
  rotate: { z: -Zdog.TAU/8 },
});

let xAnchor = new Zdog.Anchor({
  addTo: zAnchor,
  translate: { x: 40 },
  rotate: { x: Zdog.TAU/8 },
});

let yTri = new Zdog.Polygon({
  addTo: xAnchor,
  translate: { y: -60 },
  // ...
});

Objets vecteurs

Les propriétés comme translate, rotation et scale sont des Vector. Un Vector peut être défini avec un objet vecteur.

Un objet vecteur est un simple objet JavaScript doté des propriétés de coordonnées x, y et z. Ces coordonnées sont facultatives : elles valent 0 si elles ne sont pas définies. Vous n'avez donc à renseigner que les valeurs non nulles.

translate: { x: 1, z: 2 }, // => { x: 1, y: 0, z: 2 }
translate: { y: 3 },       // => { x: 0, y: 3, z: 0 }
translate: {}              // => { x: 0, y: 0, z: 0 }

Copie

Copiez un élément avec .copy(). Les options d'origine sont reprises, et vous pouvez en remplacer certaines par de nouvelles valeurs.

let shape = new Zdog.Shape({
  addTo: illo,
  stroke: 12,
  translate: { x: -40 },
  color: '#636',
});

// copie de la forme, avec un nouvel emplacement
shape.copy({
  translate: { x: 40 },
});

Copiez un élément avec ses enfants grâce à .copyGraph().

let parent = new Zdog.Shape({
  addTo: illo,
  stroke: 12,
  translate: { x: -40 },
  color: '#636',
});

new Zdog.Shape({
  addTo: parent,
  stroke: 8,
  translate: { y: -20 },
  color: '#EA0',
});

// copie du parent avec son enfant
parent.copyGraph({
  translate: { x: 40 },
});

Volume du trait

Là où les moteurs 3D polygonaux s'appuient sur des maillages de polygones pour représenter un volume, les formes de Zdog peuvent donner du volume grâce à stroke.

Regardez ce délicieux hamburger. Le steak et la tranche de fromage sont de simples cercles. Les graines de sésame sont de simples lignes. Mais avec un stroke épais, ils apparaissent comme de gros disques et de petites gélules bien rondes.

// fromage
new Zdog.Rect({
  width: 92,
  height: 92,
  stroke: 16,
  // ...
});
// steak
new Zdog.Ellipse({
  diameter: 72,
  stroke: 28,
  // ...
});
// graine
new Zdog.Shape({
  path: [ { y: -3 }, { y: 3 } ],
  stroke: 8,
  // ...
});

Utiliser stroke pour donner du volume, c'est ce qui rend Zdog spécial. Abandonnez vos polygones terrestres et ne faites plus qu'un avec l'épaisseur arrondie.

Groupes

Utilisez un Group pour contrôler l'ordre d'affichage. Les formes sont affichées dans l'ordre où elles ont été ajoutées au Group. Les Group sont utiles pour positionner des formes à l'intérieur d'autres formes, comme des fenêtres dans des murs ou des pupilles dans des yeux.

// affichage des formes dans l'ordre d'ajout
var eyeGroup = new Zdog.Group({
  addTo: illo,
  translate: { z: 20 },
});
// d'abord le blanc de l'œil
new Zdog.Ellipse({
  addTo: eyeGroup,
  width: 160,
  height: 80,
  // ...
});
// puis l'iris
let iris = new Zdog.Ellipse({
  addTo: eyeGroup,
  diameter: 70,
  // ...
});
// puis la pupille
iris.copy({
  diameter: 30,
  color: '#636',
});
// enfin le reflet, devant tout le reste
iris.copy({
  diameter: 30,
  translate: { x: 15, y: -15 },
  color: 'white',
});

Tutoriel de modélisation

Modéliser avec Zdog consiste à positionner et à combiner des formes pour fabriquer des objets plus complexes. Ce tutoriel vous guide pas à pas dans la modélisation d'un personnage qui se pavane fièrement.

Tête et visage

Notre point de départ reprend la démo de prise en main. Nous disposons d'une Illustration, d'un modèle composé d'un seul Shape et d'une boucle d'animation.

let illo = new Zdog.Illustration({
  element: elem,
  zoom: 10,
  dragRotate: true,
});

// ---- modèle ---- //

let head = new Zdog.Shape({
  addTo: illo,
  stroke: 12,
  color: gold,
});

// -- animation --- //

function animate() {
  illo.updateRenderGraph();
  requestAnimationFrame( animate );
}
animate();

La tête est affichée comme une sphère de couleur unie grâce à Shape. La classe Shape peut être configurée pour afficher n'importe quelle forme (lignes, courbes, polygones) via sa propriété path. Comme path n'est pas défini pour head, son tracé est par défaut un simple point. Ce point, associé au volume du stroke, s'affiche comme une sphère de couleur unie. Autrement dit, un cercle.

Ensuite, nous ajoutons l'Ellipse de l'œil comme forme enfant de head avec addTo: head.

let eye = new Zdog.Ellipse({
  addTo: head,
  diameter: 2,
  quarters: 2, // demi-cercle
  translate: { x: -2, y: 1, z: 4.5 },
  // rotation du demi-cercle pour qu'il pointe vers le haut
  rotate: { z: -TAU/4 },
  color: eggplant,
  stroke: 0.5,
  // masqué lorsque la face avant est tournée vers l'arrière
  backface: false,
});

Pour l'œil de droite, nous pouvons utiliser .copy() sur celui de gauche. Les options d'origine sont copiées, puis peuvent être remplacées par de nouvelles valeurs, ici en modifiant translate.

// œil de gauche
let eye = new Zdog.Ellipse({
  addTo: head,
  diameter: 2,
  quarters: 2,
  translate: { x: -2, y: 1, z: 4.5 },
  // ...
});
// œil de droite
eye.copy({
  translate: { x: 2, y: 1, z: 4.5 },
});

Comparez les objets vecteurs translate des deux yeux.

// œil de gauche
translate: { x: -2, y: 1, z: 4.5 }
// œil de droite
translate: { x: 2, y: 1, z: 4.5 }

Seule la coordonnée x diffère. Pourtant, il faut bien renseigner les trois coordonnées x, y et z. Définir uniquement translate: { x: 2 } donnerait { x: 2, y: 0, z: 0 }, ce qui n'est pas ce que nous voulons.

Le sourire est réalisé avec un demi-cercle Ellipse similaire. Son tracé est fermé avec closed: true.

// sourire
new Zdog.Ellipse({
  addTo: head,
  diameter: 3,
  quarters: 2,
  translate: { y: 2.5, z: 4.5 },
  rotate: { z: TAU/4 },
  closed: true,
  color: '#FED',
  stroke: 0.5,
  fill: true,
  backface: false,
});

Le tronc

Donnons un corps à cette tête flottante, en commençant par les hanches.

// zoom de illo : 5
// retirez la tête pour l'instant

let hips = new Zdog.Shape({
  addTo: illo,
  path: [ { x: -3 }, { x: 3 } ],
  stroke: 4,
  color: '#636',
});

hips est simplement un Shape en ligne horizontale. Contrairement à head, hips a son path défini. Le path est un tableau contenant deux objets vecteurs. Ce tracé se lit donc ainsi : partir de x: -3 et tracer une ligne jusqu'à x: 3.

Nous commençons par les hanches, car le haut du corps du modèle pivote autour d'elles. En ajoutant les formes du haut du corps à hips, nous pouvons faire tourner les hanches, et les formes tourneront avec elles.

Remontons progressivement et ajoutons maintenant la poitrine.

let chest = new Zdog.Shape({
  addTo: hips,
  path: [ { x: -1.5 }, { x: 1.5 } ],
  // positionnée juste au-dessus des hanches
  // ( hips.stroke + chest.stroke ) / 2
  translate: { y: -6.5 },
  stroke: 9,
  color: '#C25',
});

chest est une autre ligne horizontale Shape. Un stroke généreux transforme cette petite ligne en un gros torse bombé.

La tête peut maintenant retrouver sa place au sommet.

let head = new Zdog.Shape({
  addTo: chest,
  stroke: 12,
  // positionnée au-dessus de la poitrine
  translate: { y: -9.5 },
  color: '#EA0',
});

// autres formes du visage...

Nous pouvons maintenant faire tourner illo et hips pour avoir un aperçu du résultat final.

let illo = new Zdog.Illustration({
  zoom: 5,
  // rotation pour une vue de trois quarts
  rotate: { y: -Zdog.TAU/8 },
  // ...
});

let hips = new Zdog.Shape({
  addTo: illo,
  // inclinaison de 45° vers l'arrière
  rotate: { x: Zdog.TAU/8 },
  // ...
});

Quelle décontraction !

Les jambes

Remettons-nous debout et travaillons maintenant sur la moitié inférieure.

let hipX = 3;

let hips = new Zdog.Shape({
  path: [ { x: -hipX }, { x: hipX } ],
  // ...
});

let leg = new Zdog.Shape({
  addTo: hips,
  path: [ { y: 0 }, { y: 12 } ],
  translate: { x: -hipX },
  color: '#636',
  stroke: 4,
});

Nous pouvons représenter une jambe avec un Shape en ligne droite. Cette ligne part de la hanche gauche. Nous ajoutons donc leg à hips et définissons son origine avec translate: { x: -hipX }. Nous pouvons utiliser la variable hipX à la fois dans hips.path et dans leg.translate pour rester cohérents. Il ne reste qu'à tracer le path en partant de l'origine 0 et en descendant, d'où path: [ { y: 0 }, { y: 12 } ].

Passons au pied.

// pied
new Zdog.RoundedRect({
  addTo: leg,
  width: 2,
  height: 4,
  cornerRadius: 1,
  // y : au-delà de l'extrémité de la jambe, z : décalé vers l'avant
  translate: { y: 14, z: 2 },
  color: '#C25',
  fill: true,
  stroke: 4,
});

Le pied est la première forme à envisager en trois dimensions. Le bout du pied pointe vers l'avant. Nous pourrions dessiner un Shape personnalisé utilisant des valeurs z. Mais il est plus simple de faire pivoter une forme de base pour l'orienter comme nous le souhaitons.

Ce pied simple est affiché avec un RoundedRect. Les coins sont complètement arrondis avec cornerRadius: 1, ce qui donne une forme de pilule aplatie. Le pied est ajouté à leg et positionné verticalement un peu au-delà de l'extrémité de la jambe, puis le long de z vers l'avant, pour que les orteils dépassent davantage que le talon.

Faisons maintenant pivoter ce pied.

// pied
new Zdog.RoundedRect({
  addTo: leg,
  translate: { y: 14, z: 2 },
  // rotation de 90° autour de l'axe x
  rotate: { x: Zdog.TAU/4 }
  // ...
});

Parfait. Maintenant que la première jambe est en place, nous pouvons copier la jambe et son pied avec .copyGraph().

leg.copyGraph({
  // positionnement à droite
  translate: { x: hipX },
});

Les bras

Notre ami a besoin de poings pour se pavaner. Nous utiliserons la même technique que pour les jambes, cette fois à partir de chest.

var armSize = 6;

// bras gauche
let upperArm = new Zdog.Shape({
  addTo: chest,
  path: [ { y: 0 }, { y: armSize } ],
  translate: { x: -5, y: -2 },
  color: '#636',
  stroke: 4,
});

Alors que la jambe n'était qu'une ligne droite de la hanche au pied, le bras demande deux parties. La première forme, upperArm, part de chest.

let forearm = new Zdog.Shape({
  addTo: upperArm,
  path: [ { y: 0 }, { y: armSize } ],
  translate: { y: armSize },
  color: '#EA0',
  stroke: 4,
});

L'avant-bras forearm se rattache à upperArm. Il commence à l'extrémité de upperArm grâce à translate: { y: armSize }, qui correspond au dernier point du path de upperArm.

// main
new Zdog.Shape({
  addTo: forearm,
  // rattachée à l'extrémité de l'avant-bras
  // décalée un peu vers l'avant
  translate: { y: armSize, z: 1 },
  stroke: 6,
  color: '#EA0',
});

La main est un Shape à point unique, comme head. Elle est rattachée à forearm.

// copie vers le bras droit
upperArm.copyGraph({
  translate: { x: 5, y: -2 },
});

Waouh ! Toutes les parties du corps sont en place.

En faisant tourner le modèle, vous remarquerez que des formes passent par-dessus d'autres, par exemple là où upperArm et forearm se chevauchent au niveau du coude. C'est ce qu'on appelle le z-fighting. Nous allons l'atténuer un peu en écartant les formes les unes des autres par rotation. Mais ces conflits de profondeur sont inévitables avec un moteur pseudo-3D comme Zdog. Plutôt que de lutter contre cet effet, acceptez-le tel qu'il est. Ce n'est pas un bogue : c'est ce qui donne à Zdog son charme.

Faire pivoter les jambes

Donnons un coup de pied. Commençons par ajuster la position et la rotation du personnage pour mieux l'observer.

const TAU = Zdog.TAU; // constante plus facile à lire

let illo = new Zdog.Illustration({
  // rotation pour une vue de trois quarts
  rotate: { y: -TAU/8 },
  // ...
});

let hips = new Zdog.Shape({
  // légèrement abaissé
  translate: { y: 2 },
  // ...
});

La jambe de gauche doit pivoter de 90°. Comme un coup de pied fait tourner la jambe autour de son axe horizontal, il faut la faire pivoter autour de l'axe x.

let leg = new Zdog.Shape({
  addTo: hips,
  path: [ { y: 0 }, { y: 12 } ],
  translate: { x: -hipX },
  // rotation de la jambe
  rotate: { x: TAU/4 },
  color: eggplant,
  stroke: 4,
});

J'aime utiliser TAU pour définir les valeurs de rotate. TAU correspond à une rotation complète en radians, soit 2 * Math.PI. Ainsi, TAU/4 représente un quart de tour.

Les éléments pivotent autour de leur origine. L'origine de leg est son premier point, que nous avons défini dans son tracé : [ { y: 0 }, { y: 12 } ].

Les deux jambes ont pivoté, car la jambe de droite a copié les propriétés de celle de gauche. Faisons donc pivoter la jambe de droite vers sa position.

// jambe de gauche
let leg = new Zdog.Shape({
  translate: { x: -hipX },
  rotate: { x: TAU/4 },
  // ...
});
// pied
new Zdog.RoundedRect({
  addTo: leg,
  // ...
});
// jambe de droite
leg.copyGraph({
  translate: { x: hipX },
  rotate: { x: -TAU/8 },
});

Les jambes sont en place. Mais le pied du bas doit paraître bien à plat sur le sol. Là encore, il a copié la valeur de rotation de l'autre pied. Nous devons donc la remplacer.

// pied qui donne le coup de pied
let foot = new Zdog.RoundedRect({
  addTo: leg,
  rotate: { x: TAU/4 },
  // ...
});
// jambe de droite
let standLeg = leg.copy({
  translate: { x: hipX },
  rotate: { x: -TAU/8 },
});
// pied d'appui
foot.copy({
  addTo: standLeg,
  rotate: { x: -TAU/8 },
});

Pour séparer les pieds, nous remplaçons leg.copyGraph() par leg.copy(). Le pied du coup de pied, foot, peut alors être copié, ajouté à standLeg et recevoir sa propre valeur de rotate.

Et voilà une démarche parfaitement normale. Tellement réaliste !

Faire pivoter la colonne vertébrale

Inclinons le haut du corps vers l'arrière en faisant pivoter hips.

let hips = new Zdog.Shape({
  rotate: { x: TAU/8 },
  // ...
});

Aïe. En faisant pivoter hips, c'est toute la silhouette qui a pivoté, puisque toutes les formes sont des descendantes de hips.

Pour résoudre ce problème, on peut ajouter une Anchor distincte qui ne fera pivoter que les formes du haut du corps.

// ancre distincte, uniquement pour faire pivoter le haut du corps
let spine = new Zdog.Anchor({
  addTo: hips,
  rotate: { x: TAU/8 },
});

let chest = new Zdog.Shape({
  // chest est ajouté à spine plutôt qu'à hips
  addTo: spine,
  // ...
});

Dernière étape : les bras.

Faire pivoter les bras

Les bras peuvent pivoter exactement comme les jambes.

// bras gauche
let upperArm = new Zdog.Shape({
  translate: { x: -5, y: -2 },
  // rotation de 90° vers l'arrière
  rotate: { x: -TAU/4 },
  // ...
});

// avant-bras et main...

// bras droit
upperArm.copyGraph({
  translate: { x: 5, y: -2 },
  // rotation de 90° vers l'avant
  rotate: { x: TAU/4 },
});

L'avant-bras forearm et la main continuent de s'éloigner en ligne droite de upperArm. Pour plier le coude, nous pouvons faire pivoter forearm.

let forearm = new Zdog.Shape({
  addTo: upperArm,
  rotate: { x: TAU/8 },
  // ...
});

Il suffit de faire pivoter un seul avant-bras, car l'angle de rotation relatif des avant-bras par rapport à leurs formes parentes (les bras) est le même.

C'est terminé !

Maintenant que le modèle est complet, essayez de revenir en arrière pour y apporter des modifications. Comment feriez-vous pivoter les bras pour obtenir une pose en T ? Comment pourriez-vous scinder les jambes en cuisse et tibia, comme nous l'avons fait pour les bras ? Quelles formes pourraient servir à représenter un visage en colère ?

Ce tutoriel couvre les bases de la composition de formes avec Zdog. Vous êtes maintenant prêt à créer vos propres modèles. Consultez l'API pour découvrir toutes les fonctionnalités de Zdog et les formes pour voir toutes les formes disponibles.

Traduction française de la documentation de Zdog (licence MIT) – https://zzz.dog