larb 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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -1
  3. data/README.md +63 -1
  4. data/ext/larb/color.c +449 -0
  5. data/ext/larb/color.h +35 -0
  6. data/ext/larb/extconf.rb +11 -0
  7. data/ext/larb/larb.c +27 -0
  8. data/ext/larb/larb.h +31 -0
  9. data/ext/larb/mat2.c +303 -0
  10. data/ext/larb/mat2.h +30 -0
  11. data/ext/larb/mat2d.c +397 -0
  12. data/ext/larb/mat2d.h +35 -0
  13. data/ext/larb/mat3.c +455 -0
  14. data/ext/larb/mat3.h +33 -0
  15. data/ext/larb/mat4.c +651 -0
  16. data/ext/larb/mat4.h +31 -0
  17. data/ext/larb/matrix_utils.h +97 -0
  18. data/ext/larb/quat.c +567 -0
  19. data/ext/larb/quat.h +39 -0
  20. data/ext/larb/quat2.c +511 -0
  21. data/ext/larb/quat2.h +39 -0
  22. data/ext/larb/vec2.c +365 -0
  23. data/ext/larb/vec2.h +43 -0
  24. data/ext/larb/vec3.c +538 -0
  25. data/ext/larb/vec3.h +52 -0
  26. data/ext/larb/vec4.c +361 -0
  27. data/ext/larb/vec4.h +38 -0
  28. data/lib/larb/version.rb +5 -0
  29. data/lib/larb.rb +2 -14
  30. data/test/larb/color_test.rb +299 -0
  31. data/test/larb/mat2_test.rb +144 -0
  32. data/test/larb/mat2d_test.rb +197 -0
  33. data/test/larb/mat3_test.rb +147 -0
  34. data/test/larb/mat4_test.rb +347 -0
  35. data/test/larb/matrix_test.rb +135 -0
  36. data/test/larb/native_value_test.rb +157 -0
  37. data/test/larb/quat2_test.rb +254 -0
  38. data/test/larb/quat_test.rb +284 -0
  39. data/test/larb/vec2_test.rb +274 -0
  40. data/test/larb/vec3_test.rb +373 -0
  41. data/test/larb/vec4_test.rb +196 -0
  42. data/test/test_helper.rb +4 -0
  43. metadata +60 -18
  44. data/Rakefile +0 -11
  45. data/lib/larb/color.rb +0 -148
  46. data/lib/larb/mat2.rb +0 -119
  47. data/lib/larb/mat2d.rb +0 -180
  48. data/lib/larb/mat3.rb +0 -238
  49. data/lib/larb/mat4.rb +0 -329
  50. data/lib/larb/quat.rb +0 -238
  51. data/lib/larb/quat2.rb +0 -193
  52. data/lib/larb/vec2.rb +0 -150
  53. data/lib/larb/vec3.rb +0 -218
  54. data/lib/larb/vec4.rb +0 -125
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ca6912c8989e8917738c9965540ee392fce8e3c5bcfb95960b1e36d39a84b56
4
- data.tar.gz: c38e9ab279c3e9ede439d90ff7e9153a523e189e7f5bf18867811c0356b21866
3
+ metadata.gz: e661682c31f5246004965e4c9883a15fae7aae30fccb2e9a56a6e0b5e28f7ad1
4
+ data.tar.gz: b4095f395b19e7c38d10659c7ae05169972c329087be2fa4481c97b456872b62
5
5
  SHA512:
6
- metadata.gz: f3499157dd5c279ba8afac41e9d245742cfd7d6a31bdf83e683ee42d9c448e6b67666b1c28b15becf18640393486658e2825e44b10ded335a37a5eb37858c722
7
- data.tar.gz: 381dce0add3519b5b0c2661a443d34aa2cc14dfcbb1225c5b507c744b2fa9cd31d89e7fdfac5a776abf6b924e28aeb08afa66ade9e87b19d7268f0946988b7a9
6
+ metadata.gz: c9a4f13ebd8ef25af8cea483059080e89dd955633006955458b65afda317dc23c5fe34875436a83353f2ca8a8e60f98cc2aa707d65a407eff4c01958b39186e0
7
+ data.tar.gz: 1f41fd169747e148a30a18b665ff41e259dd69ce303911d68c6058cb94dbce4d2034bf83d383c730fc216038c0c7a70399263f6444ed7d2e7eb159d1109cf548
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.0.1 - 2026-09-09
6
+
7
+ - Preserve native components in `dup`/`clone` and enforce `freeze` on all mutations.
8
+ - Correct indexed Vec2 assignment, transform composition, quaternion interpolation, dual quaternion normalization/inversion, and reflection decomposition.
9
+ - Stabilize lengths, normalization, matrix inversion, and small axis-angle rotations; prevent NaN values from passing approximate comparisons.
10
+ - Validate constructor inputs, multiplication operands, tolerances, degenerate directions, length limits, perspective division, and hexadecimal colors.
11
+ - Clamp color components before integer conversion and preserve IEEE floating-point behavior in native builds.
12
+
13
+ ## 1.0.0 - 2026-01-10
14
+
15
+ - Reimplemented in C for better performance.
16
+
5
17
  ## 0.1.0 - 2026-01-02
6
18
 
7
- - Initial release.
19
+ - Initial release.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Larb
1
+ # Larb [![Gem Version](https://badge.fury.io/rb/larb.svg)](https://badge.fury.io/rb/larb) [![CI](https://github.com/ydah/larb/actions/workflows/ci.yml/badge.svg)](https://github.com/ydah/larb/actions/workflows/ci.yml)
2
2
 
3
3
  Linear algebra library for 2D/3D graphics in Ruby.
4
4
 
@@ -58,6 +58,68 @@ custom = Larb::Color.new(0.5, 0.3, 0.8, 1.0)
58
58
  hex_color = Larb::Color.from_hex("#ff8800")
59
59
  ```
60
60
 
61
+ ### Value and input behavior
62
+
63
+ All types support independent `dup`/`clone` copies and respect `freeze`, including
64
+ setters, indexed assignment, `normalize!`, and reinitialization. Failed numeric
65
+ conversion during reinitialization leaves the components unchanged.
66
+
67
+ Numeric arguments use Ruby's native numeric conversion. Strings and explicit
68
+ `nil` are rejected with `TypeError`; omitted optional arguments retain their
69
+ defaults. Array constructors require exactly 4 (`Mat2`), 6 (`Mat2d`), 9 (`Mat3`),
70
+ 16 (`Mat4`), or 8 (`Quat2`) components. Unsupported multiplication operands raise
71
+ `TypeError`; all matrix types support numeric scalar multiplication.
72
+
73
+ `near?` uses an absolute tolerance (default `1e-6`) that must be finite and
74
+ positive. NaN components never compare near another value. Normalization uses
75
+ scaled components to handle very large and very small finite values; zero-length
76
+ or non-finite inputs raise `ArgumentError`. `length_squared` can still overflow
77
+ or underflow when the squared result is outside the range of a Float.
78
+
79
+ Direction operations (`Vec3#project`, `reject`, `angle_between`, and `slerp`)
80
+ reject a zero or non-finite direction with `ArgumentError`. `Vec3#slerp`
81
+ interpolates direction along an arc and length linearly; its endpoint lengths
82
+ and interpolation parameter must be finite. Opposite directions use a
83
+ deterministic perpendicular direction to select an arc. `clamp_length` requires
84
+ a finite, nonnegative limit. Degenerate `Mat4.look_at` and `Quat.look_rotation`
85
+ inputs also raise `ArgumentError`.
86
+
87
+ `Quat#lerp`, `Quat#slerp`, and `Quat2#lerp` choose the shorter rotation path,
88
+ including between `q` and `-q`. Dual quaternion normalization enforces both unit
89
+ real length and real/dual orthogonality. Quaternion rotation factories and
90
+ matrix/point conversion APIs expect unit rotations; normalize manually
91
+ constructed quaternions before using them as rotations.
92
+
93
+ `Mat4.trs` composes `translation * rotation * scale`. `Mat4` and `Mat2d`
94
+ scale/rotation extraction support finite affine transforms with nonzero scales
95
+ and no shear (normalized-column dot tolerance `1e-6`). Reflections are assigned
96
+ to the X scale, so the extracted values reconstruct the original transform;
97
+ the original individual scale signs are not uniquely recoverable. Unsupported
98
+ decompositions raise `ArgumentError`. Matrix inversion uses scaled pivoting;
99
+ singular, numerically singular, non-finite, or non-finitely representable inverse
100
+ results raise `RuntimeError`.
101
+
102
+ `Vec4#perspective_divide` requires finite, nonzero `w` and raises `ArgumentError`
103
+ otherwise. Use `xyz` to read a direction vector's three spatial components.
104
+
105
+ `Color.from_hex` accepts 6 or 8 hexadecimal digits, optionally prefixed with one
106
+ `#`; malformed strings raise `ArgumentError`. `to_bytes` and `to_hex` clamp
107
+ components to `[0, 1]` before rounding: infinities saturate to the corresponding
108
+ endpoint and NaN raises `ArgumentError`.
109
+
110
+ ## Development
111
+
112
+ ```bash
113
+ # Install dependencies
114
+ bundle install
115
+
116
+ # Compile native extension
117
+ bundle exec rake compile
118
+
119
+ # Run tests
120
+ bundle exec rake test
121
+ ```
122
+
61
123
  ## License
62
124
 
63
125
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/ext/larb/color.c ADDED
@@ -0,0 +1,449 @@
1
+ #include "color.h"
2
+
3
+ #include <math.h>
4
+
5
+ static void color_free(void *ptr) {
6
+ xfree(ptr);
7
+ }
8
+
9
+ static size_t color_memsize(const void *ptr) {
10
+ return sizeof(ColorData);
11
+ }
12
+
13
+ static const rb_data_type_t color_type = {
14
+ "Color",
15
+ {0, color_free, color_memsize},
16
+ 0,
17
+ 0,
18
+ RUBY_TYPED_FREE_IMMEDIATELY,
19
+ };
20
+
21
+ static VALUE cColor = Qnil;
22
+ static VALUE cVec3 = Qnil;
23
+ static VALUE cVec4 = Qnil;
24
+
25
+ static ColorData *color_get(VALUE obj) {
26
+ ColorData *data = NULL;
27
+ TypedData_Get_Struct(obj, ColorData, &color_type, data);
28
+ return data;
29
+ }
30
+
31
+ static VALUE color_build(VALUE klass, double r, double g, double b, double a) {
32
+ VALUE obj = color_alloc(klass);
33
+ ColorData *data = color_get(obj);
34
+ data->r = r;
35
+ data->g = g;
36
+ data->b = b;
37
+ data->a = a;
38
+ return obj;
39
+ }
40
+
41
+ static double clamp_double(double value, double min, double max) {
42
+ if (value < min) {
43
+ return min;
44
+ }
45
+ if (value > max) {
46
+ return max;
47
+ }
48
+ return value;
49
+ }
50
+
51
+ static int color_byte(double value) {
52
+ if (isnan(value)) rb_raise(rb_eArgError, "Cannot convert NaN to a color byte");
53
+ return (int)lround(clamp_double(value, 0.0, 1.0) * 255.0);
54
+ }
55
+
56
+ static VALUE color_get_r(VALUE self) {
57
+ ColorData *data = color_get(self);
58
+ return DBL2NUM(data->r);
59
+ }
60
+
61
+ static VALUE color_set_r(VALUE self, VALUE value) {
62
+ rb_check_frozen(self);
63
+ ColorData *data = color_get(self);
64
+ double component = NUM2DBL(value);
65
+ rb_check_frozen(self);
66
+ data->r = component;
67
+ return value;
68
+ }
69
+
70
+ static VALUE color_get_g(VALUE self) {
71
+ ColorData *data = color_get(self);
72
+ return DBL2NUM(data->g);
73
+ }
74
+
75
+ static VALUE color_set_g(VALUE self, VALUE value) {
76
+ rb_check_frozen(self);
77
+ ColorData *data = color_get(self);
78
+ double component = NUM2DBL(value);
79
+ rb_check_frozen(self);
80
+ data->g = component;
81
+ return value;
82
+ }
83
+
84
+ static VALUE color_get_b(VALUE self) {
85
+ ColorData *data = color_get(self);
86
+ return DBL2NUM(data->b);
87
+ }
88
+
89
+ static VALUE color_set_b(VALUE self, VALUE value) {
90
+ rb_check_frozen(self);
91
+ ColorData *data = color_get(self);
92
+ double component = NUM2DBL(value);
93
+ rb_check_frozen(self);
94
+ data->b = component;
95
+ return value;
96
+ }
97
+
98
+ static VALUE color_get_a(VALUE self) {
99
+ ColorData *data = color_get(self);
100
+ return DBL2NUM(data->a);
101
+ }
102
+
103
+ static VALUE color_set_a(VALUE self, VALUE value) {
104
+ rb_check_frozen(self);
105
+ ColorData *data = color_get(self);
106
+ double component = NUM2DBL(value);
107
+ rb_check_frozen(self);
108
+ data->a = component;
109
+ return value;
110
+ }
111
+
112
+ VALUE color_alloc(VALUE klass) {
113
+ ColorData *data = ALLOC(ColorData);
114
+ data->r = 0.0;
115
+ data->g = 0.0;
116
+ data->b = 0.0;
117
+ data->a = 1.0;
118
+ return TypedData_Wrap_Struct(klass, &color_type, data);
119
+ }
120
+
121
+ VALUE color_initialize(int argc, VALUE *argv, VALUE self) {
122
+ rb_check_frozen(self);
123
+ VALUE vr = Qnil;
124
+ VALUE vg = Qnil;
125
+ VALUE vb = Qnil;
126
+ VALUE va = Qnil;
127
+ rb_scan_args(argc, argv, "04", &vr, &vg, &vb, &va);
128
+ ColorData value = {
129
+ argc > 0 ? NUM2DBL(vr) : 0.0,
130
+ argc > 1 ? NUM2DBL(vg) : 0.0,
131
+ argc > 2 ? NUM2DBL(vb) : 0.0,
132
+ argc > 3 ? NUM2DBL(va) : 1.0
133
+ };
134
+ rb_check_frozen(self);
135
+ *color_get(self) = value;
136
+ return self;
137
+ }
138
+
139
+ static VALUE color_class_bracket(int argc, VALUE *argv, VALUE klass) {
140
+ VALUE vr = Qnil;
141
+ VALUE vg = Qnil;
142
+ VALUE vb = Qnil;
143
+ VALUE va = Qnil;
144
+
145
+ rb_scan_args(argc, argv, "31", &vr, &vg, &vb, &va);
146
+ return color_build(klass, NUM2DBL(vr), NUM2DBL(vg),
147
+ NUM2DBL(vb), argc < 4 ? 1.0 : NUM2DBL(va));
148
+ }
149
+
150
+ static VALUE color_class_rgb(VALUE klass, VALUE r, VALUE g, VALUE b) {
151
+ return color_build(klass, NUM2DBL(r), NUM2DBL(g),
152
+ NUM2DBL(b), 1.0);
153
+ }
154
+
155
+ static VALUE color_class_rgba(VALUE klass, VALUE r, VALUE g, VALUE b, VALUE a) {
156
+ return color_build(klass, NUM2DBL(r), NUM2DBL(g),
157
+ NUM2DBL(b), NUM2DBL(a));
158
+ }
159
+
160
+ static VALUE color_class_from_bytes(int argc, VALUE *argv, VALUE klass) {
161
+ VALUE vr = Qnil;
162
+ VALUE vg = Qnil;
163
+ VALUE vb = Qnil;
164
+ VALUE va = Qnil;
165
+
166
+ rb_scan_args(argc, argv, "31", &vr, &vg, &vb, &va);
167
+ double r = NUM2DBL(vr) / 255.0;
168
+ double g = NUM2DBL(vg) / 255.0;
169
+ double b = NUM2DBL(vb) / 255.0;
170
+ double a = argc < 4 ? 1.0 : NUM2DBL(va) / 255.0;
171
+ return color_build(klass, r, g, b, a);
172
+ }
173
+
174
+ static VALUE color_class_from_hex(VALUE klass, VALUE hex_value) {
175
+ StringValue(hex_value);
176
+ const char *hex = RSTRING_PTR(hex_value);
177
+ long len = RSTRING_LEN(hex_value);
178
+ if (len > 0 && hex[0] == '#') {
179
+ hex++;
180
+ len--;
181
+ }
182
+ if (len != 6 && len != 8) {
183
+ rb_raise(rb_eArgError, "Expected 6 or 8 hexadecimal digits with an optional leading #");
184
+ }
185
+ unsigned int rgba = 0;
186
+ for (long i = 0; i < len; i++) {
187
+ unsigned int digit;
188
+ if (hex[i] >= '0' && hex[i] <= '9') digit = hex[i] - '0';
189
+ else if (hex[i] >= 'a' && hex[i] <= 'f') digit = hex[i] - 'a' + 10;
190
+ else if (hex[i] >= 'A' && hex[i] <= 'F') digit = hex[i] - 'A' + 10;
191
+ else rb_raise(rb_eArgError, "Invalid hexadecimal digit");
192
+ rgba = (rgba << 4) | digit;
193
+ }
194
+ if (len == 6) rgba = (rgba << 8) | 255;
195
+ return color_build(klass, ((rgba >> 24) & 255) / 255.0,
196
+ ((rgba >> 16) & 255) / 255.0, ((rgba >> 8) & 255) / 255.0,
197
+ (rgba & 255) / 255.0);
198
+ }
199
+
200
+ static VALUE color_class_black(VALUE klass) {
201
+ return color_build(klass, 0.0, 0.0, 0.0, 1.0);
202
+ }
203
+
204
+ static VALUE color_class_white(VALUE klass) {
205
+ return color_build(klass, 1.0, 1.0, 1.0, 1.0);
206
+ }
207
+
208
+ static VALUE color_class_red(VALUE klass) {
209
+ return color_build(klass, 1.0, 0.0, 0.0, 1.0);
210
+ }
211
+
212
+ static VALUE color_class_green(VALUE klass) {
213
+ return color_build(klass, 0.0, 1.0, 0.0, 1.0);
214
+ }
215
+
216
+ static VALUE color_class_blue(VALUE klass) {
217
+ return color_build(klass, 0.0, 0.0, 1.0, 1.0);
218
+ }
219
+
220
+ static VALUE color_class_yellow(VALUE klass) {
221
+ return color_build(klass, 1.0, 1.0, 0.0, 1.0);
222
+ }
223
+
224
+ static VALUE color_class_cyan(VALUE klass) {
225
+ return color_build(klass, 0.0, 1.0, 1.0, 1.0);
226
+ }
227
+
228
+ static VALUE color_class_magenta(VALUE klass) {
229
+ return color_build(klass, 1.0, 0.0, 1.0, 1.0);
230
+ }
231
+
232
+ static VALUE color_class_transparent(VALUE klass) {
233
+ return color_build(klass, 0.0, 0.0, 0.0, 0.0);
234
+ }
235
+
236
+ VALUE color_class_from_vec4(VALUE klass, VALUE vec4) {
237
+ double r = NUM2DBL(rb_funcall(vec4, rb_intern("x"), 0));
238
+ double g = NUM2DBL(rb_funcall(vec4, rb_intern("y"), 0));
239
+ double b = NUM2DBL(rb_funcall(vec4, rb_intern("z"), 0));
240
+ double a = NUM2DBL(rb_funcall(vec4, rb_intern("w"), 0));
241
+ return color_build(klass, r, g, b, a);
242
+ }
243
+
244
+ VALUE color_class_from_vec3(int argc, VALUE *argv, VALUE klass) {
245
+ VALUE vec3 = Qnil;
246
+ VALUE alpha = Qnil;
247
+ rb_scan_args(argc, argv, "11", &vec3, &alpha);
248
+ double r = NUM2DBL(rb_funcall(vec3, rb_intern("x"), 0));
249
+ double g = NUM2DBL(rb_funcall(vec3, rb_intern("y"), 0));
250
+ double b = NUM2DBL(rb_funcall(vec3, rb_intern("z"), 0));
251
+ double a = argc < 2 ? 1.0 : NUM2DBL(alpha);
252
+ return color_build(klass, r, g, b, a);
253
+ }
254
+
255
+ VALUE color_add(VALUE self, VALUE other) {
256
+ ColorData *a = color_get(self);
257
+ ColorData *b = color_get(other);
258
+ return color_build(rb_obj_class(self), a->r + b->r, a->g + b->g, a->b + b->b,
259
+ a->a + b->a);
260
+ }
261
+
262
+ VALUE color_sub(VALUE self, VALUE other) {
263
+ ColorData *a = color_get(self);
264
+ ColorData *b = color_get(other);
265
+ return color_build(rb_obj_class(self), a->r - b->r, a->g - b->g, a->b - b->b,
266
+ a->a - b->a);
267
+ }
268
+
269
+ VALUE color_mul(VALUE self, VALUE scalar) {
270
+ ColorData *a = color_get(self);
271
+ if (rb_obj_is_kind_of(scalar, cColor)) {
272
+ ColorData *b = color_get(scalar);
273
+ return color_build(rb_obj_class(self), a->r * b->r, a->g * b->g,
274
+ a->b * b->b, a->a * b->a);
275
+ }
276
+ if (rb_obj_is_kind_of(scalar, rb_cNumeric)) {
277
+ double s = NUM2DBL(scalar);
278
+ return color_build(rb_obj_class(self), a->r * s, a->g * s, a->b * s,
279
+ a->a * s);
280
+ }
281
+ rb_raise(rb_eTypeError, "Expected a Color or numeric scalar");
282
+ }
283
+
284
+ VALUE color_lerp(VALUE self, VALUE other, VALUE t) {
285
+ ColorData *a = color_get(self);
286
+ ColorData *b = color_get(other);
287
+ double s = NUM2DBL(t);
288
+ return color_build(rb_obj_class(self), a->r + (b->r - a->r) * s,
289
+ a->g + (b->g - a->g) * s, a->b + (b->b - a->b) * s,
290
+ a->a + (b->a - a->a) * s);
291
+ }
292
+
293
+ VALUE color_clamp(VALUE self) {
294
+ ColorData *a = color_get(self);
295
+ return color_build(rb_obj_class(self), clamp_double(a->r, 0.0, 1.0),
296
+ clamp_double(a->g, 0.0, 1.0),
297
+ clamp_double(a->b, 0.0, 1.0),
298
+ clamp_double(a->a, 0.0, 1.0));
299
+ }
300
+
301
+ VALUE color_to_bytes(VALUE self) {
302
+ ColorData *a = color_get(self);
303
+ return rb_ary_new_from_args(4, INT2NUM(color_byte(a->r)), INT2NUM(color_byte(a->g)),
304
+ INT2NUM(color_byte(a->b)), INT2NUM(color_byte(a->a)));
305
+ }
306
+
307
+ VALUE color_to_hex(VALUE self) {
308
+ VALUE bytes = color_to_bytes(self);
309
+ VALUE r = rb_ary_entry(bytes, 0);
310
+ VALUE g = rb_ary_entry(bytes, 1);
311
+ VALUE b = rb_ary_entry(bytes, 2);
312
+ VALUE a = rb_ary_entry(bytes, 3);
313
+ return rb_funcall(rb_mKernel, rb_intern("format"), 5,
314
+ rb_str_new_cstr("#%02x%02x%02x%02x"), r, g, b, a);
315
+ }
316
+
317
+ VALUE color_to_vec3(VALUE self) {
318
+ ColorData *a = color_get(self);
319
+ return rb_funcall(cVec3, rb_intern("new"), 3, DBL2NUM(a->r), DBL2NUM(a->g),
320
+ DBL2NUM(a->b));
321
+ }
322
+
323
+ VALUE color_to_vec4(VALUE self) {
324
+ ColorData *a = color_get(self);
325
+ return rb_funcall(cVec4, rb_intern("new"), 4, DBL2NUM(a->r), DBL2NUM(a->g),
326
+ DBL2NUM(a->b), DBL2NUM(a->a));
327
+ }
328
+
329
+ VALUE color_to_a(VALUE self) {
330
+ ColorData *a = color_get(self);
331
+ VALUE ary = rb_ary_new_capa(4);
332
+ rb_ary_push(ary, DBL2NUM(a->r));
333
+ rb_ary_push(ary, DBL2NUM(a->g));
334
+ rb_ary_push(ary, DBL2NUM(a->b));
335
+ rb_ary_push(ary, DBL2NUM(a->a));
336
+ return ary;
337
+ }
338
+
339
+ VALUE color_aref(VALUE self, VALUE index) {
340
+ VALUE ary = color_to_a(self);
341
+ return rb_ary_entry(ary, NUM2LONG(index));
342
+ }
343
+
344
+ VALUE color_equal(VALUE self, VALUE other) {
345
+ if (!rb_obj_is_kind_of(other, cColor)) {
346
+ return Qfalse;
347
+ }
348
+ ColorData *a = color_get(self);
349
+ ColorData *b = color_get(other);
350
+ return (a->r == b->r && a->g == b->g && a->b == b->b && a->a == b->a)
351
+ ? Qtrue
352
+ : Qfalse;
353
+ }
354
+
355
+ VALUE color_near(int argc, VALUE *argv, VALUE self) {
356
+ VALUE other = Qnil;
357
+ VALUE epsilon = Qnil;
358
+
359
+ rb_scan_args(argc, argv, "11", &other, &epsilon);
360
+ ColorData *a = color_get(self);
361
+ ColorData *b = color_get(other);
362
+ double eps = argc < 2 ? 1e-6 : NUM2DBL(epsilon);
363
+ if (!isfinite(eps) || eps <= 0.0) {
364
+ rb_raise(rb_eArgError, "epsilon must be finite and positive");
365
+ }
366
+
367
+ if (fabs(a->r - b->r) < eps && fabs(a->g - b->g) < eps &&
368
+ fabs(a->b - b->b) < eps && fabs(a->a - b->a) < eps) {
369
+ return Qtrue;
370
+ }
371
+ return Qfalse;
372
+ }
373
+
374
+ VALUE color_inspect(VALUE self) {
375
+ ColorData *a = color_get(self);
376
+ VALUE sr = rb_funcall(DBL2NUM(a->r), rb_intern("to_s"), 0);
377
+ VALUE sg = rb_funcall(DBL2NUM(a->g), rb_intern("to_s"), 0);
378
+ VALUE sb = rb_funcall(DBL2NUM(a->b), rb_intern("to_s"), 0);
379
+ VALUE sa = rb_funcall(DBL2NUM(a->a), rb_intern("to_s"), 0);
380
+ VALUE str = rb_str_new_cstr("Color[");
381
+ rb_str_concat(str, sr);
382
+ rb_str_cat_cstr(str, ", ");
383
+ rb_str_concat(str, sg);
384
+ rb_str_cat_cstr(str, ", ");
385
+ rb_str_concat(str, sb);
386
+ rb_str_cat_cstr(str, ", ");
387
+ rb_str_concat(str, sa);
388
+ rb_str_cat_cstr(str, "]");
389
+ return str;
390
+ }
391
+
392
+ static VALUE color_initialize_copy(VALUE self, VALUE other) {
393
+ if (self == other) return self;
394
+ rb_obj_init_copy(self, other);
395
+ *color_get(self) = *color_get(other);
396
+ return self;
397
+ }
398
+
399
+ void Init_color(VALUE module) {
400
+ cColor = rb_define_class_under(module, "Color", rb_cObject);
401
+ cVec3 = rb_const_get(mLarb, rb_intern("Vec3"));
402
+ cVec4 = rb_const_get(mLarb, rb_intern("Vec4"));
403
+
404
+ rb_define_alloc_func(cColor, color_alloc);
405
+ rb_define_method(cColor, "initialize", color_initialize, -1);
406
+ rb_define_method(cColor, "initialize_copy", color_initialize_copy, 1);
407
+
408
+ rb_define_singleton_method(cColor, "[]", color_class_bracket, -1);
409
+ rb_define_singleton_method(cColor, "rgb", color_class_rgb, 3);
410
+ rb_define_singleton_method(cColor, "rgba", color_class_rgba, 4);
411
+ rb_define_singleton_method(cColor, "from_bytes", color_class_from_bytes, -1);
412
+ rb_define_singleton_method(cColor, "from_hex", color_class_from_hex, 1);
413
+ rb_define_singleton_method(cColor, "black", color_class_black, 0);
414
+ rb_define_singleton_method(cColor, "white", color_class_white, 0);
415
+ rb_define_singleton_method(cColor, "red", color_class_red, 0);
416
+ rb_define_singleton_method(cColor, "green", color_class_green, 0);
417
+ rb_define_singleton_method(cColor, "blue", color_class_blue, 0);
418
+ rb_define_singleton_method(cColor, "yellow", color_class_yellow, 0);
419
+ rb_define_singleton_method(cColor, "cyan", color_class_cyan, 0);
420
+ rb_define_singleton_method(cColor, "magenta", color_class_magenta, 0);
421
+ rb_define_singleton_method(cColor, "transparent", color_class_transparent, 0);
422
+ rb_define_singleton_method(cColor, "from_vec4", color_class_from_vec4, 1);
423
+ rb_define_singleton_method(cColor, "from_vec3", color_class_from_vec3, -1);
424
+
425
+ rb_define_method(cColor, "r", color_get_r, 0);
426
+ rb_define_method(cColor, "r=", color_set_r, 1);
427
+ rb_define_method(cColor, "g", color_get_g, 0);
428
+ rb_define_method(cColor, "g=", color_set_g, 1);
429
+ rb_define_method(cColor, "b", color_get_b, 0);
430
+ rb_define_method(cColor, "b=", color_set_b, 1);
431
+ rb_define_method(cColor, "a", color_get_a, 0);
432
+ rb_define_method(cColor, "a=", color_set_a, 1);
433
+
434
+ rb_define_method(cColor, "+", color_add, 1);
435
+ rb_define_method(cColor, "-", color_sub, 1);
436
+ rb_define_method(cColor, "*", color_mul, 1);
437
+ rb_define_method(cColor, "lerp", color_lerp, 2);
438
+ rb_define_method(cColor, "clamp", color_clamp, 0);
439
+ rb_define_method(cColor, "to_bytes", color_to_bytes, 0);
440
+ rb_define_method(cColor, "to_hex", color_to_hex, 0);
441
+ rb_define_method(cColor, "to_vec3", color_to_vec3, 0);
442
+ rb_define_method(cColor, "to_vec4", color_to_vec4, 0);
443
+ rb_define_method(cColor, "to_a", color_to_a, 0);
444
+ rb_define_method(cColor, "[]", color_aref, 1);
445
+ rb_define_method(cColor, "==", color_equal, 1);
446
+ rb_define_method(cColor, "near?", color_near, -1);
447
+ rb_define_method(cColor, "inspect", color_inspect, 0);
448
+ rb_define_alias(cColor, "to_s", "inspect");
449
+ }
data/ext/larb/color.h ADDED
@@ -0,0 +1,35 @@
1
+ #ifndef COLOR_H
2
+ #define COLOR_H
3
+
4
+ #include "larb.h"
5
+
6
+ typedef struct {
7
+ double r;
8
+ double g;
9
+ double b;
10
+ double a;
11
+ } ColorData;
12
+
13
+ void Init_color(VALUE module);
14
+ VALUE color_alloc(VALUE klass);
15
+ VALUE color_initialize(int argc, VALUE *argv, VALUE self);
16
+
17
+ VALUE color_class_from_vec4(VALUE klass, VALUE vec4);
18
+ VALUE color_class_from_vec3(int argc, VALUE *argv, VALUE klass);
19
+
20
+ VALUE color_add(VALUE self, VALUE other);
21
+ VALUE color_sub(VALUE self, VALUE other);
22
+ VALUE color_mul(VALUE self, VALUE scalar);
23
+ VALUE color_lerp(VALUE self, VALUE other, VALUE t);
24
+ VALUE color_clamp(VALUE self);
25
+ VALUE color_to_bytes(VALUE self);
26
+ VALUE color_to_hex(VALUE self);
27
+ VALUE color_to_vec3(VALUE self);
28
+ VALUE color_to_vec4(VALUE self);
29
+ VALUE color_to_a(VALUE self);
30
+ VALUE color_aref(VALUE self, VALUE index);
31
+ VALUE color_equal(VALUE self, VALUE other);
32
+ VALUE color_near(int argc, VALUE *argv, VALUE self);
33
+ VALUE color_inspect(VALUE self);
34
+
35
+ #endif
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+
5
+ # mathライブラリの確認
6
+ have_library("m", "sin")
7
+
8
+ # 最適化フラグ
9
+ $CFLAGS << " -O3 -march=native -funroll-loops"
10
+
11
+ create_makefile("larb/larb")
data/ext/larb/larb.c ADDED
@@ -0,0 +1,27 @@
1
+ #include "larb.h"
2
+ #include "vec2.h"
3
+ #include "vec3.h"
4
+ #include "vec4.h"
5
+ #include "mat2.h"
6
+ #include "mat3.h"
7
+ #include "mat4.h"
8
+ #include "quat.h"
9
+ #include "mat2d.h"
10
+ #include "color.h"
11
+ #include "quat2.h"
12
+
13
+ VALUE mLarb = Qnil;
14
+
15
+ void Init_larb(void) {
16
+ mLarb = rb_define_module("Larb");
17
+ Init_vec2(mLarb);
18
+ Init_vec3(mLarb);
19
+ Init_vec4(mLarb);
20
+ Init_mat2(mLarb);
21
+ Init_mat3(mLarb);
22
+ Init_quat(mLarb);
23
+ Init_mat4(mLarb);
24
+ Init_mat2d(mLarb);
25
+ Init_color(mLarb);
26
+ Init_quat2(mLarb);
27
+ }
data/ext/larb/larb.h ADDED
@@ -0,0 +1,31 @@
1
+ #ifndef LARB_H
2
+ #define LARB_H
3
+
4
+ #include <ruby.h>
5
+ #include <math.h>
6
+
7
+ extern VALUE mLarb;
8
+
9
+ static inline void larb_normalize(double *values, size_t count) {
10
+ double scale = 0.0;
11
+ for (size_t i = 0; i < count; i++) {
12
+ if (!isfinite(values[i])) {
13
+ rb_raise(rb_eArgError, "Cannot normalize non-finite components");
14
+ }
15
+ scale = fmax(scale, fabs(values[i]));
16
+ }
17
+ if (scale == 0.0) {
18
+ rb_raise(rb_eArgError, "Cannot normalize a zero-length value");
19
+ }
20
+ double sum = 0.0;
21
+ for (size_t i = 0; i < count; i++) {
22
+ values[i] /= scale;
23
+ sum += values[i] * values[i];
24
+ }
25
+ double length = sqrt(sum);
26
+ for (size_t i = 0; i < count; i++) {
27
+ values[i] /= length;
28
+ }
29
+ }
30
+
31
+ #endif