bestguigui_lab 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 232fd8759bb525cb0e243e8fa4eccfb4fa94dc9b4a0f6fb7ce73a003f52d505d
4
- data.tar.gz: 666da34d4cea8a5f3d2efec9564e6d610854a249ffd9bf484d3b1a41dce7cc3a
3
+ metadata.gz: c8ae9a4f1ed462793b85d3bc6c0307ca45958e7f4cfa47e47e8714709f397e28
4
+ data.tar.gz: cb1b88bb9b5fbf2e163de51e59e1bb3e787339782557a09f7194a4f5f04c610c
5
5
  SHA512:
6
- metadata.gz: 8950ebdc674b3141c96dd79872f55af0ac520c1e2bfc13270cffd887b337d7dc36c6890b7a28c86f6136bddd8c28aad565d274ef7830e97e2747e427e22c7752
7
- data.tar.gz: '039120dc1ae9996040c279190835707d89ebdf935b7ffd6efc4cd5323b4ed0a2dd80f2e4b2796f2ee0c6ae4a517ea6ec6a1f349c77a820204e99d0ba1da02d8a'
6
+ metadata.gz: 92b8120063fa68d38ee3146420f700afa121d18cb0b701df124cc69bd893fb1d9b18bb724ba816f4938aa27371530c1188037c911b9cd421047eee639ec1477d
7
+ data.tar.gz: 5eb225c89974c30a6a2cff05464d7f9b5204e9accebb5a8d7fa9a38020bff9b80ec8f987a18977ff6ceee20f62ab5b01762f802676b9de2191fd837e492af41f
@@ -10,7 +10,7 @@ module Bestguigui
10
10
  @sounds = Hash.new { |cache, path| cache[path] = Gosu::Sample.new(path) }
11
11
  @songs = Hash.new { |cache, path| cache[path] = Gosu::Song.new(path) }
12
12
  @fonts = Hash.new { |cache, key| cache[key] = Gosu::Font.new(key[1], name: key[0]) }
13
- @models = Hash.new { |cache, path| cache[path] = ObjModel.new(path) }
13
+ @models = Hash.new { |cache, key| cache[key] = ObjModel.new(key[0], shading: key[1]) }
14
14
  @textures = Hash.new { |cache, path| cache[path] = GLTexture.new(path) }
15
15
  @frames = Hash.new { |cache, key| cache[key] = Gosu::Image.load_tiles(key[0], key[1], key[2], retro: true) }
16
16
 
@@ -28,7 +28,7 @@ module Bestguigui
28
28
  @fonts[[path, size]]
29
29
  end
30
30
 
31
- def self.model(name) = @models[resolve(name)]
31
+ def self.model(name, shading: :texture) = @models[[resolve(name), shading]]
32
32
  def self.texture(name) = @textures[resolve(name)]
33
33
  def self.frames(name, tile_width, tile_height) = @frames[[resolve(name), tile_width, tile_height]]
34
34
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bestguigui
4
+ # Envoie un buffer de floats vers un VBO OpenGL (GL_ARRAY_BUFFER) et
5
+ # renvoie son nom — utilisé par ObjModel pour charger sa géométrie.
6
+ class GLBuffer
7
+ def self.upload(data)
8
+ name_buffer = "\x00" * 4
9
+ glGenBuffers(1, name_buffer)
10
+ vbo = name_buffer.unpack1('L')
11
+
12
+ glBindBuffer(GL_ARRAY_BUFFER, vbo)
13
+ glBufferData(GL_ARRAY_BUFFER, data.bytesize, data, GL_STATIC_DRAW)
14
+
15
+ vbo
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bestguigui
4
+ # Calcule une couleur par sommet à partir de sa normale et d'une lumière
5
+ # fixe, quantifiée en peu de nuances par canal — utilisé par ObjModel en
6
+ # mode shading: :gouraud pour simuler, en plus de la texture (elle la
7
+ # module, ne la remplace pas), un rendu façon modèles de perso PSX
8
+ # (FF7 & co) : pas de lumière OpenGL temps réel, un effet "en bandes"
9
+ # plutôt qu'un dégradé lisse.
10
+ class GouraudShading
11
+ LIGHT_DIRECTION = [0.4, 0.8, 0.4].freeze # direction fixe
12
+ AMBIENT = 0.35 # part de lumière reçue même côté ombre
13
+ COLOR_LEVELS = 16 # teintes par canal : peu de nuances, façon banding PSX
14
+
15
+ def initialize(light_direction: LIGHT_DIRECTION, ambient: AMBIENT, color_levels: COLOR_LEVELS)
16
+ @light_direction = normalize(light_direction)
17
+ @ambient = ambient
18
+ @color_levels = color_levels
19
+ end
20
+
21
+ def vertex_color(normal, material_color)
22
+ intensity = @ambient + ((1.0 - @ambient) * [dot(normal, @light_direction), 0.0].max)
23
+ material_color.map { |channel| quantize(channel * intensity) }
24
+ end
25
+
26
+ private
27
+
28
+ def dot(vector_a, vector_b) = vector_a.zip(vector_b).sum { |x, y| x * y }
29
+
30
+ def normalize(vector)
31
+ length = Math.sqrt(vector.sum { |c| c * c })
32
+ vector.map { |c| c / length }
33
+ end
34
+
35
+ # Réduit à COLOR_LEVELS nuances entre 0 et 1 : le dégradé de lumière
36
+ # devient visiblement "en bandes", comme la faible précision couleur
37
+ # de la PSX.
38
+ def quantize(value)
39
+ step = 1.0 / (@color_levels - 1)
40
+ (value.clamp(0.0, 1.0) / step).round * step
41
+ end
42
+ end
43
+ end
@@ -1,20 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bestguigui
4
- # Charge un modèle .obj/.mtl et le dessine via un VBO : géométrie
4
+ # Charge un modèle .obj/.mtl et le dessine via un VBO texturé. shading:
5
+ # :gouraud ajoute, en plus de la texture (ou d'un blanc si absente), une
6
+ # teinte par sommet calculée une fois au chargement à partir de sa
7
+ # normale et d'une lumière fixe (voir GouraudShading) : simule le rendu
8
+ # bas-poly des perso PSX (FF7 & co), sans lumière OpenGL temps réel.
5
9
  class ObjModel
6
- # position (3) + texcoord (2) + normale (3) par sommet.
10
+ # position (3) + texcoord (2) + normale ou teinte Gouraud (3) par sommet.
7
11
  FLOATS_PER_VERTEX = 8
8
12
  STRIDE = FLOATS_PER_VERTEX * 4 # 4 octets par float
9
13
 
10
- def initialize(obj_path)
14
+ def initialize(obj_path, shading: :texture)
11
15
  super()
12
- @vertices = []
13
- @tex_coords = []
14
- @normals = []
15
- @faces = []
16
+ @shading = shading
17
+ # rubocop:disable-next Style/ParallelAssignment -- reste sous la limite de Metrics/MethodLength
18
+ @vertices, @tex_coords, @normals, @faces = [], [], [], []
16
19
  @texture = nil
17
-
20
+ @material_color = [1.0, 1.0, 1.0]
18
21
  parse_obj(obj_path)
19
22
  @texture ||= GLTexture.blank
20
23
  @vertex_count = @faces.size
@@ -75,53 +78,60 @@ module Bestguigui
75
78
  end
76
79
  end
77
80
 
78
- # Un seul matériau par modèle : la dernière texture (map_Kd) trouvée.
81
+ # Un seul matériau : dernière texture (map_Kd) et couleur diffuse (Kd) trouvées, Kd sert de base au Gouraud.
79
82
  def load_material(path)
80
83
  directory = File.dirname(path)
81
84
 
82
85
  File.foreach(path) do |line|
83
86
  tokens = line.split
84
- next if tokens.empty? || tokens.shift != 'map_Kd'
87
+ next if tokens.empty?
85
88
 
86
- @texture = GLTexture.new(File.join(directory, tokens.first))
89
+ keyword = tokens.shift
90
+ case keyword
91
+ when 'Kd' then @material_color = tokens.map(&:to_f)
92
+ when 'map_Kd' then @texture = GLTexture.new(File.join(directory, tokens.first))
93
+ end
87
94
  end
88
95
  end
89
96
 
90
97
  # Buffer plat non indexé : les sommets partagés sont dupliqués, pas
91
98
  # d'index buffer pour rester simple.
92
99
  def build_vertex_buffer
93
- data = @faces.flat_map { |v, vt, vn| vertex_attributes(v, vt, vn) }.pack('F*')
94
-
95
- name_buffer = "\x00" * 4
96
- glGenBuffers(1, name_buffer)
97
- vbo = name_buffer.unpack1('L')
98
-
99
- glBindBuffer(GL_ARRAY_BUFFER, vbo)
100
- glBufferData(GL_ARRAY_BUFFER, data.bytesize, data, GL_STATIC_DRAW)
101
-
102
- vbo
100
+ gouraud = GouraudShading.new if @shading == :gouraud
101
+ data = @faces.flat_map { |v, vt, vn| vertex_attributes(v, vt, vn, gouraud) }.pack('F*')
102
+ GLBuffer.upload(data)
103
103
  end
104
104
 
105
- def vertex_attributes(vertex_index, tex_coord_index, normal_index)
105
+ # extra : la normale (mode :texture, inutilisée) ou une teinte Gouraud qui modulera la texture au dessin.
106
+ def vertex_attributes(vertex_index, tex_coord_index, normal_index, gouraud)
106
107
  position = @vertices[vertex_index]
107
108
  tex_coord = tex_coord_index ? @tex_coords[tex_coord_index] : [0.0, 0.0]
108
109
  normal = normal_index ? @normals[normal_index] : [0.0, 0.0, 1.0]
110
+ extra = gouraud ? gouraud.vertex_color(normal, @material_color) : normal
109
111
 
110
- position + tex_coord + normal
112
+ position + tex_coord + extra
111
113
  end
112
114
 
113
115
  def enable_vertex_arrays
114
116
  glEnableClientState(GL_VERTEX_ARRAY)
115
117
  glEnableClientState(GL_TEXTURE_COORD_ARRAY)
116
- glEnableClientState(GL_NORMAL_ARRAY)
117
-
118
118
  glVertexPointer(3, GL_FLOAT, STRIDE, 0)
119
119
  glTexCoordPointer(2, GL_FLOAT, STRIDE, 3 * 4)
120
+ @shading == :gouraud ? enable_color_array : enable_normal_array
121
+ end
122
+
123
+ def enable_color_array
124
+ glEnableClientState(GL_COLOR_ARRAY)
125
+ glColorPointer(3, GL_FLOAT, STRIDE, 5 * 4)
126
+ end
127
+
128
+ def enable_normal_array
129
+ glEnableClientState(GL_NORMAL_ARRAY)
120
130
  glNormalPointer(GL_FLOAT, STRIDE, 5 * 4)
121
131
  end
122
132
 
123
133
  def disable_vertex_arrays
124
- glDisableClientState(GL_NORMAL_ARRAY)
134
+ glDisableClientState(@shading == :gouraud ? GL_COLOR_ARRAY : GL_NORMAL_ARRAY)
125
135
  glDisableClientState(GL_TEXTURE_COORD_ARRAY)
126
136
  glDisableClientState(GL_VERTEX_ARRAY)
127
137
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bestguigui
4
- VERSION = '0.12.0'
4
+ VERSION = '0.13.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bestguigui_lab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bestguigui
@@ -45,7 +45,9 @@ files:
45
45
  - lib/bestguigui_lab/animation.rb
46
46
  - lib/bestguigui_lab/assets.rb
47
47
  - lib/bestguigui_lab/assets/splash_jingle.wav
48
+ - lib/bestguigui_lab/gl_buffer.rb
48
49
  - lib/bestguigui_lab/gl_texture.rb
50
+ - lib/bestguigui_lab/gouraud_shading.rb
49
51
  - lib/bestguigui_lab/helpers.rb
50
52
  - lib/bestguigui_lab/image_pixels.rb
51
53
  - lib/bestguigui_lab/input.rb