pyxen.Mesh
Un Mesh est un élément dessinable fait de triangles ou de quads que vous définissez sommet par sommet. Chaque sommet a une position, un point d’une image à échantillonner et une couleur : un mesh peut donc être un polygone uni, une bande texturée, un dégradé, ou une surface qui se plie et ondule.
C’est un composant, comme Sprite, Text et les formes : vous en donnez un à une entité au moment du spawn, et l’entité le dessine à chaque frame.
Un mesh est en 2D. Les sommets ont un x et un y et aucune profondeur : pas de z, pas de tampon de profondeur, pas de perspective. Tout est transformé sur le CPU et dessiné à plat, dans l’ordre des calques comme n’importe quel autre composant. C’est un moyen de dessiner des formes que les sprites et les formes ne savent pas faire, pas un moteur 3D.
from pyxen import *
def start():
world.spawn(x=160, y=60,
mesh=Mesh(points=(-40, 40, 0, -40, 40, 40), fill=(0.3, 0.9, 0.5, 1.0)))
Constructeur
Mesh(
*,
points=None,
vertices=None,
indices=None,
fill=None,
image=None,
mode=TRIANGLES
)
Tous les paramètres sont réservés aux mots-clés. Il faut donner points ou vertices ; n’en donner aucun lève ValueError: Mesh needs points= or vertices=.
Paramètres
| Paramètre | Type | Description |
|---|---|---|
points | tuple, liste ou FloatBuffer | Positions à plat, deux flottants par sommet : (x1, y1, x2, y2, …). Chaque sommet reçoit l’unique couleur de fill |
vertices | tuple, liste ou FloatBuffer | Sommets complets, huit flottants par sommet : x, y, u, v, r, g, b, a |
indices | tuple, liste ou IntBuffer | Quels sommets forment chaque triangle ou quad. Omettez-le pour utiliser les sommets dans l’ordre |
fill | couleur | Couleur pour points. Un tuple (r, g, b, a) ou une Color. Blanc opaque par défaut |
image | str | Nom d’une image du projet qui texture le mesh |
mode | TRIANGLES ou QUADS | Ce qu’est chaque primitive. TRIANGLES par défaut |
Si vous passez à la fois points et vertices, vertices l’emporte.
Sommets
Un sommet, ce sont huit nombres :
| Flottant | Signification |
|---|---|
x, y | Position, en pixels, relative à l’entité. Voir Transformations |
u, v | Le point de l’image que montre ce sommet, de 0.0 à 1.0 sur toute l’image. (0, 0) est le coin supérieur gauche et (1, 1) le coin inférieur droit. Ignorés sans image |
r, g, b, a | La couleur du sommet, de 0.0 à 1.0 chacune |
La couleur est interpolée entre les sommets : donner une couleur différente à chaque coin d’un quad dessine un dégradé. Avec une image, la couleur est multipliée par l’image : le blanc laisse l’image telle quelle, le rouge n’en garde que le rouge.
La forme courte points sert aux géométries sans texture et d’une seule couleur. C’est équivalent à vertices avec u = v = 0 et toutes les couleurs égales à fill.
Indices
Les sommets sont listés une seule fois. Les indices disent lesquels forment chaque primitive : deux triangles qui partagent une arête peuvent donc en partager les deux sommets au lieu de les répéter.
- Avec
mode=TRIANGLES, chaque groupe de 3 indices forme un triangle. - Avec
mode=QUADS, chaque groupe de 4 indices forme un quad. Listez les coins dans l’ordre autour du quad, par exemple haut-gauche, haut-droite, bas-droite, bas-gauche.
Un indice compte des sommets, pas des flottants : 2 désigne le troisième sommet, quelle que soit la forme des sommets.
# Un carré fait de deux triangles qui partagent l'arête 0-2
Mesh(vertices=square, indices=(0, 1, 2, 0, 2, 3))
# Le même carré en un seul quad
Mesh(vertices=square, indices=(0, 1, 2, 3), mode=QUADS)
Sans indices, les sommets sont utilisés dans l’ordre : les trois premiers forment un triangle (ou les quatre premiers un quad), et ainsi de suite. C’est ce qu’il faut pour une bande construite sommet par sommet.
Deux choses sont tolérées plutôt que signalées. Un indice qui dépasse le dernier sommet supprime son propre triangle ou quad, et le reste se dessine. Un groupe final de moins de 3 (ou 4) indices est ignoré. Un jeu qui reconstruit ses sommets alors qu’une ancienne liste d’indices est encore en place perd un triangle pendant une frame au lieu de s’arrêter.
Textures
world.spawn(x=40, y=40, mesh=Mesh(
vertices=(0, 0, 0, 0, 1, 1, 1, 1,
64, 0, 1, 0, 1, 1, 1, 1,
64, 64, 1, 1, 1, 1, 1, 1,
0, 64, 0, 1, 1, 1, 1, 1),
image="tiles",
mode=QUADS,
))
image est le nom d’une image du projet, sans extension, trouvée de la même façon que pour Sprite. Les u et v de chaque sommet choisissent le point de l’image qu’il montre, et l’image est étirée sur la primitive entre eux.
u et v sont des fractions de l’image entière, pas des pixels. Les valeurs hors de 0.0–1.0 ne se répètent pas en mosaïque : elles répètent les pixels du bord de l’image.
Transformations
Les sommets d’un mesh sont locaux à son entité. Le x, le y, la rotation et l’échelle (scale) de l’entité, ainsi que ceux de son parent s’il en a un, sont appliqués à chaque sommet au dessin. Construisez la géométrie autour de (0, 0) et déplacez-la, tournez-la et mettez-la à l’échelle comme n’importe quelle entité.
def start():
world.spawn(name="diamond", x=160, y=90,
mesh=Mesh(points=(-20, 0, 0, -30, 20, 0, 0, 30),
indices=(0, 1, 2, 3), mode=QUADS,
fill=(1.0, 0.6, 0.2, 1.0)))
def update():
world.get(name="diamond").rotation += 1
Attacher un Mesh
Lors du spawn :
e = world.spawn(x=100, y=60, mesh=Mesh(points=(0, 0, 50, 0, 25, 40)))
Le spawn copie la géométrie dans l’entité. Ensuite, l’objet Mesh que vous avez construit n’est plus relié à elle : modifiez la géométrie vivante via entity.mesh, décrit ci-dessous. Le même objet Mesh peut être spawné plusieurs fois pour produire des copies indépendantes.
Propriétés du composant
Accès via entity.mesh :
| Membre | L/E | Description |
|---|---|---|
points = buf | E | Remplace la position de chaque sommet à partir d’une séquence plate (x, y, …). Les couleurs et u, v restent ce qu’ils étaient |
vertices = buf | E | Remplace chaque sommet à partir d’une séquence plate, 8 flottants chacun. Le nombre de sommets peut changer |
indices = buf | E | Remplace la liste d’indices |
visible = flag | E | Dessine le mesh ou l’ignore, sans perdre sa géométrie |
mode | L | TRIANGLES ou QUADS |
set_vertex(i, x, y, [u, v, [r, g, b, a]]) | Modifie un seul sommet. u, v puis r, g, b, a sont facultatifs, mais chaque groupe doit être donné en entier | |
count() | Le nombre de sommets |
mode est fixé à la construction du mesh.
e.mesh.points = (0, 0, 60, 0, 30, 50) # déplacer les trois sommets
e.mesh.set_vertex(1, 70, 5) # puis pousser l'un d'eux
e.mesh.visible = False # le masquer
Appeler set_vertex avec un numéro de sommet qui n’existe pas est une erreur, tout comme utiliser entity.mesh sur une entité qui n’en a pas.
Un points plus court que le mesh ne déplace que les premiers sommets ; un points plus long est coupé au dernier sommet.
Animer un mesh
Préférez points pour tout ce qui bouge à chaque frame. Une seule assignation remplace toutes les positions et la boucle sur les sommets s’exécute dans le moteur ; la même mise à jour via set_vertex coûte un appel Python par sommet.
Cela rend l’envoi bon marché, pas le calcul. Vous calculez toujours chaque nouvelle position dans votre code à chaque frame, et pour un mesh à nombreux sommets c’est là que passe le temps. Un mesh construit une fois puis déplacé, tourné ou mis à l’échelle avec son entité ne coûte presque rien par frame ; un mesh dont la forme change à chaque frame coûte ce que coûte le calcul de sa géométrie.
Ce ruban ondulant construit sa géométrie une seule fois puis déplace les sommets via entity.mesh.points. Il n’a besoin d’aucun asset.
from pyxen import *
import math
COLUMNS = 20
STEP = 14
def ribbon(t):
points = []
for i in range(COLUMNS + 1):
y = math.sin(t * 0.08 + i * 0.5) * 14
points.append(i * STEP)
points.append(y - 12) # bord supérieur
points.append(i * STEP)
points.append(y + 12) # bord inférieur
return points
def start():
vertices = []
indices = []
for i in range(COLUMNS + 1):
r = i / COLUMNS
# sommets haut et bas de cette colonne : x, y, u, v, r, g, b, a
vertices.extend((0, 0, 0, 0, 1.0 - r, 0.4, r, 1.0))
vertices.extend((0, 0, 0, 0, 1.0 - r, 0.4, r, 1.0))
for i in range(COLUMNS):
a = i * 2
# haut-gauche, bas-gauche, bas-droite, haut-droite
indices.extend((a, a + 1, a + 3, a + 2))
world.spawn(name="ribbon", x=20, y=90,
mesh=Mesh(vertices=vertices, indices=indices, mode=QUADS))
def update():
world.get(name="ribbon").mesh.points = ribbon(time.frame)
FloatBuffer et IntBuffer
Un mesh peut se construire avec des tuples et des listes, mais quand la géométrie est grande ou reconstruite souvent, deux types de tampon évitent de fabriquer un nouveau tuple à chaque fois.
positions = FloatBuffer(64) # capacité
positions.append(0.0)
positions.append(0.0)
positions[0] = 5.0 # lecture et écriture par indice
indices = IntBuffer(64)
indices.append(0)
FloatBuffer(capacity) | IntBuffer(capacity) | |
|---|---|---|
| Contient | Des flottants : positions, sommets | Des entiers : indices |
append(value) | Ajoute à la fin | Ajoute à la fin |
clear() | Le vide | Le vide |
buf[i] | Lecture et assignation | Lecture et assignation |
capacity est la taille de départ ; un tampon grandit quand on ajoute au-delà. Passez-les en points=, vertices= ou indices= à un Mesh, ou assignez-les à la propriété correspondante de entity.mesh.
Mesh ou formes ?
Les formes conviennent pour un rectangle, un cercle, un triangle ou une ligne, et draw pour une image que vous redessinez à chaque frame. Passez au mesh quand il vous faut ce qu’elles ne savent pas faire : un polygone texturé qui n’est pas un rectangle, une couleur différente à chaque coin, ou une surface dont les sommets bougent indépendamment, comme un drapeau, une route, un ruban ou de l’eau.
Erreurs courantes
Géométrie manquante
Mesh(fill=(1, 0, 0, 1))
ValueError: Mesh needs points= or vertices=