Coverage for src/ensae_teaching_cs/special/image/image_synthese_scene.py: 98%
124 statements
« prev ^ index » next coverage.py v7.1.0, created at 2023-04-28 06:23 +0200
« prev ^ index » next coverage.py v7.1.0, created at 2023-04-28 06:23 +0200
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief définition d'une scène
5"""
6import math
7from .image_synthese_base import Pixel, Vecteur, Rayon, Couleur
10class Scene:
11 """
12 définit une scène, les axes x,y sont ceux de l'écran,
13 z-1 est la distance à l'écran du point (x,y,z)
14 """
16 def __init__(self, repere, alpha, x, y):
17 """définit la position de l'oeil, l'angle d'ouverture,
18 et la taille de l'écran"""
19 self.repere = repere
20 self.alpha = float(alpha)
21 self.dim = (int(x), int(y))
23 def ajoute_source(self, source):
24 """ajoute une source ponctuelle de lumière"""
25 if not hasattr(self, "sources"):
26 self.sources = []
27 self.sources.append(source)
29 def ajoute_objet(self, objet):
30 """ajoute un objet à la scène"""
31 if not hasattr(self, "objets"):
32 self.objets = []
33 self.objets.append(objet)
35 def __str__(self):
36 """affichage"""
37 s = "scene ----------------------------\n"
38 s += "repere : " + str(self.repere) + "\n"
39 s += "angle d'ouverture : " + str(self.alpha) + "\n"
40 s += "dimension de l'ecran : " + str(self.dim) + "\n"
41 if hasattr(self, "sources"):
42 for a in self.sources:
43 s += " " + str(a) + "\n"
44 if hasattr(self, "objets"):
45 for a in self.objets:
46 s += " " + str(a) + "\n"
47 return s
49 def intersection(self, rayon):
50 """calcule le point d'intersection entre un rayon et le plus proche des objets,
51 retourne l'objet et le point d'intersection"""
52 if not hasattr(self, "objets"):
53 return None, None
54 p = rayon.origine
55 sp, so = None, None
56 for o in self.objets:
57 i = o.intersection(rayon)
58 if i is None:
59 continue
60 if rayon.direction.scalaire(i - p) <= 0:
61 continue
62 if i == rayon.origine:
63 continue
64 if sp is None:
65 sp = i
66 so = o
67 else:
68 v = i - p
69 d = sp - p
70 if v.norme2() < d.norme2():
71 sp = i
72 so = o
73 return so, sp
75 def sources_atteintes(self, p):
76 """retourne la liste des sources atteintes depuis une position p de l'espace,
77 vérifie qu'aucun objet ne fait obstacle"""
78 res = []
79 for s in self.sources:
80 r = Rayon(s.origine, p - s.origine, Pixel(0, 0), s.couleur)
81 _, i = self.intersection(r)
82 if i is None:
83 continue
84 if (i - p).norme2() < 1e-10: # possible problème d'arrondi
85 res.append(s)
86 continue
87 return res
89 def construit_rayon(self, pixel):
90 """construit le rayon correspondant au pixel pixel"""
91 x = (pixel.x - self.dim[0] / 2) * \
92 math.tan(self.alpha / 2) / min(self.dim)
93 y = (pixel.y - self.dim[1] / 2) * \
94 math.tan(self.alpha / 2) / min(self.dim)
95 v = Vecteur(x, y, 1)
96 r = Rayon(self.repere.origine, self.repere.coordonnees(v),
97 pixel, Couleur(1, 1, 1))
98 return r
100 def modele_illumination(self, rayon, p, obj, source):
101 """calcule la couleur pour un rayon donné, un point p, un objet obj,
102 et une source de lumière source"""
103 n = obj.normale(p, rayon)
104 cos = n.cosinus(source.origine - p)
105 cl = obj.couleur_point(p) * cos
106 cl = cl.produit_terme(rayon.couleur)
107 return cl
109 def couleur_fond(self):
110 """retourne la couleur du fond"""
111 return Couleur(0, 0, 0)
113 def rayon_couleur(self, rayon, ref=True):
114 """retourne la couleur d'un rayon connaissant les objets,
115 cette fonction doit être surchargée pour chaque modèle d'illumination,
116 si ref == True, on tient compte des rayons réfractés et réfléchis"""
118 list_rayon = [rayon]
119 c = Couleur(0, 0, 0)
120 b = False
121 while len(list_rayon) > 0:
122 r = list_rayon.pop()
123 o, p = self.intersection(r)
125 if p is None:
126 continue
128 if ref:
129 t = o.rayon_refracte(r, p)
130 if t is not None:
131 list_rayon.append(t)
132 t = o.rayon_reflechi(r, p)
133 if t is not None:
134 list_rayon.append(t)
136 sources = self.sources_atteintes(p)
137 if len(sources) == 0:
138 return Couleur(0, 0, 0)
139 for s in sources:
140 cl = self.modele_illumination(r, p, o, s)
141 c += cl
142 b = True
144 if not b:
145 c = self.couleur_fond()
146 else:
147 c.borne()
148 return c
150 def construit_image(self, screen, pygame, fLOG):
151 """construit l'image de synthèse où screen est un objet du module pygame"""
152 count = 0
153 nbpixel = int(self.dim[0] * self.dim[1] / 100)
154 for y in range(0, self.dim[1]):
155 for x in range(0, self.dim[0]):
156 p = Pixel(x, y)
157 r = self.construit_rayon(p)
158 c = self.rayon_couleur(r, True)
159 q = (p.x, self.dim[1] - p.y - 1)
160 d = (int(c.x * 255), int(c.y * 255), int(c.z * 255))
161 if pygame is None:
162 for i, di in enumerate(d):
163 screen[q[0], q[1], i] = di
164 else:
165 pygame.draw.line(screen, d, q, q)
166 count += 1
167 if pygame is not None:
168 if count % 150 == 0:
169 pygame.display.flip()
170 if count % nbpixel == 0:
171 fLOG("avancement ", count // nbpixel, "%")
172 if pygame is not None:
173 pygame.display.flip()