3rb 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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/3rb.gemspec +29 -0
  4. data/CHANGELOG.md +12 -0
  5. data/LICENSE +21 -0
  6. data/README.md +321 -0
  7. data/Rakefile +13 -0
  8. data/examples/01_hello_cube.rb +29 -0
  9. data/examples/02_basic_geometries.rb +56 -0
  10. data/examples/03_materials.rb +61 -0
  11. data/examples/04_lighting.rb +63 -0
  12. data/examples/05_animation.rb +79 -0
  13. data/examples/06_custom_shader.rb +92 -0
  14. data/examples/07_scene_graph.rb +74 -0
  15. data/examples/08_orbit_controls.rb +50 -0
  16. data/examples/09_3d_chart.rb +71 -0
  17. data/examples/10_procedural_terrain.rb +140 -0
  18. data/examples/11_particle_system.rb +68 -0
  19. data/examples/12_model_loader.rb +73 -0
  20. data/examples/13_game_prototype.rb +145 -0
  21. data/examples/14_utah_teapot.rb +291 -0
  22. data/examples/15_stanford_bunny.rb +200 -0
  23. data/examples/16_cornell_box.rb +373 -0
  24. data/examples/17_weird_fractal4.rb +130 -0
  25. data/examples/18_platonic_solids.rb +268 -0
  26. data/lib/3rb/animation/animation_clip.rb +287 -0
  27. data/lib/3rb/animation/animation_mixer.rb +366 -0
  28. data/lib/3rb/cameras/camera.rb +50 -0
  29. data/lib/3rb/cameras/orthographic_camera.rb +92 -0
  30. data/lib/3rb/cameras/perspective_camera.rb +103 -0
  31. data/lib/3rb/controls/orbit_controls.rb +341 -0
  32. data/lib/3rb/core/buffer_attribute.rb +172 -0
  33. data/lib/3rb/core/group.rb +9 -0
  34. data/lib/3rb/core/object3d.rb +298 -0
  35. data/lib/3rb/core/scene.rb +78 -0
  36. data/lib/3rb/dsl/helpers.rb +57 -0
  37. data/lib/3rb/dsl/scene_builder.rb +288 -0
  38. data/lib/3rb/ffi/glfw.rb +61 -0
  39. data/lib/3rb/ffi/opengl.rb +137 -0
  40. data/lib/3rb/ffi/platform.rb +65 -0
  41. data/lib/3rb/geometries/box_geometry.rb +101 -0
  42. data/lib/3rb/geometries/buffer_geometry.rb +345 -0
  43. data/lib/3rb/geometries/cone_geometry.rb +29 -0
  44. data/lib/3rb/geometries/cylinder_geometry.rb +149 -0
  45. data/lib/3rb/geometries/plane_geometry.rb +75 -0
  46. data/lib/3rb/geometries/sphere_geometry.rb +93 -0
  47. data/lib/3rb/geometries/torus_geometry.rb +77 -0
  48. data/lib/3rb/lights/ambient_light.rb +9 -0
  49. data/lib/3rb/lights/directional_light.rb +57 -0
  50. data/lib/3rb/lights/hemisphere_light.rb +26 -0
  51. data/lib/3rb/lights/light.rb +27 -0
  52. data/lib/3rb/lights/point_light.rb +68 -0
  53. data/lib/3rb/lights/rect_area_light.rb +35 -0
  54. data/lib/3rb/lights/spot_light.rb +88 -0
  55. data/lib/3rb/loaders/gltf_loader.rb +304 -0
  56. data/lib/3rb/loaders/loader.rb +94 -0
  57. data/lib/3rb/loaders/obj_loader.rb +186 -0
  58. data/lib/3rb/loaders/texture_loader.rb +55 -0
  59. data/lib/3rb/materials/basic_material.rb +70 -0
  60. data/lib/3rb/materials/lambert_material.rb +102 -0
  61. data/lib/3rb/materials/material.rb +114 -0
  62. data/lib/3rb/materials/phong_material.rb +106 -0
  63. data/lib/3rb/materials/shader_material.rb +104 -0
  64. data/lib/3rb/materials/standard_material.rb +106 -0
  65. data/lib/3rb/math/color.rb +246 -0
  66. data/lib/3rb/math/euler.rb +156 -0
  67. data/lib/3rb/math/math_utils.rb +132 -0
  68. data/lib/3rb/math/matrix3.rb +269 -0
  69. data/lib/3rb/math/matrix4.rb +501 -0
  70. data/lib/3rb/math/quaternion.rb +337 -0
  71. data/lib/3rb/math/vector2.rb +216 -0
  72. data/lib/3rb/math/vector3.rb +366 -0
  73. data/lib/3rb/math/vector4.rb +233 -0
  74. data/lib/3rb/native/gl.rb +382 -0
  75. data/lib/3rb/native/native.rb +55 -0
  76. data/lib/3rb/native/window.rb +111 -0
  77. data/lib/3rb/native.rb +9 -0
  78. data/lib/3rb/objects/line.rb +116 -0
  79. data/lib/3rb/objects/mesh.rb +40 -0
  80. data/lib/3rb/objects/points.rb +71 -0
  81. data/lib/3rb/renderers/opengl_renderer.rb +567 -0
  82. data/lib/3rb/renderers/renderer.rb +60 -0
  83. data/lib/3rb/renderers/shader_lib.rb +100 -0
  84. data/lib/3rb/textures/cube_texture.rb +26 -0
  85. data/lib/3rb/textures/data_texture.rb +35 -0
  86. data/lib/3rb/textures/render_target.rb +125 -0
  87. data/lib/3rb/textures/texture.rb +190 -0
  88. data/lib/3rb/version.rb +5 -0
  89. data/lib/3rb.rb +86 -0
  90. data/shaders/basic.frag +19 -0
  91. data/shaders/basic.vert +15 -0
  92. data/shaders/common/lights.glsl +53 -0
  93. data/shaders/common/uniforms.glsl +9 -0
  94. data/shaders/lambert.frag +37 -0
  95. data/shaders/lambert.vert +22 -0
  96. data/shaders/phong.frag +51 -0
  97. data/shaders/phong.vert +28 -0
  98. data/shaders/standard.frag +92 -0
  99. data/shaders/standard.vert +28 -0
  100. metadata +155 -0
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sunrb
4
+ class Color
5
+ attr_accessor :r, :g, :b
6
+
7
+ def initialize(r = nil, g = nil, b = nil)
8
+ if r.nil? && g.nil? && b.nil?
9
+ @r = @g = @b = 1.0
10
+ elsif g.nil? && b.nil?
11
+ if r.is_a?(Integer)
12
+ set_hex(r)
13
+ elsif r.is_a?(String)
14
+ set_style(r)
15
+ elsif r.is_a?(Color)
16
+ copy(r)
17
+ else
18
+ @r = @g = @b = r.to_f
19
+ end
20
+ else
21
+ @r = r.to_f
22
+ @g = (g || r).to_f
23
+ @b = (b || r).to_f
24
+ end
25
+ end
26
+
27
+ def set(r, g = nil, b = nil)
28
+ if g.nil? && b.nil?
29
+ copy(r)
30
+ else
31
+ @r = r.to_f
32
+ @g = g.to_f
33
+ @b = b.to_f
34
+ end
35
+ self
36
+ end
37
+
38
+ def set_scalar(scalar)
39
+ @r = @g = @b = scalar.to_f
40
+ self
41
+ end
42
+
43
+ def set_hex(hex)
44
+ hex = hex.floor
45
+ @r = ((hex >> 16) & 255) / 255.0
46
+ @g = ((hex >> 8) & 255) / 255.0
47
+ @b = (hex & 255) / 255.0
48
+ self
49
+ end
50
+
51
+ def set_rgb(r, g, b)
52
+ @r = r.to_f
53
+ @g = g.to_f
54
+ @b = b.to_f
55
+ self
56
+ end
57
+
58
+ def set_hsl(h, s, l)
59
+ h = h % 1
60
+ h += 1 if h < 0
61
+
62
+ if s.zero?
63
+ @r = @g = @b = l
64
+ else
65
+ p = l <= 0.5 ? l * (1 + s) : l + s - l * s
66
+ q = 2 * l - p
67
+ @r = hue_to_rgb(q, p, h + 1.0 / 3)
68
+ @g = hue_to_rgb(q, p, h)
69
+ @b = hue_to_rgb(q, p, h - 1.0 / 3)
70
+ end
71
+ self
72
+ end
73
+
74
+ def set_style(style)
75
+ if style.start_with?("#")
76
+ hex = style[1..].to_i(16)
77
+ set_hex(hex)
78
+ elsif style.start_with?("rgb(")
79
+ match = style.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/)
80
+ if match
81
+ @r = match[1].to_i / 255.0
82
+ @g = match[2].to_i / 255.0
83
+ @b = match[3].to_i / 255.0
84
+ end
85
+ end
86
+ self
87
+ end
88
+
89
+ def copy(c)
90
+ @r = c.r
91
+ @g = c.g
92
+ @b = c.b
93
+ self
94
+ end
95
+
96
+ def clone
97
+ Color.new(@r, @g, @b)
98
+ end
99
+
100
+ def get_hex
101
+ ((@r * 255).round << 16) ^ ((@g * 255).round << 8) ^ (@b * 255).round
102
+ end
103
+
104
+ def get_hex_string
105
+ format("%06x", get_hex)
106
+ end
107
+
108
+ def get_hsl
109
+ max = [@r, @g, @b].max
110
+ min = [@r, @g, @b].min
111
+
112
+ l = (min + max) / 2.0
113
+
114
+ if min == max
115
+ h = 0
116
+ s = 0
117
+ else
118
+ delta = max - min
119
+ s = l <= 0.5 ? delta / (max + min) : delta / (2 - max - min)
120
+
121
+ h = case max
122
+ when @r then ((@g - @b) / delta) + (@g < @b ? 6 : 0)
123
+ when @g then ((@b - @r) / delta) + 2
124
+ when @b then ((@r - @g) / delta) + 4
125
+ end
126
+ h /= 6
127
+ end
128
+
129
+ { h: h, s: s, l: l }
130
+ end
131
+
132
+ def get_style
133
+ "rgb(#{(@r * 255).round},#{(@g * 255).round},#{(@b * 255).round})"
134
+ end
135
+
136
+ def +(other)
137
+ Color.new(@r + other.r, @g + other.g, @b + other.b)
138
+ end
139
+
140
+ def add!(other)
141
+ @r += other.r
142
+ @g += other.g
143
+ @b += other.b
144
+ self
145
+ end
146
+
147
+ def add_scalar!(s)
148
+ @r += s
149
+ @g += s
150
+ @b += s
151
+ self
152
+ end
153
+
154
+ def *(other)
155
+ if other.is_a?(Color)
156
+ Color.new(@r * other.r, @g * other.g, @b * other.b)
157
+ else
158
+ Color.new(@r * other, @g * other, @b * other)
159
+ end
160
+ end
161
+
162
+ def multiply!(other)
163
+ @r *= other.r
164
+ @g *= other.g
165
+ @b *= other.b
166
+ self
167
+ end
168
+
169
+ def multiply_scalar!(s)
170
+ @r *= s
171
+ @g *= s
172
+ @b *= s
173
+ self
174
+ end
175
+
176
+ def lerp(other, alpha)
177
+ Color.new(
178
+ @r + (other.r - @r) * alpha,
179
+ @g + (other.g - @g) * alpha,
180
+ @b + (other.b - @b) * alpha
181
+ )
182
+ end
183
+
184
+ def lerp!(other, alpha)
185
+ @r += (other.r - @r) * alpha
186
+ @g += (other.g - @g) * alpha
187
+ @b += (other.b - @b) * alpha
188
+ self
189
+ end
190
+
191
+ def ==(other)
192
+ return false unless other.is_a?(Color)
193
+
194
+ (@r - other.r).abs < Float::EPSILON &&
195
+ (@g - other.g).abs < Float::EPSILON &&
196
+ (@b - other.b).abs < Float::EPSILON
197
+ end
198
+
199
+ def to_a
200
+ [@r, @g, @b]
201
+ end
202
+
203
+ def to_h
204
+ { r: @r, g: @g, b: @b }
205
+ end
206
+
207
+ def inspect
208
+ "#<Sunrb::Color r=#{@r} g=#{@g} b=#{@b}>"
209
+ end
210
+
211
+ class << self
212
+ def white
213
+ new(1, 1, 1)
214
+ end
215
+
216
+ def black
217
+ new(0, 0, 0)
218
+ end
219
+
220
+ def red
221
+ new(1, 0, 0)
222
+ end
223
+
224
+ def green
225
+ new(0, 1, 0)
226
+ end
227
+
228
+ def blue
229
+ new(0, 0, 1)
230
+ end
231
+ end
232
+
233
+ private
234
+
235
+ def hue_to_rgb(p, q, t)
236
+ t += 1 if t < 0
237
+ t -= 1 if t > 1
238
+
239
+ return p + (q - p) * 6 * t if t < 1.0 / 6
240
+ return q if t < 1.0 / 2
241
+ return p + (q - p) * 6 * (2.0 / 3 - t) if t < 2.0 / 3
242
+
243
+ p
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sunrb
4
+ class Euler
5
+ ROTATION_ORDERS = %w[XYZ YXZ ZXY ZYX YZX XZY].freeze
6
+ DEFAULT_ORDER = "XYZ"
7
+
8
+ attr_accessor :x, :y, :z, :order
9
+
10
+ def initialize(x = 0, y = 0, z = 0, order = DEFAULT_ORDER)
11
+ @x = x.to_f
12
+ @y = y.to_f
13
+ @z = z.to_f
14
+ @order = order
15
+ end
16
+
17
+ def set(x, y, z, order = @order)
18
+ @x = x.to_f
19
+ @y = y.to_f
20
+ @z = z.to_f
21
+ @order = order
22
+ self
23
+ end
24
+
25
+ def copy(e)
26
+ @x = e.x
27
+ @y = e.y
28
+ @z = e.z
29
+ @order = e.order
30
+ self
31
+ end
32
+
33
+ def clone
34
+ Euler.new(@x, @y, @z, @order)
35
+ end
36
+
37
+ def set_from_rotation_matrix(m, order = @order)
38
+ e = m.elements
39
+
40
+ m11 = e[0]
41
+ m12 = e[4]
42
+ m13 = e[8]
43
+ m21 = e[1]
44
+ m22 = e[5]
45
+ m23 = e[9]
46
+ m31 = e[2]
47
+ m32 = e[6]
48
+ m33 = e[10]
49
+
50
+ case order
51
+ when "XYZ"
52
+ @y = Math.asin(clamp(m13, -1, 1))
53
+ if m13.abs < 0.9999999
54
+ @x = Math.atan2(-m23, m33)
55
+ @z = Math.atan2(-m12, m11)
56
+ else
57
+ @x = Math.atan2(m32, m22)
58
+ @z = 0
59
+ end
60
+ when "YXZ"
61
+ @x = Math.asin(-clamp(m23, -1, 1))
62
+ if m23.abs < 0.9999999
63
+ @y = Math.atan2(m13, m33)
64
+ @z = Math.atan2(m21, m22)
65
+ else
66
+ @y = Math.atan2(-m31, m11)
67
+ @z = 0
68
+ end
69
+ when "ZXY"
70
+ @x = Math.asin(clamp(m32, -1, 1))
71
+ if m32.abs < 0.9999999
72
+ @y = Math.atan2(-m31, m33)
73
+ @z = Math.atan2(-m12, m22)
74
+ else
75
+ @y = 0
76
+ @z = Math.atan2(m21, m11)
77
+ end
78
+ when "ZYX"
79
+ @y = Math.asin(-clamp(m31, -1, 1))
80
+ if m31.abs < 0.9999999
81
+ @x = Math.atan2(m32, m33)
82
+ @z = Math.atan2(m21, m11)
83
+ else
84
+ @x = 0
85
+ @z = Math.atan2(-m12, m22)
86
+ end
87
+ when "YZX"
88
+ @z = Math.asin(clamp(m21, -1, 1))
89
+ if m21.abs < 0.9999999
90
+ @x = Math.atan2(-m23, m22)
91
+ @y = Math.atan2(-m31, m11)
92
+ else
93
+ @x = 0
94
+ @y = Math.atan2(m13, m33)
95
+ end
96
+ when "XZY"
97
+ @z = Math.asin(-clamp(m12, -1, 1))
98
+ if m12.abs < 0.9999999
99
+ @x = Math.atan2(m32, m22)
100
+ @y = Math.atan2(m13, m11)
101
+ else
102
+ @x = Math.atan2(-m23, m33)
103
+ @y = 0
104
+ end
105
+ end
106
+
107
+ @order = order
108
+ self
109
+ end
110
+
111
+ def set_from_quaternion(q, order = @order)
112
+ matrix = Matrix4.new.make_rotation_from_quaternion(q)
113
+ set_from_rotation_matrix(matrix, order)
114
+ end
115
+
116
+ def set_from_vector3(v, order = @order)
117
+ set(v.x, v.y, v.z, order)
118
+ end
119
+
120
+ def reorder(new_order)
121
+ q = Quaternion.new.set_from_euler(self)
122
+ set_from_quaternion(q, new_order)
123
+ end
124
+
125
+ def ==(other)
126
+ return false unless other.is_a?(Euler)
127
+
128
+ (@x - other.x).abs < Float::EPSILON &&
129
+ (@y - other.y).abs < Float::EPSILON &&
130
+ (@z - other.z).abs < Float::EPSILON &&
131
+ @order == other.order
132
+ end
133
+
134
+ def to_a
135
+ [@x, @y, @z]
136
+ end
137
+
138
+ def to_h
139
+ { x: @x, y: @y, z: @z, order: @order }
140
+ end
141
+
142
+ def to_vector3
143
+ Vector3.new(@x, @y, @z)
144
+ end
145
+
146
+ def inspect
147
+ "#<Sunrb::Euler x=#{@x} y=#{@y} z=#{@z} order=#{@order}>"
148
+ end
149
+
150
+ private
151
+
152
+ def clamp(value, min, max)
153
+ [[value, min].max, max].min
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Sunrb
6
+ module MathUtils
7
+ DEG2RAD = Math::PI / 180
8
+ RAD2DEG = 180 / Math::PI
9
+
10
+ module_function
11
+
12
+ def generate_uuid
13
+ SecureRandom.uuid
14
+ end
15
+
16
+ def clamp(value, min, max)
17
+ [[value, min].max, max].min
18
+ end
19
+
20
+ def euclidean_modulo(n, m)
21
+ ((n % m) + m) % m
22
+ end
23
+
24
+ def map_linear(x, a1, a2, b1, b2)
25
+ b1 + (x - a1) * (b2 - b1) / (a2 - a1)
26
+ end
27
+
28
+ def lerp(x, y, t)
29
+ (1 - t) * x + t * y
30
+ end
31
+
32
+ def inverse_lerp(x, y, value)
33
+ return 0.0 if x == y
34
+
35
+ (value - x).to_f / (y - x)
36
+ end
37
+
38
+ def damp(x, y, lambda_val, dt)
39
+ lerp(x, y, 1 - Math.exp(-lambda_val * dt))
40
+ end
41
+
42
+ def ping_pong(x, length = 1)
43
+ length - (euclidean_modulo(x, length * 2) - length).abs
44
+ end
45
+
46
+ def smooth_step(x, min, max)
47
+ return 0 if x <= min
48
+ return 1 if x >= max
49
+
50
+ x = (x - min) / (max - min)
51
+ x * x * (3 - 2 * x)
52
+ end
53
+
54
+ def smoother_step(x, min, max)
55
+ return 0 if x <= min
56
+ return 1 if x >= max
57
+
58
+ x = (x - min) / (max - min)
59
+ x * x * x * (x * (x * 6 - 15) + 10)
60
+ end
61
+
62
+ def random_int(low, high)
63
+ low + (rand * (high - low + 1)).floor
64
+ end
65
+
66
+ def random_float(low, high)
67
+ low + rand * (high - low)
68
+ end
69
+
70
+ def random_float_spread(range)
71
+ range * (0.5 - rand)
72
+ end
73
+
74
+ def seeded_random(seed)
75
+ # Simple seeded random using modular arithmetic
76
+ x = Math.sin(seed) * 10000
77
+ x - x.floor
78
+ end
79
+
80
+ def deg_to_rad(degrees)
81
+ degrees * DEG2RAD
82
+ end
83
+
84
+ def rad_to_deg(radians)
85
+ radians * RAD2DEG
86
+ end
87
+
88
+ def is_power_of_two(value)
89
+ (value & (value - 1)).zero? && value != 0
90
+ end
91
+
92
+ def ceil_power_of_two(value)
93
+ 2 ** Math.log2(value).ceil
94
+ end
95
+
96
+ def floor_power_of_two(value)
97
+ 2 ** Math.log2(value).floor
98
+ end
99
+
100
+ def nearest_power_of_two(value)
101
+ 2 ** Math.log2(value).round
102
+ end
103
+
104
+ def denormalize(value, array)
105
+ case array
106
+ when Array
107
+ case array.first
108
+ when Integer
109
+ value * 255
110
+ else
111
+ value
112
+ end
113
+ else
114
+ value
115
+ end
116
+ end
117
+
118
+ def normalize(value, array)
119
+ case array
120
+ when Array
121
+ case array.first
122
+ when Integer
123
+ value / 255.0
124
+ else
125
+ value
126
+ end
127
+ else
128
+ value
129
+ end
130
+ end
131
+ end
132
+ end