rbgl 0.1.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +13 -1
- data/lib/rbgl/engine/buffer.rb +102 -33
- data/lib/rbgl/engine/clip_space_clipper.rb +143 -0
- data/lib/rbgl/engine/context.rb +117 -64
- data/lib/rbgl/engine/framebuffer.rb +41 -32
- data/lib/rbgl/engine/pipeline.rb +38 -7
- data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +105 -0
- data/lib/rbgl/engine/rasterizer/line_renderer.rb +72 -0
- data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
- data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +87 -0
- data/lib/rbgl/engine/rasterizer.rb +73 -162
- data/lib/rbgl/engine/shader/base_shader.rb +55 -0
- data/lib/rbgl/engine/shader/builtins.rb +254 -0
- data/lib/rbgl/engine/shader/dynamic_data.rb +65 -0
- data/lib/rbgl/engine/shader.rb +5 -318
- data/lib/rbgl/engine/texture.rb +227 -38
- data/lib/rbgl/engine.rb +1 -0
- data/lib/rbgl/gui/backend.rb +12 -30
- data/lib/rbgl/gui/backend_factory.rb +85 -0
- data/lib/rbgl/gui/cocoa/backend.rb +15 -35
- data/lib/rbgl/gui/file_backend/bmp_writer.rb +63 -0
- data/lib/rbgl/gui/file_backend/frame_writer.rb +28 -0
- data/lib/rbgl/gui/file_backend/ppm_writer.rb +25 -0
- data/lib/rbgl/gui/file_backend.rb +25 -48
- data/lib/rbgl/gui/wayland/backend.rb +108 -26
- data/lib/rbgl/gui/wayland/codec.rb +54 -0
- data/lib/rbgl/gui/wayland/connection.rb +80 -240
- data/lib/rbgl/gui/wayland/event_dispatcher.rb +74 -0
- data/lib/rbgl/gui/wayland/global_binder.rb +44 -0
- data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +51 -0
- data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
- data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +146 -0
- data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
- data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +45 -0
- data/lib/rbgl/gui/wayland/protocol_objects.rb +7 -0
- data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
- data/lib/rbgl/gui/window/render_loop.rb +58 -0
- data/lib/rbgl/gui/window.rb +41 -82
- data/lib/rbgl/gui/x11/atom_cache.rb +26 -0
- data/lib/rbgl/gui/x11/backend.rb +14 -28
- data/lib/rbgl/gui/x11/connection.rb +104 -169
- data/lib/rbgl/gui/x11/event_parser.rb +60 -0
- data/lib/rbgl/gui/x11/request_encoder.rb +154 -0
- data/lib/rbgl/gui/x11/transport.rb +31 -0
- data/lib/rbgl/gui.rb +1 -0
- data/lib/rbgl/version.rb +1 -1
- metadata +31 -4
data/lib/rbgl/engine/shader.rb
CHANGED
|
@@ -1,324 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "shader/dynamic_data"
|
|
4
|
+
require_relative "shader/builtins"
|
|
5
|
+
require_relative "shader/base_shader"
|
|
6
|
+
|
|
3
7
|
module RBGL
|
|
4
8
|
module Engine
|
|
5
|
-
|
|
6
|
-
def initialize
|
|
7
|
-
@data = {}
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def method_missing(name, *args)
|
|
11
|
-
if name.to_s.end_with?("=")
|
|
12
|
-
@data[name.to_s.chomp("=").to_sym] = args.first
|
|
13
|
-
else
|
|
14
|
-
@data[name]
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def respond_to_missing?(_name, _include_private = false)
|
|
19
|
-
true
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def [](key)
|
|
23
|
-
@data[key]
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def []=(key, value)
|
|
27
|
-
@data[key] = value
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def to_h
|
|
31
|
-
@data.dup
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def keys
|
|
35
|
-
@data.keys
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
class Uniforms
|
|
40
|
-
def initialize(data = {})
|
|
41
|
-
@data = data.transform_keys(&:to_sym)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def method_missing(name, *args)
|
|
45
|
-
if name.to_s.end_with?("=")
|
|
46
|
-
@data[name.to_s.chomp("=").to_sym] = args.first
|
|
47
|
-
else
|
|
48
|
-
@data[name]
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def respond_to_missing?(_name, _include_private = false)
|
|
53
|
-
true
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def [](key)
|
|
57
|
-
@data[key.to_sym]
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def []=(key, value)
|
|
61
|
-
@data[key.to_sym] = value
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def merge(other)
|
|
65
|
-
Uniforms.new(@data.merge(other.to_h))
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def to_h
|
|
69
|
-
@data.dup
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
module ShaderBuiltins
|
|
74
|
-
def vec2(x, y = nil)
|
|
75
|
-
y ||= x
|
|
76
|
-
Larb::Vec2.new(x, y)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def vec3(x, y = nil, z = nil)
|
|
80
|
-
if y.nil? && z.nil?
|
|
81
|
-
Larb::Vec3.new(x, x, x)
|
|
82
|
-
elsif z.nil? && y.is_a?(Larb::Vec2)
|
|
83
|
-
Larb::Vec3.new(x, y.x, y.y)
|
|
84
|
-
else
|
|
85
|
-
Larb::Vec3.new(x, y, z)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def vec4(x, y = nil, z = nil, w = nil)
|
|
90
|
-
case
|
|
91
|
-
when y.nil? && z.nil? && w.nil?
|
|
92
|
-
Larb::Vec4.new(x, x, x, x)
|
|
93
|
-
when x.is_a?(Larb::Vec3) && !y.nil?
|
|
94
|
-
Larb::Vec4.new(x.x, x.y, x.z, y)
|
|
95
|
-
when x.is_a?(Larb::Vec2) && y.is_a?(Larb::Vec2)
|
|
96
|
-
Larb::Vec4.new(x.x, x.y, y.x, y.y)
|
|
97
|
-
else
|
|
98
|
-
Larb::Vec4.new(x, y, z, w)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def dot(a, b)
|
|
103
|
-
a.dot(b)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def cross(a, b)
|
|
107
|
-
a.cross(b)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def normalize(v)
|
|
111
|
-
v.normalize
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def length(v)
|
|
115
|
-
v.length
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def reflect(v, n)
|
|
119
|
-
v.reflect(n)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def refract(v, n, eta)
|
|
123
|
-
cos_i = -dot(n, v)
|
|
124
|
-
sin_t2 = eta * eta * (1.0 - cos_i * cos_i)
|
|
125
|
-
return vec3(0) if sin_t2 > 1.0
|
|
126
|
-
|
|
127
|
-
cos_t = Math.sqrt(1.0 - sin_t2)
|
|
128
|
-
v * eta + n * (eta * cos_i - cos_t)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def mix(a, b, t)
|
|
132
|
-
case a
|
|
133
|
-
when Numeric then a + (b - a) * t
|
|
134
|
-
when Larb::Vec2, Larb::Vec3, Larb::Vec4 then a.lerp(b, t)
|
|
135
|
-
when Larb::Color then a.lerp(b, t)
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
alias lerp mix
|
|
139
|
-
|
|
140
|
-
def clamp(v, min_val, max_val)
|
|
141
|
-
case v
|
|
142
|
-
when Numeric then v.clamp(min_val, max_val)
|
|
143
|
-
when Larb::Vec3
|
|
144
|
-
Larb::Vec3.new(
|
|
145
|
-
v.x.clamp(min_val, max_val),
|
|
146
|
-
v.y.clamp(min_val, max_val),
|
|
147
|
-
v.z.clamp(min_val, max_val)
|
|
148
|
-
)
|
|
149
|
-
when Larb::Color then v.clamp
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def saturate(v)
|
|
154
|
-
clamp(v, 0.0, 1.0)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def smoothstep(edge0, edge1, x)
|
|
158
|
-
t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0)
|
|
159
|
-
t * t * (3.0 - 2.0 * t)
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def step(edge, x)
|
|
163
|
-
x < edge ? 0.0 : 1.0
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def fract(x)
|
|
167
|
-
x - x.floor
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def mod(x, y)
|
|
171
|
-
x - y * (x / y).floor
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def abs(x)
|
|
175
|
-
case x
|
|
176
|
-
when Numeric then x.abs
|
|
177
|
-
when Larb::Vec3 then Larb::Vec3.new(x.x.abs, x.y.abs, x.z.abs)
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def sign(x)
|
|
182
|
-
x <=> 0
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def floor(x)
|
|
186
|
-
case x
|
|
187
|
-
when Numeric then x.floor
|
|
188
|
-
when Larb::Vec3 then Larb::Vec3.new(x.x.floor, x.y.floor, x.z.floor)
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
def ceil(x)
|
|
193
|
-
case x
|
|
194
|
-
when Numeric then x.ceil
|
|
195
|
-
when Larb::Vec3 then Larb::Vec3.new(x.x.ceil, x.y.ceil, x.z.ceil)
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def pow(x, y)
|
|
200
|
-
case x
|
|
201
|
-
when Numeric then x**y
|
|
202
|
-
when Larb::Vec3 then Larb::Vec3.new(x.x**y, x.y**y, x.z**y)
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def sqrt(x)
|
|
207
|
-
case x
|
|
208
|
-
when Numeric then Math.sqrt(x)
|
|
209
|
-
when Larb::Vec3 then Larb::Vec3.new(Math.sqrt(x.x), Math.sqrt(x.y), Math.sqrt(x.z))
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def sin(x)
|
|
214
|
-
Math.sin(x)
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def cos(x)
|
|
218
|
-
Math.cos(x)
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def tan(x)
|
|
222
|
-
Math.tan(x)
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def asin(x)
|
|
226
|
-
Math.asin(x)
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
def acos(x)
|
|
230
|
-
Math.acos(x)
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
def atan(y, x = nil)
|
|
234
|
-
x ? Math.atan2(y, x) : Math.atan(y)
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def min(*args)
|
|
238
|
-
args.flatten.min
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
def max(*args)
|
|
242
|
-
args.flatten.max
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
def texture(tex, uv)
|
|
246
|
-
tex.sample(uv.x, uv.y)
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
def texture_lod(tex, uv, lod)
|
|
250
|
-
tex.sample(uv.x, uv.y, lod: lod)
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def rgb(r, g, b)
|
|
254
|
-
Larb::Color.rgb(r, g, b)
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def rgba(r, g, b, a)
|
|
258
|
-
Larb::Color.rgba(r, g, b, a)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
def color_from_vec3(v)
|
|
262
|
-
Larb::Color.from_vec3(v)
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
def color_from_vec4(v)
|
|
266
|
-
Larb::Color.from_vec4(v)
|
|
267
|
-
end
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
class VertexShader
|
|
271
|
-
include ShaderBuiltins
|
|
272
|
-
|
|
273
|
-
def initialize(&block)
|
|
274
|
-
@process_block = block
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
def process(input, uniforms)
|
|
278
|
-
output = ShaderIO.new
|
|
279
|
-
@input = input
|
|
280
|
-
@uniforms = uniforms
|
|
281
|
-
@output = output
|
|
282
|
-
|
|
283
|
-
instance_exec(input, uniforms, output, &@process_block)
|
|
284
|
-
|
|
285
|
-
raise "VertexShader must set output.position" unless output[:position]
|
|
286
|
-
|
|
287
|
-
output
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
attr_reader :input, :uniforms, :output
|
|
291
|
-
|
|
292
|
-
def self.create(&block)
|
|
293
|
-
new(&block)
|
|
294
|
-
end
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
class FragmentShader
|
|
298
|
-
include ShaderBuiltins
|
|
299
|
-
|
|
300
|
-
def initialize(&block)
|
|
301
|
-
@process_block = block
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
def process(input, uniforms)
|
|
305
|
-
output = ShaderIO.new
|
|
306
|
-
@input = input
|
|
307
|
-
@uniforms = uniforms
|
|
308
|
-
@output = output
|
|
309
|
-
|
|
310
|
-
instance_exec(input, uniforms, output, &@process_block)
|
|
311
|
-
|
|
312
|
-
output[:color] ||= Larb::Color.white
|
|
313
|
-
|
|
314
|
-
output
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
attr_reader :input, :uniforms, :output
|
|
318
|
-
|
|
319
|
-
def self.create(&block)
|
|
320
|
-
new(&block)
|
|
321
|
-
end
|
|
322
|
-
end
|
|
9
|
+
private_constant :DynamicData
|
|
323
10
|
end
|
|
324
11
|
end
|
data/lib/rbgl/engine/texture.rb
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
module RBGL
|
|
4
4
|
module Engine
|
|
5
5
|
class Texture
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
PPM_HEADER = /\A(P[36])(?:\s+|#[^\n]*\n)+(\d+)(?:\s+|#[^\n]*\n)+(\d+)(?:\s+|#[^\n]*\n)+(\d+)\s/m
|
|
7
|
+
WRAP_MODES = %i[repeat clamp mirror].freeze
|
|
8
|
+
FILTER_MODES = %i[nearest linear].freeze
|
|
9
|
+
|
|
10
|
+
attr_reader :width, :height, :data, :wrap_s, :wrap_t, :filter_min, :filter_mag
|
|
8
11
|
|
|
9
12
|
WRAP_REPEAT = :repeat
|
|
10
13
|
WRAP_CLAMP = :clamp
|
|
@@ -16,7 +19,9 @@ module RBGL
|
|
|
16
19
|
def initialize(width, height, data = nil)
|
|
17
20
|
@width = width
|
|
18
21
|
@height = height
|
|
19
|
-
@data = data
|
|
22
|
+
@data = data ? normalize_data!(data) : Array.new(width * height) { Larb::Color.black }
|
|
23
|
+
@levels = [@data]
|
|
24
|
+
@mipmaps_dirty = true
|
|
20
25
|
@wrap_s = WRAP_REPEAT
|
|
21
26
|
@wrap_t = WRAP_REPEAT
|
|
22
27
|
@filter_min = FILTER_LINEAR
|
|
@@ -26,48 +31,54 @@ module RBGL
|
|
|
26
31
|
def sample(u, v, lod: 0)
|
|
27
32
|
u = wrap_coord(u, @wrap_s)
|
|
28
33
|
v = wrap_coord(v, @wrap_t)
|
|
34
|
+
level = mip_level_for(lod)
|
|
35
|
+
level_width = width_for_level(level)
|
|
36
|
+
level_height = height_for_level(level)
|
|
29
37
|
|
|
30
|
-
x = u * (
|
|
31
|
-
y = v * (
|
|
38
|
+
x = u * (level_width - 1)
|
|
39
|
+
y = v * (level_height - 1)
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
sample_nearest(x, y)
|
|
35
|
-
else
|
|
36
|
-
sample_bilinear(x, y)
|
|
37
|
-
end
|
|
41
|
+
sample_with_filter(level, x, y, filter_for_lod(lod))
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
def get_pixel(x, y)
|
|
41
|
-
|
|
42
|
-
y = y.clamp(0, @height - 1).to_i
|
|
43
|
-
@data[y * @width + x]
|
|
45
|
+
pixel_for_level(0, x, y)
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
def set_pixel(x, y, color)
|
|
47
49
|
return if x < 0 || x >= @width || y < 0 || y >= @height
|
|
48
50
|
|
|
49
|
-
@data[y.to_i * @width + x.to_i] = color
|
|
51
|
+
@data[y.to_i * @width + x.to_i] = validate_color!(color)
|
|
52
|
+
@mipmaps_dirty = true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def generate_mipmaps!
|
|
56
|
+
rebuild_mipmap_chain!
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def wrap_s=(mode)
|
|
61
|
+
@wrap_s = validate_wrap_mode!(mode)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def wrap_t=(mode)
|
|
65
|
+
@wrap_t = validate_wrap_mode!(mode)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def filter_min=(filter)
|
|
69
|
+
@filter_min = validate_filter_mode!(filter)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def filter_mag=(filter)
|
|
73
|
+
@filter_mag = validate_filter_mode!(filter)
|
|
50
74
|
end
|
|
51
75
|
|
|
52
76
|
def self.from_ppm(filename)
|
|
53
|
-
content = File.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
_format = lines.shift.strip
|
|
57
|
-
dimensions = lines.shift.strip.split.map(&:to_i)
|
|
58
|
-
width, height = dimensions
|
|
59
|
-
max_val = lines.shift.strip.to_i
|
|
60
|
-
|
|
61
|
-
data = []
|
|
62
|
-
pixels = lines.join.split.map(&:to_i)
|
|
63
|
-
(pixels.size / 3).times do |i|
|
|
64
|
-
r = pixels[i * 3] / max_val.to_f
|
|
65
|
-
g = pixels[i * 3 + 1] / max_val.to_f
|
|
66
|
-
b = pixels[i * 3 + 2] / max_val.to_f
|
|
67
|
-
data << Larb::Color.rgb(r, g, b)
|
|
68
|
-
end
|
|
77
|
+
content = File.binread(filename)
|
|
78
|
+
format, width, height, max_val, body_offset = parse_ppm_header(content)
|
|
79
|
+
samples = parse_ppm_samples(content, format, width * height, max_val, body_offset)
|
|
69
80
|
|
|
70
|
-
new(width, height,
|
|
81
|
+
new(width, height, colors_from_ppm_samples(samples, max_val))
|
|
71
82
|
end
|
|
72
83
|
|
|
73
84
|
def self.checker(width, height, size, color1 = Larb::Color.white, color2 = Larb::Color.black)
|
|
@@ -85,8 +96,81 @@ module RBGL
|
|
|
85
96
|
new(width, height, Array.new(width * height) { color })
|
|
86
97
|
end
|
|
87
98
|
|
|
99
|
+
def self.parse_ppm_header(content)
|
|
100
|
+
match = PPM_HEADER.match(content)
|
|
101
|
+
raise ArgumentError, "Invalid PPM header" unless match
|
|
102
|
+
|
|
103
|
+
format = match[1]
|
|
104
|
+
width = match[2].to_i
|
|
105
|
+
height = match[3].to_i
|
|
106
|
+
max_val = match[4].to_i
|
|
107
|
+
raise ArgumentError, "Unsupported PPM max value: #{max_val}" unless max_val.between?(1, 65_535)
|
|
108
|
+
|
|
109
|
+
[format, width, height, max_val, match.end(0)]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def self.parse_ppm_samples(content, format, pixel_count, max_val, body_offset)
|
|
113
|
+
case format
|
|
114
|
+
when "P3"
|
|
115
|
+
parse_p3_samples(content.byteslice(body_offset..), pixel_count)
|
|
116
|
+
when "P6"
|
|
117
|
+
parse_p6_samples(content, body_offset, pixel_count, max_val)
|
|
118
|
+
else
|
|
119
|
+
raise ArgumentError, "Unsupported PPM format: #{format}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def self.parse_p3_samples(body, pixel_count)
|
|
124
|
+
samples = body.to_s.gsub(/#[^\n]*/, " ").scan(/\d+/).map(&:to_i)
|
|
125
|
+
expected_count = pixel_count * 3
|
|
126
|
+
raise ArgumentError, "PPM pixel data is truncated" if samples.length < expected_count
|
|
127
|
+
|
|
128
|
+
samples.first(expected_count)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.parse_p6_samples(content, body_offset, pixel_count, max_val)
|
|
132
|
+
sample_size = max_val < 256 ? 1 : 2
|
|
133
|
+
expected_bytes = pixel_count * 3 * sample_size
|
|
134
|
+
body = content.byteslice(body_offset, expected_bytes)
|
|
135
|
+
raise ArgumentError, "PPM pixel data is truncated" unless body && body.bytesize == expected_bytes
|
|
136
|
+
|
|
137
|
+
if sample_size == 1
|
|
138
|
+
body.bytes
|
|
139
|
+
else
|
|
140
|
+
body.unpack("n*")
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def self.colors_from_ppm_samples(samples, max_val)
|
|
145
|
+
samples.each_slice(3).map do |r, g, b|
|
|
146
|
+
Larb::Color.rgb(r / max_val.to_f, g / max_val.to_f, b / max_val.to_f)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private_class_method :parse_ppm_header, :parse_ppm_samples, :parse_p3_samples,
|
|
151
|
+
:parse_p6_samples, :colors_from_ppm_samples
|
|
152
|
+
|
|
88
153
|
private
|
|
89
154
|
|
|
155
|
+
def normalize_data!(data)
|
|
156
|
+
raise ArgumentError, "Texture data must be convertible to an Array" unless data.respond_to?(:to_a)
|
|
157
|
+
|
|
158
|
+
normalized = data.to_a
|
|
159
|
+
expected_size = @width * @height
|
|
160
|
+
|
|
161
|
+
unless normalized.size == expected_size
|
|
162
|
+
raise ArgumentError, "Texture data size mismatch: expected #{expected_size}, got #{normalized.size}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
normalized.map { |pixel| validate_color!(pixel) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def validate_color!(color)
|
|
169
|
+
return color if color.is_a?(Larb::Color)
|
|
170
|
+
|
|
171
|
+
raise ArgumentError, "Texture data must contain only Larb::Color values"
|
|
172
|
+
end
|
|
173
|
+
|
|
90
174
|
def wrap_coord(coord, mode)
|
|
91
175
|
case mode
|
|
92
176
|
when WRAP_REPEAT
|
|
@@ -99,11 +183,40 @@ module RBGL
|
|
|
99
183
|
end
|
|
100
184
|
end
|
|
101
185
|
|
|
102
|
-
def
|
|
103
|
-
|
|
186
|
+
def validate_wrap_mode!(mode)
|
|
187
|
+
normalized = mode.to_sym
|
|
188
|
+
return normalized if WRAP_MODES.include?(normalized)
|
|
189
|
+
|
|
190
|
+
raise ArgumentError, "Unsupported wrap mode: #{mode}"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def validate_filter_mode!(mode)
|
|
194
|
+
normalized = mode.to_sym
|
|
195
|
+
return normalized if FILTER_MODES.include?(normalized)
|
|
196
|
+
|
|
197
|
+
raise ArgumentError, "Unsupported filter mode: #{mode}"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def sample_nearest(level, x, y)
|
|
201
|
+
pixel_for_level(level, x.round, y.round)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def filter_for_lod(lod)
|
|
205
|
+
lod.to_f.positive? ? @filter_min : @filter_mag
|
|
104
206
|
end
|
|
105
207
|
|
|
106
|
-
def
|
|
208
|
+
def sample_with_filter(level, x, y, filter)
|
|
209
|
+
case filter
|
|
210
|
+
when FILTER_NEAREST
|
|
211
|
+
sample_nearest(level, x, y)
|
|
212
|
+
when FILTER_LINEAR
|
|
213
|
+
sample_bilinear(level, x, y)
|
|
214
|
+
else
|
|
215
|
+
raise ArgumentError, "Unsupported filter mode: #{filter}"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def sample_bilinear(level, x, y)
|
|
107
220
|
x0 = x.floor.to_i
|
|
108
221
|
y0 = y.floor.to_i
|
|
109
222
|
x1 = x0 + 1
|
|
@@ -111,15 +224,91 @@ module RBGL
|
|
|
111
224
|
fx = x - x0
|
|
112
225
|
fy = y - y0
|
|
113
226
|
|
|
114
|
-
c00 =
|
|
115
|
-
c10 =
|
|
116
|
-
c01 =
|
|
117
|
-
c11 =
|
|
227
|
+
c00 = pixel_for_level(level, x0, y0)
|
|
228
|
+
c10 = pixel_for_level(level, x1, y0)
|
|
229
|
+
c01 = pixel_for_level(level, x0, y1)
|
|
230
|
+
c11 = pixel_for_level(level, x1, y1)
|
|
118
231
|
|
|
119
232
|
c0 = c00.lerp(c10, fx)
|
|
120
233
|
c1 = c01.lerp(c11, fx)
|
|
121
234
|
c0.lerp(c1, fy)
|
|
122
235
|
end
|
|
236
|
+
|
|
237
|
+
def mip_level_for(lod)
|
|
238
|
+
return 0 unless lod.to_f.positive?
|
|
239
|
+
|
|
240
|
+
rebuild_mipmap_chain! if @mipmaps_dirty
|
|
241
|
+
[lod.floor, @levels.size - 1].min
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def pixel_for_level(level, x, y)
|
|
245
|
+
level_width = width_for_level(level)
|
|
246
|
+
level_height = height_for_level(level)
|
|
247
|
+
level_data = level.zero? ? @data : level_data(level)
|
|
248
|
+
clamped_x = x.clamp(0, level_width - 1).to_i
|
|
249
|
+
clamped_y = y.clamp(0, level_height - 1).to_i
|
|
250
|
+
level_data[clamped_y * level_width + clamped_x]
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def level_data(level)
|
|
254
|
+
rebuild_mipmap_chain! if @mipmaps_dirty
|
|
255
|
+
@levels[level] || @data
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def width_for_level(level)
|
|
259
|
+
[@width >> level, 1].max
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def height_for_level(level)
|
|
263
|
+
[@height >> level, 1].max
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def rebuild_mipmap_chain!
|
|
267
|
+
@levels = [@data]
|
|
268
|
+
|
|
269
|
+
current_data = @data
|
|
270
|
+
current_width = @width
|
|
271
|
+
current_height = @height
|
|
272
|
+
|
|
273
|
+
while current_width > 1 || current_height > 1
|
|
274
|
+
next_width = [current_width / 2, 1].max
|
|
275
|
+
next_height = [current_height / 2, 1].max
|
|
276
|
+
current_data = build_next_level(current_data, current_width, current_height, next_width, next_height)
|
|
277
|
+
@levels << current_data
|
|
278
|
+
current_width = next_width
|
|
279
|
+
current_height = next_height
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
@mipmaps_dirty = false
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def build_next_level(source_data, source_width, source_height, target_width, target_height)
|
|
286
|
+
Array.new(target_width * target_height) do |index|
|
|
287
|
+
x = index % target_width
|
|
288
|
+
y = index / target_width
|
|
289
|
+
average_texel_block(source_data, source_width, source_height, x * 2, y * 2)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def average_texel_block(source_data, source_width, source_height, start_x, start_y)
|
|
294
|
+
samples = []
|
|
295
|
+
|
|
296
|
+
2.times do |dy|
|
|
297
|
+
2.times do |dx|
|
|
298
|
+
x = [start_x + dx, source_width - 1].min
|
|
299
|
+
y = [start_y + dy, source_height - 1].min
|
|
300
|
+
samples << source_data[y * source_width + x]
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
sample_count = samples.length.to_f
|
|
305
|
+
Larb::Color.new(
|
|
306
|
+
samples.sum(&:r) / sample_count,
|
|
307
|
+
samples.sum(&:g) / sample_count,
|
|
308
|
+
samples.sum(&:b) / sample_count,
|
|
309
|
+
samples.sum(&:a) / sample_count
|
|
310
|
+
)
|
|
311
|
+
end
|
|
123
312
|
end
|
|
124
313
|
end
|
|
125
314
|
end
|
data/lib/rbgl/engine.rb
CHANGED