rbgl 0.1.0 → 1.0.1

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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +20 -2
  4. data/README.md +130 -74
  5. data/Rakefile +7 -0
  6. data/bench/renderer_bench.rb +283 -0
  7. data/docs/.nojekyll +1 -0
  8. data/docs/index.html +38 -0
  9. data/examples/array_test.rb +1 -1
  10. data/examples/color_test.rb +1 -1
  11. data/examples/cube_spinning.rb +1 -1
  12. data/examples/gradient.rb +1 -1
  13. data/examples/multi_return_test.rb +1 -1
  14. data/examples/plasma.rb +1 -1
  15. data/examples/sphere_raymarch.rb +1 -1
  16. data/examples/triangle_window.rb +1 -1
  17. data/exe/rbgl +42 -0
  18. data/lib/rbgl/cli/doctor.rb +155 -0
  19. data/lib/rbgl/engine/buffer.rb +150 -40
  20. data/lib/rbgl/engine/clip_space_clipper.rb +163 -0
  21. data/lib/rbgl/engine/context.rb +133 -63
  22. data/lib/rbgl/engine/framebuffer.rb +153 -48
  23. data/lib/rbgl/engine/immutable_color.rb +32 -0
  24. data/lib/rbgl/engine/pipeline.rb +38 -7
  25. data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +195 -0
  26. data/lib/rbgl/engine/rasterizer/line_renderer.rb +88 -0
  27. data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
  28. data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +136 -0
  29. data/lib/rbgl/engine/rasterizer.rb +65 -171
  30. data/lib/rbgl/engine/shader/base_shader.rb +51 -0
  31. data/lib/rbgl/engine/shader/builtins.rb +257 -0
  32. data/lib/rbgl/engine/shader/dynamic_data.rb +73 -0
  33. data/lib/rbgl/engine/shader.rb +5 -318
  34. data/lib/rbgl/engine/texture.rb +264 -51
  35. data/lib/rbgl/engine.rb +4 -1
  36. data/lib/rbgl/gui/backend.rb +21 -34
  37. data/lib/rbgl/gui/backend_factory.rb +89 -0
  38. data/lib/rbgl/gui/cocoa/backend.rb +59 -46
  39. data/lib/rbgl/gui/cocoa/key_mapper.rb +48 -0
  40. data/lib/rbgl/gui/event.rb +4 -2
  41. data/lib/rbgl/gui/file_backend/bmp_writer.rb +63 -0
  42. data/lib/rbgl/gui/file_backend/frame_writer.rb +35 -0
  43. data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
  44. data/lib/rbgl/gui/file_backend/ppm_writer.rb +26 -0
  45. data/lib/rbgl/gui/file_backend.rb +50 -55
  46. data/lib/rbgl/gui/wayland/backend.rb +214 -60
  47. data/lib/rbgl/gui/wayland/codec.rb +50 -0
  48. data/lib/rbgl/gui/wayland/connection.rb +188 -231
  49. data/lib/rbgl/gui/wayland/event_dispatcher.rb +165 -0
  50. data/lib/rbgl/gui/wayland/global_binder.rb +46 -0
  51. data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
  52. data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +63 -0
  53. data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
  54. data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
  55. data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +157 -0
  56. data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
  57. data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +60 -0
  58. data/lib/rbgl/gui/wayland/protocol_objects.rb +8 -0
  59. data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
  60. data/lib/rbgl/gui/window/render_loop.rb +111 -0
  61. data/lib/rbgl/gui/window.rb +74 -84
  62. data/lib/rbgl/gui/x11/atom_cache.rb +29 -0
  63. data/lib/rbgl/gui/x11/backend.rb +73 -49
  64. data/lib/rbgl/gui/x11/connection.rb +284 -219
  65. data/lib/rbgl/gui/x11/event_parser.rb +71 -0
  66. data/lib/rbgl/gui/x11/key_mapper.rb +48 -0
  67. data/lib/rbgl/gui/x11/pixel_encoder.rb +164 -0
  68. data/lib/rbgl/gui/x11/request_encoder.rb +155 -0
  69. data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
  70. data/lib/rbgl/gui/x11/transport.rb +81 -0
  71. data/lib/rbgl/gui/x11/x_authority.rb +107 -0
  72. data/lib/rbgl/gui.rb +3 -5
  73. data/lib/rbgl/version.rb +1 -1
  74. metadata +63 -24
  75. data/examples/chemical_heartbeat.rb +0 -166
  76. data/examples/dark_transit.rb +0 -166
  77. data/examples/fractured_orb.rb +0 -428
  78. data/examples/fractured_orb_rb.rb +0 -598
  79. data/examples/hexagonal_flow.rb +0 -333
  80. data/examples/teapot.rb +0 -362
  81. data/examples/teapot_mcu.rb +0 -344
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "rasterizer/attribute_interpolator"
4
+ require_relative "rasterizer/triangle_renderer"
5
+ require_relative "rasterizer/line_renderer"
6
+ require_relative "rasterizer/point_renderer"
7
+
3
8
  module RBGL
4
9
  module Engine
5
10
  class Rasterizer
@@ -8,112 +13,57 @@ module RBGL
8
13
  def initialize(framebuffer)
9
14
  @framebuffer = framebuffer
10
15
  @viewport = { x: 0, y: 0, width: framebuffer.width, height: framebuffer.height }
16
+ @attribute_interpolator = AttributeInterpolator.new
17
+ @triangle_renderer = TriangleRenderer.new(
18
+ framebuffer,
19
+ interpolator: @attribute_interpolator,
20
+ viewport_transform: method(:viewport_transform),
21
+ edge_function: method(:edge_function)
22
+ )
23
+ @line_renderer = LineRenderer.new(
24
+ framebuffer,
25
+ interpolator: @attribute_interpolator,
26
+ viewport_transform: method(:viewport_transform)
27
+ )
28
+ @point_renderer = PointRenderer.new(
29
+ framebuffer,
30
+ viewport_transform: method(:viewport_transform),
31
+ point_pixel_offsets: method(:point_pixel_offsets)
32
+ )
11
33
  end
12
34
 
13
- def rasterize_triangle(v0, v1, v2, fragment_shader, uniforms, cull_mode: :none)
14
- p0 = viewport_transform(v0[:position])
15
- p1 = viewport_transform(v1[:position])
16
- p2 = viewport_transform(v2[:position])
17
-
18
- min_x = [p0.x, p1.x, p2.x].min.floor.clamp(0, @framebuffer.width - 1)
19
- max_x = [p0.x, p1.x, p2.x].max.ceil.clamp(0, @framebuffer.width - 1)
20
- min_y = [p0.y, p1.y, p2.y].min.floor.clamp(0, @framebuffer.height - 1)
21
- max_y = [p0.y, p1.y, p2.y].max.ceil.clamp(0, @framebuffer.height - 1)
22
-
23
- area = edge_function(p0, p1, p2)
24
- return if area.abs < 1e-10
25
-
26
- case cull_mode
27
- when :back
28
- return if area < 0
29
- when :front
30
- return if area > 0
31
- end
32
-
33
- (min_y..max_y).each do |y|
34
- (min_x..max_x).each do |x|
35
- px = x + 0.5
36
- py = y + 0.5
37
- p = Larb::Vec2.new(px, py)
38
-
39
- w0 = edge_function(p1, p2, p)
40
- w1 = edge_function(p2, p0, p)
41
- w2 = edge_function(p0, p1, p)
42
-
43
- if (w0 >= 0 && w1 >= 0 && w2 >= 0) || (w0 <= 0 && w1 <= 0 && w2 <= 0)
44
- inv_area = 1.0 / area
45
- w0 *= inv_area
46
- w1 *= inv_area
47
- w2 *= inv_area
48
-
49
- depth = w0 * p0.z + w1 * p1.z + w2 * p2.z
50
-
51
- interpolated = interpolate_attributes(v0, v1, v2, w0, w1, w2)
52
-
53
- frag_output = fragment_shader.process(interpolated, uniforms)
54
- color = frag_output[:color]
55
-
56
- @framebuffer.write_pixel(x, y, color, depth)
57
- end
58
- end
59
- end
35
+ def resize(width, height)
36
+ @viewport = @viewport.merge(width: width, height: height)
60
37
  end
61
38
 
62
- def rasterize_line(v0, v1, fragment_shader, uniforms)
63
- p0 = viewport_transform(v0[:position])
64
- p1 = viewport_transform(v1[:position])
65
-
66
- x0 = p0.x.round
67
- y0 = p0.y.round
68
- x1 = p1.x.round
69
- y1 = p1.y.round
70
-
71
- dx = (x1 - x0).abs
72
- dy = -(y1 - y0).abs
73
- sx = x0 < x1 ? 1 : -1
74
- sy = y0 < y1 ? 1 : -1
75
- err = dx + dy
76
-
77
- total_dist = Math.sqrt((x1 - x0)**2 + (y1 - y0)**2)
78
-
79
- loop do
80
- current_dist = Math.sqrt((x0 - p0.x.round)**2 + (y0 - p0.y.round)**2)
81
- t = total_dist > 0 ? current_dist / total_dist : 0
82
-
83
- depth = p0.z + (p1.z - p0.z) * t
84
-
85
- interpolated = interpolate_line_attributes(v0, v1, t)
86
-
87
- frag_output = fragment_shader.process(interpolated, uniforms)
88
- @framebuffer.write_pixel(x0, y0, frag_output[:color], depth)
89
-
90
- break if x0 == x1 && y0 == y1
91
-
92
- e2 = 2 * err
93
- if e2 >= dy
94
- err += dy
95
- x0 += sx
96
- end
97
- if e2 <= dx
98
- err += dx
99
- y0 += sy
100
- end
101
- end
39
+ def rasterize_triangle(v0, v1, v2, fragment_shader, uniforms, cull_mode: :none,
40
+ depth_test: true, depth_write: true, blend_mode: :none)
41
+ @triangle_renderer.rasterize(v0, v1, v2, fragment_shader, uniforms, state(
42
+ cull_mode: cull_mode,
43
+ depth_test: depth_test,
44
+ depth_write: depth_write,
45
+ blend_mode: blend_mode
46
+ ))
102
47
  end
103
48
 
104
- def rasterize_point(vertex, fragment_shader, uniforms, size: 1)
105
- p = viewport_transform(vertex[:position])
106
- x = p.x.round
107
- y = p.y.round
108
- depth = p.z
49
+ def rasterize_line(v0, v1, fragment_shader, uniforms, cull_mode: :none,
50
+ depth_test: true, depth_write: true, blend_mode: :none)
51
+ @line_renderer.rasterize(v0, v1, fragment_shader, uniforms, state(
52
+ cull_mode: cull_mode,
53
+ depth_test: depth_test,
54
+ depth_write: depth_write,
55
+ blend_mode: blend_mode
56
+ ))
57
+ end
109
58
 
110
- half = size / 2
111
- (-half..half).each do |dy|
112
- (-half..half).each do |dx|
113
- frag_output = fragment_shader.process(vertex, uniforms)
114
- @framebuffer.write_pixel(x + dx, y + dy, frag_output[:color], depth)
115
- end
116
- end
59
+ def rasterize_point(vertex, fragment_shader, uniforms, size: 1,
60
+ depth_test: true, depth_write: true, blend_mode: :none)
61
+ @point_renderer.rasterize(vertex, fragment_shader, uniforms, {
62
+ size: size,
63
+ depth_test: depth_test,
64
+ depth_write: depth_write,
65
+ blend_mode: blend_mode
66
+ })
117
67
  end
118
68
 
119
69
  private
@@ -126,87 +76,31 @@ module RBGL
126
76
  end
127
77
 
128
78
  Larb::Vec3.new(
129
- (ndc.x + 1) * 0.5 * @viewport[:width] + @viewport[:x],
130
- (1 - ndc.y) * 0.5 * @viewport[:height] + @viewport[:y],
79
+ ((ndc.x + 1) * 0.5 * @viewport[:width]) + @viewport[:x],
80
+ ((1 - ndc.y) * 0.5 * @viewport[:height]) + @viewport[:y],
131
81
  (ndc.z + 1) * 0.5
132
82
  )
133
83
  end
134
84
 
135
85
  def edge_function(a, b, c)
136
- (c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)
137
- end
138
-
139
- def interpolate_attributes(v0, v1, v2, w0, w1, w2)
140
- result = ShaderIO.new
141
-
142
- all_keys = (v0.to_h.keys | v1.to_h.keys | v2.to_h.keys) - [:position]
143
-
144
- all_keys.each do |key|
145
- a0 = v0[key]
146
- a1 = v1[key]
147
- a2 = v2[key]
148
- next unless a0 && a1 && a2
149
-
150
- result[key] = interpolate_value(a0, a1, a2, w0, w1, w2)
151
- end
152
-
153
- result
86
+ ((c.x - a.x) * (b.y - a.y)) - ((c.y - a.y) * (b.x - a.x))
154
87
  end
155
88
 
156
- def interpolate_value(a, b, c, w0, w1, w2)
157
- case a
158
- when Larb::Vec2
159
- Larb::Vec2.new(
160
- a.x * w0 + b.x * w1 + c.x * w2,
161
- a.y * w0 + b.y * w1 + c.y * w2
162
- )
163
- when Larb::Vec3
164
- Larb::Vec3.new(
165
- a.x * w0 + b.x * w1 + c.x * w2,
166
- a.y * w0 + b.y * w1 + c.y * w2,
167
- a.z * w0 + b.z * w1 + c.z * w2
168
- )
169
- when Larb::Vec4
170
- Larb::Vec4.new(
171
- a.x * w0 + b.x * w1 + c.x * w2,
172
- a.y * w0 + b.y * w1 + c.y * w2,
173
- a.z * w0 + b.z * w1 + c.z * w2,
174
- a.w * w0 + b.w * w1 + c.w * w2
175
- )
176
- when Larb::Color
177
- Larb::Color.new(
178
- a.r * w0 + b.r * w1 + c.r * w2,
179
- a.g * w0 + b.g * w1 + c.g * w2,
180
- a.b * w0 + b.b * w1 + c.b * w2,
181
- a.a * w0 + b.a * w1 + c.a * w2
182
- )
183
- when Numeric
184
- a * w0 + b * w1 + c * w2
185
- else
186
- a
187
- end
89
+ def state(cull_mode:, depth_test:, depth_write:, blend_mode:)
90
+ {
91
+ cull_mode: cull_mode,
92
+ depth_test: depth_test,
93
+ depth_write: depth_write,
94
+ blend_mode: blend_mode
95
+ }
188
96
  end
189
97
 
190
- def interpolate_line_attributes(v0, v1, t)
191
- result = ShaderIO.new
192
- all_keys = (v0.to_h.keys | v1.to_h.keys) - [:position]
193
-
194
- all_keys.each do |key|
195
- a0 = v0[key]
196
- a1 = v1[key]
197
- next unless a0 && a1
198
-
199
- result[key] = case a0
200
- when Larb::Vec2, Larb::Vec3, Larb::Vec4, Larb::Color
201
- a0.lerp(a1, t)
202
- when Numeric
203
- a0 + (a1 - a0) * t
204
- else
205
- a0
206
- end
207
- end
208
-
209
- result
98
+ def point_pixel_offsets(size)
99
+ pixel_size = [size.to_i, 1].max
100
+ min_offset = -(pixel_size / 2.0).floor
101
+ max_offset = ((pixel_size - 1) / 2.0).floor
102
+ offsets = (min_offset..max_offset).to_a
103
+ [offsets, offsets]
210
104
  end
211
105
  end
212
106
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module Engine
5
+ class BaseShader
6
+ include ShaderBuiltins
7
+
8
+ def initialize(&block)
9
+ @process_block = block
10
+ end
11
+
12
+ def process(input, uniforms, output: nil)
13
+ output ||= ShaderIO.new
14
+ output.clear
15
+
16
+ instance_exec(input, uniforms, output, &@process_block)
17
+ finalize_output(output)
18
+ end
19
+
20
+ def self.create(&block)
21
+ new(&block)
22
+ end
23
+
24
+ private
25
+
26
+ def finalize_output(output)
27
+ output
28
+ end
29
+ end
30
+
31
+ class VertexShader < BaseShader
32
+ private
33
+
34
+ def finalize_output(output)
35
+ raise "VertexShader must set output.position" unless output[:position]
36
+
37
+ output
38
+ end
39
+ end
40
+
41
+ class FragmentShader < BaseShader
42
+ private
43
+
44
+ def finalize_output(output)
45
+ output[:color] ||= Larb::Color.white
46
+
47
+ output
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module Engine
5
+ module ShaderBuiltins
6
+ def vec2(x, y = nil)
7
+ y ||= x
8
+ Larb::Vec2.new(x, y)
9
+ end
10
+
11
+ def vec3(x, y = nil, z = nil)
12
+ if y.nil? && z.nil?
13
+ Larb::Vec3.new(x, x, x)
14
+ elsif z.nil? && y.is_a?(Larb::Vec2)
15
+ Larb::Vec3.new(x, y.x, y.y)
16
+ else
17
+ Larb::Vec3.new(x, y, z)
18
+ end
19
+ end
20
+
21
+ def vec4(x, y = nil, z = nil, w = nil)
22
+ case
23
+ when y.nil? && z.nil? && w.nil?
24
+ Larb::Vec4.new(x, x, x, x)
25
+ when x.is_a?(Larb::Vec3) && !y.nil?
26
+ Larb::Vec4.new(x.x, x.y, x.z, y)
27
+ when x.is_a?(Larb::Vec2) && y.is_a?(Larb::Vec2)
28
+ Larb::Vec4.new(x.x, x.y, y.x, y.y)
29
+ else
30
+ Larb::Vec4.new(x, y, z, w)
31
+ end
32
+ end
33
+
34
+ def dot(a, b)
35
+ a.dot(b)
36
+ end
37
+
38
+ def cross(a, b)
39
+ a.cross(b)
40
+ end
41
+
42
+ def normalize(v)
43
+ v.normalize
44
+ end
45
+
46
+ def length(v)
47
+ v.length
48
+ end
49
+
50
+ def reflect(v, n)
51
+ v.reflect(n)
52
+ end
53
+
54
+ def refract(v, n, eta)
55
+ cos_i = -dot(n, v)
56
+ sin_t2 = eta * eta * (1.0 - (cos_i * cos_i))
57
+ return v * 0 if sin_t2 > 1.0
58
+
59
+ cos_t = Math.sqrt(1.0 - sin_t2)
60
+ (v * eta) + (n * ((eta * cos_i) - cos_t))
61
+ end
62
+
63
+ def mix(a, b, t)
64
+ case a
65
+ when Numeric then a + ((b - a) * t)
66
+ when Larb::Vec2, Larb::Vec3, Larb::Vec4, Larb::Color then a.lerp(b, t)
67
+ else
68
+ raise ArgumentError, "Unsupported mix operand: #{a.class}"
69
+ end
70
+ end
71
+
72
+ def clamp(v, min_val, max_val)
73
+ case v
74
+ when Numeric then v.clamp(min_val, max_val)
75
+ when Larb::Color then Larb::Color.new(*v.to_a.map { |component| component.clamp(min_val, max_val) })
76
+ else
77
+ component_map(v) { |component| component.clamp(min_val, max_val) }
78
+ end
79
+ end
80
+
81
+ def saturate(v)
82
+ clamp(v, 0.0, 1.0)
83
+ end
84
+
85
+ def smoothstep(edge0, edge1, x)
86
+ component_ternary_map(edge0, edge1, x) do |e0, e1, value|
87
+ next value < e0 ? 0.0 : 1.0 if e0 == e1
88
+
89
+ t = (value - e0).fdiv(e1 - e0).clamp(0.0, 1.0)
90
+ t * t * (3.0 - (2.0 * t))
91
+ end
92
+ end
93
+
94
+ def step(edge, x)
95
+ component_binary_map(edge, x) { |limit, value| value < limit ? 0.0 : 1.0 }
96
+ end
97
+
98
+ def fract(x)
99
+ component_map(x) { |value| value - value.floor }
100
+ end
101
+
102
+ def mod(x, y)
103
+ component_binary_map(x, y) { |left, right| left - (right * (left / right).floor) }
104
+ end
105
+
106
+ def abs(x)
107
+ component_map(x, &:abs)
108
+ end
109
+
110
+ def sign(x)
111
+ component_map(x) { |value| value <=> 0 }
112
+ end
113
+
114
+ def floor(x)
115
+ component_map(x, &:floor)
116
+ end
117
+
118
+ def ceil(x)
119
+ component_map(x, &:ceil)
120
+ end
121
+
122
+ def pow(x, y)
123
+ component_binary_map(x, y) { |left, right| left**right }
124
+ end
125
+
126
+ def sqrt(x)
127
+ component_map(x) { |value| Math.sqrt(value) }
128
+ end
129
+
130
+ def sin(x)
131
+ component_map(x) { |value| Math.sin(value) }
132
+ end
133
+
134
+ def cos(x)
135
+ component_map(x) { |value| Math.cos(value) }
136
+ end
137
+
138
+ def tan(x)
139
+ component_map(x) { |value| Math.tan(value) }
140
+ end
141
+
142
+ def asin(x)
143
+ component_map(x) { |value| Math.asin(value) }
144
+ end
145
+
146
+ def acos(x)
147
+ component_map(x) { |value| Math.acos(value) }
148
+ end
149
+
150
+ def atan(y, x = nil)
151
+ if x
152
+ component_binary_map(y, x) { |left, right| Math.atan2(left, right) }
153
+ else
154
+ component_map(y) { |value| Math.atan(value) }
155
+ end
156
+ end
157
+
158
+ def min(*args)
159
+ values = args.flatten
160
+ return values.min unless values.any? { |value| vector_value?(value) }
161
+
162
+ values.reduce { |current, value| component_binary_map(current, value) { |left, right| [left, right].min } }
163
+ end
164
+
165
+ def max(*args)
166
+ values = args.flatten
167
+ return values.max unless values.any? { |value| vector_value?(value) }
168
+
169
+ values.reduce { |current, value| component_binary_map(current, value) { |left, right| [left, right].max } }
170
+ end
171
+
172
+ def texture(tex, uv)
173
+ tex.sample(uv.x, uv.y)
174
+ end
175
+
176
+ def texture_lod(tex, uv, lod)
177
+ tex.sample(uv.x, uv.y, lod: lod)
178
+ end
179
+
180
+ def rgb(r, g, b)
181
+ Larb::Color.rgb(r, g, b)
182
+ end
183
+
184
+ def rgba(r, g, b, a)
185
+ Larb::Color.rgba(r, g, b, a)
186
+ end
187
+
188
+ def color_from_vec3(v)
189
+ Larb::Color.from_vec3(v)
190
+ end
191
+
192
+ def color_from_vec4(v)
193
+ Larb::Color.from_vec4(v)
194
+ end
195
+
196
+ private
197
+
198
+ def component_map(value)
199
+ return yield(value) unless vector_value?(value)
200
+
201
+ rebuild_vector(value, vector_components(value).map { |component| yield(component) })
202
+ end
203
+
204
+ def component_binary_map(left, right)
205
+ return yield(left, right) unless vector_value?(left) || vector_value?(right)
206
+
207
+ template = left if vector_value?(left)
208
+ template ||= right
209
+
210
+ rebuilt_components = vector_components(template).each_index.map do |index|
211
+ yield(component_at(left, index), component_at(right, index))
212
+ end
213
+
214
+ rebuild_vector(template, rebuilt_components)
215
+ end
216
+
217
+ def component_ternary_map(first, second, third)
218
+ return yield(first, second, third) unless [first, second, third].any? { |value| vector_value?(value) }
219
+
220
+ template = [first, second, third].find { |value| vector_value?(value) }
221
+ rebuilt_components = vector_components(template).each_index.map do |index|
222
+ yield(component_at(first, index), component_at(second, index), component_at(third, index))
223
+ end
224
+
225
+ rebuild_vector(template, rebuilt_components)
226
+ end
227
+
228
+ def vector_value?(value)
229
+ value.is_a?(Larb::Vec2) || value.is_a?(Larb::Vec3) || value.is_a?(Larb::Vec4)
230
+ end
231
+
232
+ def vector_components(value)
233
+ case value
234
+ when Larb::Vec2 then [value.x, value.y]
235
+ when Larb::Vec3 then [value.x, value.y, value.z]
236
+ when Larb::Vec4 then [value.x, value.y, value.z, value.w]
237
+ else
238
+ []
239
+ end
240
+ end
241
+
242
+ def component_at(value, index)
243
+ return value unless vector_value?(value)
244
+
245
+ vector_components(value)[index]
246
+ end
247
+
248
+ def rebuild_vector(template, components)
249
+ case template
250
+ when Larb::Vec2 then Larb::Vec2.new(*components)
251
+ when Larb::Vec3 then Larb::Vec3.new(*components)
252
+ when Larb::Vec4 then Larb::Vec4.new(*components)
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module Engine
5
+ class DynamicData
6
+ def initialize(data = {})
7
+ @data = {}
8
+ @writer_keys = {}
9
+ data.to_h.each { |key, value| self[key] = value }
10
+ end
11
+
12
+ def method_missing(name, *args)
13
+ key = normalize_key(name)
14
+ if args.empty? && @data.key?(key)
15
+ @data[key]
16
+ elsif (writer = writer_key(name))
17
+ self[writer] = args.first
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ def respond_to_missing?(name, include_private = false)
24
+ @data.key?(normalize_key(name)) || !writer_key(name).nil? || super
25
+ end
26
+
27
+ def [](key)
28
+ @data[normalize_key(key)]
29
+ end
30
+
31
+ def []=(key, value)
32
+ @data[normalize_key(key)] = value
33
+ end
34
+
35
+ def to_h
36
+ @data.dup
37
+ end
38
+
39
+ def keys
40
+ @data.keys
41
+ end
42
+
43
+ def clear
44
+ @data.clear
45
+ self
46
+ end
47
+
48
+ private
49
+
50
+ def normalize_key(key)
51
+ return key if key.is_a?(Symbol)
52
+
53
+ key.to_s.chomp("=").to_sym
54
+ end
55
+
56
+ def writer_key(name)
57
+ return @writer_keys[name] if @writer_keys.key?(name)
58
+
59
+ text = name.to_s
60
+ @writer_keys[name] = text.end_with?("=") ? text.chomp("=").to_sym : nil
61
+ end
62
+ end
63
+
64
+ class ShaderIO < DynamicData
65
+ end
66
+
67
+ class Uniforms < DynamicData
68
+ def merge(other)
69
+ Uniforms.new(@data.merge(other.to_h))
70
+ end
71
+ end
72
+ end
73
+ end