more comments on the flower drawing, some simplifications of the simple code
This commit is contained in:
parent
990f154cf1
commit
15cb88baee
2 changed files with 61 additions and 15 deletions
|
|
@ -9,13 +9,11 @@ import ImageColor
|
||||||
width = 500
|
width = 500
|
||||||
perspective = width
|
perspective = width
|
||||||
cameraZ = -width
|
cameraZ = -width
|
||||||
|
zBuffer = {}
|
||||||
|
|
||||||
im = Image.new("RGB", (width,width) )
|
im = Image.new("RGB", (width,width) )
|
||||||
|
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
zBuffer = {}
|
|
||||||
|
|
||||||
|
|
||||||
def sphere(a, b, radius):
|
def sphere(a, b, radius):
|
||||||
angle = a * math.pi * 2
|
angle = a * math.pi * 2
|
||||||
|
|
@ -55,7 +53,6 @@ def cylinder( a,b, radius=100, length=400 ):
|
||||||
|
|
||||||
return {"x": math.cos(angle) * radius,
|
return {"x": math.cos(angle) * radius,
|
||||||
"y": math.sin(angle) * radius,
|
"y": math.sin(angle) * radius,
|
||||||
# centrage du cylindre
|
|
||||||
"z": b * length - length / 2,
|
"z": b * length - length / 2,
|
||||||
"r": 0,
|
"r": 0,
|
||||||
"g": math.floor(b*255),
|
"g": math.floor(b*255),
|
||||||
|
|
@ -63,7 +60,12 @@ def cylinder( a,b, radius=100, length=400 ):
|
||||||
|
|
||||||
|
|
||||||
def if_none( func ):
|
def if_none( func ):
|
||||||
|
"""Ce décorateur permet de rajouter un test « autour » d'une fonction passée en argument.
|
||||||
|
Ici, il s'agit de n'appliquer la fonction décorée que si le premier argument est défini."""
|
||||||
|
|
||||||
|
# Le nom de la fonction embarquée importe peu.
|
||||||
def wrapper( *args, **kwargs ):
|
def wrapper( *args, **kwargs ):
|
||||||
|
"""Si le premier argument n'est pas "None", applique la fonction, sinon, le renvoie."""
|
||||||
if args[0]:
|
if args[0]:
|
||||||
return func( *args, **kwargs )
|
return func( *args, **kwargs )
|
||||||
else:
|
else:
|
||||||
|
|
@ -71,6 +73,9 @@ def if_none( func ):
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
# Le décorateur est appelé en premier lors de l'appel aux fonctions,
|
||||||
|
# ce qui détermine si la fonction est réellement appelé (si le premier argument existe)
|
||||||
|
# ou non (si l'argument n'existe pas).
|
||||||
@if_none
|
@if_none
|
||||||
def rotate_x( d, a ):
|
def rotate_x( d, a ):
|
||||||
d["y"] = d["y"] * math.cos(a) - d["z"] * math.sin(a)
|
d["y"] = d["y"] * math.cos(a) - d["z"] * math.sin(a)
|
||||||
|
|
@ -108,6 +113,8 @@ def draw_point( point ):
|
||||||
if not zBuffer.has_key(zbi) or point["z"] < zBuffer[zbi]:
|
if not zBuffer.has_key(zbi) or point["z"] < zBuffer[zbi]:
|
||||||
zBuffer[zbi] = point["z"]
|
zBuffer[zbi] = point["z"]
|
||||||
fill = ( int(point["r"]), int(point["g"]), int(point["b"]) )
|
fill = ( int(point["r"]), int(point["g"]), int(point["b"]) )
|
||||||
|
# On pourrait ne dessiner que la profondeur des objets
|
||||||
|
# en n'utilisant qu'un gradient de blanc calculé sur "z" :
|
||||||
#fill = ( 10+int(zBuffer[zbi]), ) * 3
|
#fill = ( 10+int(zBuffer[zbi]), ) * 3
|
||||||
draw.point( (int(pX),int(pY)), fill )
|
draw.point( (int(pX),int(pY)), fill )
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,18 @@ draw = ImageDraw.Draw(im)
|
||||||
# Fonctions de dessin #
|
# Fonctions de dessin #
|
||||||
#######################
|
#######################
|
||||||
|
|
||||||
|
# Le système de coordonnées utilisé est choisi du point de vue de la caméra :
|
||||||
|
# z
|
||||||
|
# /
|
||||||
|
# +-- x
|
||||||
|
# |
|
||||||
|
# y
|
||||||
|
|
||||||
|
|
||||||
def sphere(a, b, radius):
|
def sphere(a, b, radius):
|
||||||
"""Prend des coordonnées 2D ("a" et "b") entre 0 et 1 et les déforment
|
"""Prend des coordonnées 2D ("a" et "b") entre 0 et 1 et les déforment
|
||||||
de manière à les placer dans un cercle de rayon "radius".
|
de manière à les placer dans un cercle de rayon "radius".
|
||||||
Ajoute de la profondeur et un gradient de couleur jaune."""
|
Ajoute de la profondeur le long de l'axe b et un gradient de couleur jaune."""
|
||||||
|
|
||||||
# Angle en radian (pi/2 = 180°)
|
# Angle en radian (pi/2 = 180°)
|
||||||
angle = a * math.pi * 2
|
angle = a * math.pi * 2
|
||||||
|
|
@ -34,7 +42,7 @@ def sphere(a, b, radius):
|
||||||
|
|
||||||
return {"x":math.cos(angle) * radius * b + x0, # projection de a vers x
|
return {"x":math.cos(angle) * radius * b + x0, # projection de a vers x
|
||||||
"y":math.sin(angle) * radius * b + y0 ,# projection de b vers y
|
"y":math.sin(angle) * radius * b + y0 ,# projection de b vers y
|
||||||
"z": b * radius - radius / 2, # profondeur
|
"z": b * radius - radius / 2, # profondeur le long de b
|
||||||
"r": 50 + math.floor((1 - b**2) * 300),# gradient de couleur rouge
|
"r": 50 + math.floor((1 - b**2) * 300),# gradient de couleur rouge
|
||||||
"g": 50 + math.floor((1 - b**2) * 200),# gradient de couleur verte
|
"g": 50 + math.floor((1 - b**2) * 200),# gradient de couleur verte
|
||||||
"b": 0, # pas de couleur bleue
|
"b": 0, # pas de couleur bleue
|
||||||
|
|
@ -69,19 +77,31 @@ def petal(a,b,radius):
|
||||||
|
|
||||||
def cylinder( a,b, radius=100, length=400 ):
|
def cylinder( a,b, radius=100, length=400 ):
|
||||||
"""Déforme des coordonnées dans [0,1] en un cylindre de rayon "radius" et de longueur "length".
|
"""Déforme des coordonnées dans [0,1] en un cylindre de rayon "radius" et de longueur "length".
|
||||||
"""
|
Ajoute une profondeur sur b et un gradient vert."""
|
||||||
|
|
||||||
angle = a * 2*math.pi
|
angle = a * 2*math.pi
|
||||||
|
|
||||||
return {"x": math.cos(angle) * radius,
|
return {"x": math.cos(angle) * radius,
|
||||||
"y": math.sin(angle) * radius,
|
"y": math.sin(angle) * radius,
|
||||||
"z": b * length - length / 2, # centrage du cylindre
|
"z": b * length - length / 2, # le cylindre est centré
|
||||||
"r": 0,
|
"r": 0,
|
||||||
"g": math.floor(b*255),
|
"g": math.floor(b*255),
|
||||||
"b": 0 }
|
"b": 0 }
|
||||||
|
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Fonctions de manipulation de coordonnées #
|
||||||
|
############################################
|
||||||
|
|
||||||
|
# Les fonctions "rotate_*" déplacent toutes un point "d" selon une rotation d'angle "a",
|
||||||
|
# autour d'un axe donné.
|
||||||
|
# Les « points » sont ici des dictionnaires disposant de clefs "x","y" et "z".
|
||||||
|
|
||||||
def rotate_x( d, a ):
|
def rotate_x( d, a ):
|
||||||
|
"""Rotation du point d d'un angle a autour de l'axe x."""
|
||||||
|
# Si l'objet "d" existe (c'est à dire s'il n'est pas "None")
|
||||||
if d:
|
if d:
|
||||||
|
# Rotation
|
||||||
d["y"] = d["y"] * math.cos(a) - d["z"] * math.sin(a)
|
d["y"] = d["y"] * math.cos(a) - d["z"] * math.sin(a)
|
||||||
d["z"] = d["y"] * math.sin(a) + d["z"] * math.cos(a)
|
d["z"] = d["y"] * math.sin(a) + d["z"] * math.cos(a)
|
||||||
return d
|
return d
|
||||||
|
|
@ -90,6 +110,7 @@ def rotate_x( d, a ):
|
||||||
|
|
||||||
|
|
||||||
def rotate_y( d, a ):
|
def rotate_y( d, a ):
|
||||||
|
"""Rotation du point d d'un angle a autour de l'axe y."""
|
||||||
if d:
|
if d:
|
||||||
d["z"] = d["z"] * math.cos(a) - d["x"] * math.sin(a)
|
d["z"] = d["z"] * math.cos(a) - d["x"] * math.sin(a)
|
||||||
d["x"] = d["z"] * math.sin(a) + d["x"] * math.cos(a)
|
d["x"] = d["z"] * math.sin(a) + d["x"] * math.cos(a)
|
||||||
|
|
@ -99,6 +120,7 @@ def rotate_y( d, a ):
|
||||||
|
|
||||||
|
|
||||||
def rotate_z( d, a ):
|
def rotate_z( d, a ):
|
||||||
|
"""Rotation du point d d'un angle a autour de l'axe z."""
|
||||||
if d:
|
if d:
|
||||||
d["x"] = d["x"] * math.cos(a) - d["y"] * math.sin(a)
|
d["x"] = d["x"] * math.cos(a) - d["y"] * math.sin(a)
|
||||||
d["y"] = d["x"] * math.sin(a) + d["y"] * math.cos(a)
|
d["y"] = d["x"] * math.sin(a) + d["y"] * math.cos(a)
|
||||||
|
|
@ -108,7 +130,9 @@ def rotate_z( d, a ):
|
||||||
|
|
||||||
|
|
||||||
def move( d, dx, dy, dz ):
|
def move( d, dx, dy, dz ):
|
||||||
|
"""Déplace un point "d" selon des distances données par "dx", "dy" et "dz"."""
|
||||||
if d:
|
if d:
|
||||||
|
# les "d*" peuvent être positifs ou négatifs
|
||||||
d["x"] = d["x"] + dx
|
d["x"] = d["x"] + dx
|
||||||
d["y"] = d["y"] + dy
|
d["y"] = d["y"] + dy
|
||||||
d["z"] = d["z"] + dz
|
d["z"] = d["z"] + dz
|
||||||
|
|
@ -118,14 +142,27 @@ def move( d, dx, dy, dz ):
|
||||||
|
|
||||||
|
|
||||||
def draw_point( point ):
|
def draw_point( point ):
|
||||||
|
"""Projette un point donné en coordonnées 3D sur une image (2D, par définition)."""
|
||||||
|
|
||||||
|
# Si le point n'est pas en dehors de la forme (ce qui peut arriver si on dessine un pétale).
|
||||||
if point:
|
if point:
|
||||||
|
# Calcul le projetté de la coordonné "x" selon la perspective et le recul de la caméra.
|
||||||
|
# Notez que l'axe "z" est utilisé dans les deux calculs, au profit de "x" et "y".
|
||||||
pX = math.floor( (point["x"] * perspective) / (point["z"] - cameraZ) + width/2 )
|
pX = math.floor( (point["x"] * perspective) / (point["z"] - cameraZ) + width/2 )
|
||||||
pY = math.floor( (point["y"] * perspective) / (point["z"] - cameraZ) + width/2 )
|
pY = math.floor( (point["y"] * perspective) / (point["z"] - cameraZ) + width/2 )
|
||||||
zbi = pY * width + pX
|
|
||||||
|
# Coordonnées du pixel dans le calque de superposition.
|
||||||
|
zbi = (pY,pX)
|
||||||
|
|
||||||
|
# Si le pixel n'a jamais été dessiné OU si c'est le cas…
|
||||||
|
# … mais que sa coordonnée "z" est inférieur au pixel déjà dessiné
|
||||||
|
# (et est donc plus proche de la caméra).
|
||||||
if not zBuffer.has_key(zbi) or point["z"] < zBuffer[zbi]:
|
if not zBuffer.has_key(zbi) or point["z"] < zBuffer[zbi]:
|
||||||
|
# On garde en mémoire le pixel dessiné dans le calque de superposition.
|
||||||
zBuffer[zbi] = point["z"]
|
zBuffer[zbi] = point["z"]
|
||||||
|
|
||||||
|
# Dessine le pixel dans l'image.
|
||||||
fill = ( int(point["r"]), int(point["g"]), int(point["b"]) )
|
fill = ( int(point["r"]), int(point["g"]), int(point["b"]) )
|
||||||
#fill = ( 10+int(zBuffer[zbi]), ) * 3
|
|
||||||
draw.point( (int(pX),int(pY)), fill )
|
draw.point( (int(pX),int(pY)), fill )
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -133,19 +170,20 @@ def draw_point( point ):
|
||||||
import random
|
import random
|
||||||
# Nombres de points à dessiner
|
# Nombres de points à dessiner
|
||||||
for i in range(90000):
|
for i in range(90000):
|
||||||
|
# Valeurs dans [0,1[
|
||||||
a = random.random()
|
a = random.random()
|
||||||
b = random.random()
|
b = random.random()
|
||||||
# z
|
|
||||||
# /
|
# Rayons du cœur et des pétals
|
||||||
# +-- x
|
|
||||||
# |
|
|
||||||
# y
|
|
||||||
r_heart = 25
|
r_heart = 25
|
||||||
r_petal = 50
|
r_petal = 50
|
||||||
|
|
||||||
# coeur
|
# coeur
|
||||||
draw_point( sphere( a, b, r_heart ) )
|
draw_point( sphere( a, b, r_heart ) )
|
||||||
# pétale du haut
|
# pétale du haut
|
||||||
|
# Les valeurs des déplacements sont arbitraires et dépendent de ce que vous souhaitez faire.
|
||||||
draw_point( move( petal( a,b, r_petal ), 0, -70, 0 ) )
|
draw_point( move( petal( a,b, r_petal ), 0, -70, 0 ) )
|
||||||
|
# De même pour les rotations.
|
||||||
# pétale du bas
|
# pétale du bas
|
||||||
draw_point( move( rotate_x( petal( a,b, r_petal ), 1.15*math.pi ), -2, 141, -10 ) )
|
draw_point( move( rotate_x( petal( a,b, r_petal ), 1.15*math.pi ), -2, 141, -10 ) )
|
||||||
# pétale de gauche
|
# pétale de gauche
|
||||||
|
|
@ -155,5 +193,6 @@ for i in range(90000):
|
||||||
# tige
|
# tige
|
||||||
draw_point( move( rotate_x( cylinder( a,b, r_heart/4, 400 ), math.pi/2 ), 55, 250, 250 ) )
|
draw_point( move( rotate_x( cylinder( a,b, r_heart/4, 400 ), math.pi/2 ), 55, 250, 250 ) )
|
||||||
|
|
||||||
|
# Écris l'image dans un fichier au format « Portable Network Graphics », compressé sans perte.
|
||||||
im.save("paquerette.png", "PNG")
|
im.save("paquerette.png", "PNG")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue