bestguigui_jam_kit 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9eab78344f9a0d2b2fbef670ffa2549ccf9095f617c76aa4fb04e0bcf30cf45b
4
+ data.tar.gz: f719011fee460de08633a08350ffac7a9982720e3e342902bf3f8fd184104670
5
+ SHA512:
6
+ metadata.gz: 6444d4704051d68a6c3f255b6a06ced4c5c70c18bc4701ff3b882798b96bc9deb3e3a77856d3b0c2e150cf53a8e0db8628c32abc7ba9e92c18e6eb3180e4c12b
7
+ data.tar.gz: 03c8e61a6632efa0a5d20a3ba88ef719046e8ea889073080bc39de908420191f4ce2428114589adf375284673a5ac20a14c600742f738551d0866864ed0294f9
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bestguigui
4
+ # Charge et met en cache les assets (images, sons, modèles 3D...) pour éviter de les recharger plusieurs fois.
5
+ class Assets
6
+ @root = Dir.pwd
7
+
8
+ @images = Hash.new { |cache, path| cache[path] = Gosu::Image.new(path, retro: true) }
9
+ @sounds = Hash.new { |cache, path| cache[path] = Gosu::Sample.new(path) }
10
+ @songs = Hash.new { |cache, path| cache[path] = Gosu::Song.new(path) }
11
+ @fonts = Hash.new { |cache, key| cache[key] = Gosu::Font.new(key[1], name: key[0]) }
12
+ @models = Hash.new { |cache, path| cache[path] = ObjModel.new(path) }
13
+ @textures = Hash.new { |cache, path| cache[path] = GLTexture.new(path) }
14
+
15
+ # À fixer une fois au démarrage (ex. `Assets.root = __dir__` dans ta
16
+ # fenêtre principale) : dossier depuis lequel résoudre les chemins
17
+ # relatifs passés aux méthodes ci-dessous.
18
+ class << self
19
+ attr_writer :root
20
+ end
21
+
22
+ def self.resolve(name) = File.expand_path(name, @root)
23
+
24
+ def self.image(name) = @images[resolve(name)]
25
+ def self.sound(name) = @sounds[resolve(name)]
26
+ def self.song(name) = @songs[resolve(name)]
27
+ def self.font(name, size = 20) = @fonts[[resolve(name), size]]
28
+ def self.model(name) = @models[resolve(name)]
29
+ def self.texture(name) = @textures[resolve(name)]
30
+ end
31
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bestguigui
4
+ # Texture OpenGL indépendante de l'atlas partagé de Gosu, construite à
5
+ # partir des pixels bruts (`to_blob`) — permet un vrai GL_REPEAT natif.
6
+ class GLTexture
7
+ attr_reader :tex_name
8
+
9
+ def initialize(path)
10
+ super()
11
+ image = Gosu::Image.new(path, retro: true)
12
+ @tex_name = upload(image)
13
+ end
14
+
15
+ private
16
+
17
+ def upload(image)
18
+ name_buffer = "\x00" * 4
19
+ glGenTextures(1, name_buffer)
20
+ tex_name = name_buffer.unpack1('L')
21
+
22
+ # Garder les pixels dans une variable (pas `image.to_blob` inline
23
+ # en argument) : sinon Ruby peut libérer la String pendant
24
+ # l'appel FFI, avant qu'OpenGL l'ait lue (texture à zéro, sans
25
+ # erreur OpenGL).
26
+ pixels = image.to_blob
27
+
28
+ glBindTexture(GL_TEXTURE_2D, tex_name)
29
+ configure_wrapping
30
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)
31
+
32
+ tex_name
33
+ end
34
+
35
+ def configure_wrapping
36
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
37
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
38
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
39
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bestguigui
4
+ # Charge un modèle .obj/.mtl et le dessine via un VBO : géométrie
5
+ class ObjModel
6
+ # position (3) + texcoord (2) + normale (3) par sommet.
7
+ FLOATS_PER_VERTEX = 8
8
+ STRIDE = FLOATS_PER_VERTEX * 4 # 4 octets par float
9
+
10
+ def initialize(obj_path)
11
+ super()
12
+ @vertices = []
13
+ @tex_coords = []
14
+ @normals = []
15
+ @faces = []
16
+ @texture = nil
17
+
18
+ parse_obj(obj_path)
19
+ @vertex_count = @faces.size
20
+ @vbo = build_vertex_buffer
21
+ end
22
+
23
+ def draw
24
+ glEnable(GL_TEXTURE_2D)
25
+ glBindTexture(GL_TEXTURE_2D, @texture.tex_name)
26
+ glBindBuffer(GL_ARRAY_BUFFER, @vbo)
27
+
28
+ enable_vertex_arrays
29
+ glDrawArrays(GL_TRIANGLES, 0, @vertex_count)
30
+ disable_vertex_arrays
31
+
32
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
33
+ glDisable(GL_TEXTURE_2D)
34
+ end
35
+
36
+ private
37
+
38
+ def parse_obj(path)
39
+ directory = File.dirname(path)
40
+
41
+ File.foreach(path) do |line|
42
+ tokens = line.split
43
+ next if tokens.empty?
44
+
45
+ keyword = tokens.shift
46
+ parse_line(keyword, tokens, directory)
47
+ end
48
+ end
49
+
50
+ def parse_line(keyword, tokens, directory)
51
+ case keyword
52
+ when 'v', 'vt', 'vn' then parse_coordinates(keyword, tokens)
53
+ when 'f' then @faces.concat(parse_face(tokens))
54
+ when 'mtllib' then load_material(File.join(directory, tokens.first))
55
+ end
56
+ end
57
+
58
+ def parse_coordinates(keyword, tokens)
59
+ values = tokens.map(&:to_f)
60
+
61
+ case keyword
62
+ when 'v' then @vertices << values
63
+ when 'vt' then @tex_coords << values
64
+ when 'vn' then @normals << values
65
+ end
66
+ end
67
+
68
+ # "f" contient des groupes "v/vt/vn" (indices 1-indexés, vt/vn
69
+ # parfois absents).
70
+ def parse_face(tokens)
71
+ tokens.map do |group|
72
+ v, vt, vn = group.split('/').map { |i| i.nil? || i.empty? ? nil : i.to_i - 1 }
73
+ [v, vt, vn]
74
+ end
75
+ end
76
+
77
+ # Un seul matériau par modèle : la dernière texture (map_Kd) trouvée.
78
+ def load_material(path)
79
+ directory = File.dirname(path)
80
+
81
+ File.foreach(path) do |line|
82
+ tokens = line.split
83
+ next if tokens.empty? || tokens.shift != 'map_Kd'
84
+
85
+ @texture = GLTexture.new(File.join(directory, tokens.first))
86
+ end
87
+ end
88
+
89
+ # Buffer plat non indexé : les sommets partagés sont dupliqués, pas
90
+ # d'index buffer pour rester simple.
91
+ def build_vertex_buffer
92
+ data = @faces.flat_map { |v, vt, vn| vertex_attributes(v, vt, vn) }.pack('F*')
93
+
94
+ name_buffer = "\x00" * 4
95
+ glGenBuffers(1, name_buffer)
96
+ vbo = name_buffer.unpack1('L')
97
+
98
+ glBindBuffer(GL_ARRAY_BUFFER, vbo)
99
+ glBufferData(GL_ARRAY_BUFFER, data.bytesize, data, GL_STATIC_DRAW)
100
+
101
+ vbo
102
+ end
103
+
104
+ def vertex_attributes(vertex_index, tex_coord_index, normal_index)
105
+ position = @vertices[vertex_index]
106
+ tex_coord = tex_coord_index ? @tex_coords[tex_coord_index] : [0.0, 0.0]
107
+ normal = normal_index ? @normals[normal_index] : [0.0, 0.0, 1.0]
108
+
109
+ position + tex_coord + normal
110
+ end
111
+
112
+ def enable_vertex_arrays
113
+ glEnableClientState(GL_VERTEX_ARRAY)
114
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY)
115
+ glEnableClientState(GL_NORMAL_ARRAY)
116
+
117
+ glVertexPointer(3, GL_FLOAT, STRIDE, 0)
118
+ glTexCoordPointer(2, GL_FLOAT, STRIDE, 3 * 4)
119
+ glNormalPointer(GL_FLOAT, STRIDE, 5 * 4)
120
+ end
121
+
122
+ def disable_vertex_arrays
123
+ glDisableClientState(GL_NORMAL_ARRAY)
124
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY)
125
+ glDisableClientState(GL_VERTEX_ARRAY)
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bestguigui
4
+ # Instancie-la une fois, garde une référence, utilise-la dans draw :
5
+ # @renderer = Render3D.new
6
+ # @renderer.render_3d(self) { @renderer.look_at(...) ; @model.draw }
7
+ class Render3D
8
+ # fov : angle de champ de vision vertical, en degrés.
9
+ # clip : [proche, lointain] — tout ce qui est en dehors n'est pas dessiné.
10
+ def render_3d(window, z_order: 0, fov: 60.0, clip: [0.1, 1000.0], &block)
11
+ w, h = window.fullscreen? ? [Gosu.screen_width, Gosu.screen_height] : [window.width, window.height]
12
+ Gosu.gl(z_order) { render_3d_frame(w, h, fov, clip, &block) }
13
+ end
14
+
15
+ # À appeler dans le bloc de render_3d, avant de dessiner, pour
16
+ # positionner la caméra : eye = position, center = point regardé.
17
+ def look_at(eye, center, up_vector = [0, 1, 0])
18
+ gluLookAt(*eye, *center, *up_vector)
19
+ end
20
+
21
+ private
22
+
23
+ # Gosu.gl isole déjà l'état (glPushAttrib/glPopAttrib) et remet sa
24
+ # propre projection/modelview en sortant du bloc — pas besoin de
25
+ # push/pop matrice de notre côté, juste de poser nos valeurs.
26
+ def render_3d_frame(width, height, fov, clip)
27
+ glEnable(GL_DEPTH_TEST)
28
+ glDepthFunc(GL_LESS)
29
+
30
+ setup_projection(width, height, fov, clip)
31
+ glMatrixMode(GL_MODELVIEW)
32
+ glLoadIdentity
33
+
34
+ yield
35
+ end
36
+
37
+ def setup_projection(width, height, fov, clip)
38
+ near, far = clip
39
+
40
+ glMatrixMode(GL_PROJECTION)
41
+ glLoadIdentity
42
+ gluPerspective(fov, width.to_f / height, near, far)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bestguigui
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gosu'
4
+ require 'opengl'
5
+ require 'glu'
6
+
7
+ include OpenGL
8
+ include GLU
9
+
10
+ # Sans ça, les fonctions OpenGL au-delà de la version 1.1 plantent en
11
+ # silence (NoMethodError sur nil).
12
+ OpenGL.load_lib
13
+ GLU.load_lib
14
+
15
+ Dir[File.join(__dir__, 'gosu_jam_kit', '*.rb')].each { |fn| require_relative fn }
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bestguigui_jam_kit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bestguigui
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: gosu
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.4'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.4'
26
+ - !ruby/object:Gem::Dependency
27
+ name: opengl-bindings
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.6'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.6'
40
+ executables: []
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - lib/gosu_jam_kit.rb
45
+ - lib/gosu_jam_kit/assets.rb
46
+ - lib/gosu_jam_kit/gl_texture.rb
47
+ - lib/gosu_jam_kit/obj_model.rb
48
+ - lib/gosu_jam_kit/render_3d.rb
49
+ - lib/gosu_jam_kit/version.rb
50
+ homepage: https://github.com/guillaumequillet/bestguigui_jam_kit
51
+ licenses:
52
+ - MIT
53
+ metadata:
54
+ rubygems_mfa_required: 'true'
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '3.0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubygems_version: 4.0.16
70
+ specification_version: 4
71
+ summary: A Game Jam Starter Kit to optimize Game Jams
72
+ test_files: []