larb 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b1d77ff0ad9a72005f26e788711b7a6de60fecf47f9e7232009bc2f27c797ec
4
- data.tar.gz: 409a982d4e663ea572852deead61d9bd94f87aead91276322c03bbfb3d79b906
3
+ metadata.gz: e661682c31f5246004965e4c9883a15fae7aae30fccb2e9a56a6e0b5e28f7ad1
4
+ data.tar.gz: b4095f395b19e7c38d10659c7ae05169972c329087be2fa4481c97b456872b62
5
5
  SHA512:
6
- metadata.gz: 4ce1d13c4eff9c79b54d49619182e61f56a7a00cd7e1b37119e168d3491e114e3ecbd76c26428d9e57a39acc4c7ed8e21472d302d46da01fe34dce8e8a5b1689
7
- data.tar.gz: 82ca7ec6c30c6ac3080ed5ef7070afefddcbdc2455a1bc36045673d1492d4c9b34f3a1fc94310edca0cbf6f4bee544c65d22ae8b6bf23b9b1f142ae9a6d80950
6
+ metadata.gz: c9a4f13ebd8ef25af8cea483059080e89dd955633006955458b65afda317dc23c5fe34875436a83353f2ca8a8e60f98cc2aa707d65a407eff4c01958b39186e0
7
+ data.tar.gz: 1f41fd169747e148a30a18b665ff41e259dd69ce303911d68c6058cb94dbce4d2034bf83d383c730fc216038c0c7a70399263f6444ed7d2e7eb159d1109cf548
data/CHANGELOG.md CHANGED
@@ -2,10 +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
+
5
13
  ## 1.0.0 - 2026-01-10
6
14
 
7
15
  - Reimplemented in C for better performance.
8
16
 
9
17
  ## 0.1.0 - 2026-01-02
10
18
 
11
- - Initial release.
19
+ - Initial release.
data/README.md CHANGED
@@ -58,6 +58,55 @@ 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
+
61
110
  ## Development
62
111
 
63
112
  ```bash
data/ext/larb/color.c CHANGED
@@ -22,11 +22,6 @@ static VALUE cColor = Qnil;
22
22
  static VALUE cVec3 = Qnil;
23
23
  static VALUE cVec4 = Qnil;
24
24
 
25
- static double value_to_double(VALUE value) {
26
- VALUE coerced = rb_funcall(value, rb_intern("to_f"), 0);
27
- return NUM2DBL(coerced);
28
- }
29
-
30
25
  static ColorData *color_get(VALUE obj) {
31
26
  ColorData *data = NULL;
32
27
  TypedData_Get_Struct(obj, ColorData, &color_type, data);
@@ -53,14 +48,9 @@ static double clamp_double(double value, double min, double max) {
53
48
  return value;
54
49
  }
55
50
 
56
- static int clamp_int(int value, int min, int max) {
57
- if (value < min) {
58
- return min;
59
- }
60
- if (value > max) {
61
- return max;
62
- }
63
- return value;
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);
64
54
  }
65
55
 
66
56
  static VALUE color_get_r(VALUE self) {
@@ -69,8 +59,11 @@ static VALUE color_get_r(VALUE self) {
69
59
  }
70
60
 
71
61
  static VALUE color_set_r(VALUE self, VALUE value) {
62
+ rb_check_frozen(self);
72
63
  ColorData *data = color_get(self);
73
- data->r = value_to_double(value);
64
+ double component = NUM2DBL(value);
65
+ rb_check_frozen(self);
66
+ data->r = component;
74
67
  return value;
75
68
  }
76
69
 
@@ -80,8 +73,11 @@ static VALUE color_get_g(VALUE self) {
80
73
  }
81
74
 
82
75
  static VALUE color_set_g(VALUE self, VALUE value) {
76
+ rb_check_frozen(self);
83
77
  ColorData *data = color_get(self);
84
- data->g = value_to_double(value);
78
+ double component = NUM2DBL(value);
79
+ rb_check_frozen(self);
80
+ data->g = component;
85
81
  return value;
86
82
  }
87
83
 
@@ -91,8 +87,11 @@ static VALUE color_get_b(VALUE self) {
91
87
  }
92
88
 
93
89
  static VALUE color_set_b(VALUE self, VALUE value) {
90
+ rb_check_frozen(self);
94
91
  ColorData *data = color_get(self);
95
- data->b = value_to_double(value);
92
+ double component = NUM2DBL(value);
93
+ rb_check_frozen(self);
94
+ data->b = component;
96
95
  return value;
97
96
  }
98
97
 
@@ -102,8 +101,11 @@ static VALUE color_get_a(VALUE self) {
102
101
  }
103
102
 
104
103
  static VALUE color_set_a(VALUE self, VALUE value) {
104
+ rb_check_frozen(self);
105
105
  ColorData *data = color_get(self);
106
- data->a = value_to_double(value);
106
+ double component = NUM2DBL(value);
107
+ rb_check_frozen(self);
108
+ data->a = component;
107
109
  return value;
108
110
  }
109
111
 
@@ -117,18 +119,20 @@ VALUE color_alloc(VALUE klass) {
117
119
  }
118
120
 
119
121
  VALUE color_initialize(int argc, VALUE *argv, VALUE self) {
122
+ rb_check_frozen(self);
120
123
  VALUE vr = Qnil;
121
124
  VALUE vg = Qnil;
122
125
  VALUE vb = Qnil;
123
126
  VALUE va = Qnil;
124
- ColorData *data = color_get(self);
125
-
126
127
  rb_scan_args(argc, argv, "04", &vr, &vg, &vb, &va);
127
- data->r = NIL_P(vr) ? 0.0 : value_to_double(vr);
128
- data->g = NIL_P(vg) ? 0.0 : value_to_double(vg);
129
- data->b = NIL_P(vb) ? 0.0 : value_to_double(vb);
130
- data->a = NIL_P(va) ? 1.0 : value_to_double(va);
131
-
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;
132
136
  return self;
133
137
  }
134
138
 
@@ -139,18 +143,18 @@ static VALUE color_class_bracket(int argc, VALUE *argv, VALUE klass) {
139
143
  VALUE va = Qnil;
140
144
 
141
145
  rb_scan_args(argc, argv, "31", &vr, &vg, &vb, &va);
142
- return color_build(klass, value_to_double(vr), value_to_double(vg),
143
- value_to_double(vb), NIL_P(va) ? 1.0 : value_to_double(va));
146
+ return color_build(klass, NUM2DBL(vr), NUM2DBL(vg),
147
+ NUM2DBL(vb), argc < 4 ? 1.0 : NUM2DBL(va));
144
148
  }
145
149
 
146
150
  static VALUE color_class_rgb(VALUE klass, VALUE r, VALUE g, VALUE b) {
147
- return color_build(klass, value_to_double(r), value_to_double(g),
148
- value_to_double(b), 1.0);
151
+ return color_build(klass, NUM2DBL(r), NUM2DBL(g),
152
+ NUM2DBL(b), 1.0);
149
153
  }
150
154
 
151
155
  static VALUE color_class_rgba(VALUE klass, VALUE r, VALUE g, VALUE b, VALUE a) {
152
- return color_build(klass, value_to_double(r), value_to_double(g),
153
- value_to_double(b), value_to_double(a));
156
+ return color_build(klass, NUM2DBL(r), NUM2DBL(g),
157
+ NUM2DBL(b), NUM2DBL(a));
154
158
  }
155
159
 
156
160
  static VALUE color_class_from_bytes(int argc, VALUE *argv, VALUE klass) {
@@ -160,41 +164,37 @@ static VALUE color_class_from_bytes(int argc, VALUE *argv, VALUE klass) {
160
164
  VALUE va = Qnil;
161
165
 
162
166
  rb_scan_args(argc, argv, "31", &vr, &vg, &vb, &va);
163
- double r = value_to_double(vr) / 255.0;
164
- double g = value_to_double(vg) / 255.0;
165
- double b = value_to_double(vb) / 255.0;
166
- double a = NIL_P(va) ? 1.0 : value_to_double(va) / 255.0;
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;
167
171
  return color_build(klass, r, g, b, a);
168
172
  }
169
173
 
170
174
  static VALUE color_class_from_hex(VALUE klass, VALUE hex_value) {
171
- VALUE hex = rb_funcall(hex_value, rb_intern("to_s"), 0);
172
- VALUE cleaned = rb_funcall(hex, rb_intern("delete"), 1, rb_str_new_cstr("#"));
173
- VALUE len_val = rb_funcall(cleaned, rb_intern("length"), 0);
174
- long len = NUM2LONG(len_val);
175
-
176
- VALUE r_str = rb_funcall(cleaned, rb_intern("[]"), 2, INT2NUM(0), INT2NUM(2));
177
- VALUE g_str = rb_funcall(cleaned, rb_intern("[]"), 2, INT2NUM(2), INT2NUM(2));
178
- VALUE b_str = rb_funcall(cleaned, rb_intern("[]"), 2, INT2NUM(4), INT2NUM(2));
179
- double r = value_to_double(rb_funcall(r_str, rb_intern("to_i"), 1,
180
- INT2NUM(16))) /
181
- 255.0;
182
- double g = value_to_double(rb_funcall(g_str, rb_intern("to_i"), 1,
183
- INT2NUM(16))) /
184
- 255.0;
185
- double b = value_to_double(rb_funcall(b_str, rb_intern("to_i"), 1,
186
- INT2NUM(16))) /
187
- 255.0;
188
- double a = 1.0;
189
- if (len > 6) {
190
- VALUE a_str =
191
- rb_funcall(cleaned, rb_intern("[]"), 2, INT2NUM(6), INT2NUM(2));
192
- a = value_to_double(
193
- rb_funcall(a_str, rb_intern("to_i"), 1, INT2NUM(16))) /
194
- 255.0;
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--;
195
181
  }
196
-
197
- return color_build(klass, r, g, b, a);
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
198
  }
199
199
 
200
200
  static VALUE color_class_black(VALUE klass) {
@@ -234,10 +234,10 @@ static VALUE color_class_transparent(VALUE klass) {
234
234
  }
235
235
 
236
236
  VALUE color_class_from_vec4(VALUE klass, VALUE vec4) {
237
- double r = value_to_double(rb_funcall(vec4, rb_intern("x"), 0));
238
- double g = value_to_double(rb_funcall(vec4, rb_intern("y"), 0));
239
- double b = value_to_double(rb_funcall(vec4, rb_intern("z"), 0));
240
- double a = value_to_double(rb_funcall(vec4, rb_intern("w"), 0));
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
241
  return color_build(klass, r, g, b, a);
242
242
  }
243
243
 
@@ -245,10 +245,10 @@ VALUE color_class_from_vec3(int argc, VALUE *argv, VALUE klass) {
245
245
  VALUE vec3 = Qnil;
246
246
  VALUE alpha = Qnil;
247
247
  rb_scan_args(argc, argv, "11", &vec3, &alpha);
248
- double r = value_to_double(rb_funcall(vec3, rb_intern("x"), 0));
249
- double g = value_to_double(rb_funcall(vec3, rb_intern("y"), 0));
250
- double b = value_to_double(rb_funcall(vec3, rb_intern("z"), 0));
251
- double a = NIL_P(alpha) ? 1.0 : value_to_double(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
252
  return color_build(klass, r, g, b, a);
253
253
  }
254
254
 
@@ -274,17 +274,17 @@ VALUE color_mul(VALUE self, VALUE scalar) {
274
274
  a->b * b->b, a->a * b->a);
275
275
  }
276
276
  if (rb_obj_is_kind_of(scalar, rb_cNumeric)) {
277
- double s = value_to_double(scalar);
277
+ double s = NUM2DBL(scalar);
278
278
  return color_build(rb_obj_class(self), a->r * s, a->g * s, a->b * s,
279
279
  a->a * s);
280
280
  }
281
- return Qnil;
281
+ rb_raise(rb_eTypeError, "Expected a Color or numeric scalar");
282
282
  }
283
283
 
284
284
  VALUE color_lerp(VALUE self, VALUE other, VALUE t) {
285
285
  ColorData *a = color_get(self);
286
286
  ColorData *b = color_get(other);
287
- double s = value_to_double(t);
287
+ double s = NUM2DBL(t);
288
288
  return color_build(rb_obj_class(self), a->r + (b->r - a->r) * s,
289
289
  a->g + (b->g - a->g) * s, a->b + (b->b - a->b) * s,
290
290
  a->a + (b->a - a->a) * s);
@@ -300,16 +300,8 @@ VALUE color_clamp(VALUE self) {
300
300
 
301
301
  VALUE color_to_bytes(VALUE self) {
302
302
  ColorData *a = color_get(self);
303
- int r = (int)lround(a->r * 255.0);
304
- int g = (int)lround(a->g * 255.0);
305
- int b = (int)lround(a->b * 255.0);
306
- int alpha = (int)lround(a->a * 255.0);
307
- VALUE ary = rb_ary_new_capa(4);
308
- rb_ary_push(ary, INT2NUM(clamp_int(r, 0, 255)));
309
- rb_ary_push(ary, INT2NUM(clamp_int(g, 0, 255)));
310
- rb_ary_push(ary, INT2NUM(clamp_int(b, 0, 255)));
311
- rb_ary_push(ary, INT2NUM(clamp_int(alpha, 0, 255)));
312
- return ary;
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)));
313
305
  }
314
306
 
315
307
  VALUE color_to_hex(VALUE self) {
@@ -367,7 +359,10 @@ VALUE color_near(int argc, VALUE *argv, VALUE self) {
367
359
  rb_scan_args(argc, argv, "11", &other, &epsilon);
368
360
  ColorData *a = color_get(self);
369
361
  ColorData *b = color_get(other);
370
- double eps = NIL_P(epsilon) ? 1e-6 : value_to_double(epsilon);
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
+ }
371
366
 
372
367
  if (fabs(a->r - b->r) < eps && fabs(a->g - b->g) < eps &&
373
368
  fabs(a->b - b->b) < eps && fabs(a->a - b->a) < eps) {
@@ -394,6 +389,13 @@ VALUE color_inspect(VALUE self) {
394
389
  return str;
395
390
  }
396
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
+
397
399
  void Init_color(VALUE module) {
398
400
  cColor = rb_define_class_under(module, "Color", rb_cObject);
399
401
  cVec3 = rb_const_get(mLarb, rb_intern("Vec3"));
@@ -401,6 +403,7 @@ void Init_color(VALUE module) {
401
403
 
402
404
  rb_define_alloc_func(cColor, color_alloc);
403
405
  rb_define_method(cColor, "initialize", color_initialize, -1);
406
+ rb_define_method(cColor, "initialize_copy", color_initialize_copy, 1);
404
407
 
405
408
  rb_define_singleton_method(cColor, "[]", color_class_bracket, -1);
406
409
  rb_define_singleton_method(cColor, "rgb", color_class_rgb, 3);
data/ext/larb/extconf.rb CHANGED
@@ -6,6 +6,6 @@ require "mkmf"
6
6
  have_library("m", "sin")
7
7
 
8
8
  # 最適化フラグ
9
- $CFLAGS << " -O3 -march=native -ffast-math -funroll-loops"
9
+ $CFLAGS << " -O3 -march=native -funroll-loops"
10
10
 
11
11
  create_makefile("larb/larb")
data/ext/larb/larb.h CHANGED
@@ -2,7 +2,30 @@
2
2
  #define LARB_H
3
3
 
4
4
  #include <ruby.h>
5
+ #include <math.h>
5
6
 
6
7
  extern VALUE mLarb;
7
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
+
8
31
  #endif
data/ext/larb/mat2.c CHANGED
@@ -1,4 +1,5 @@
1
1
  #include "mat2.h"
2
+ #include "matrix_utils.h"
2
3
 
3
4
  #include <math.h>
4
5
 
@@ -21,11 +22,6 @@ static const rb_data_type_t mat2_type = {
21
22
  static VALUE cMat2 = Qnil;
22
23
  static VALUE cVec2 = Qnil;
23
24
 
24
- static double value_to_double(VALUE value) {
25
- VALUE coerced = rb_funcall(value, rb_intern("to_f"), 0);
26
- return NUM2DBL(coerced);
27
- }
28
-
29
25
  static Mat2Data *mat2_get(VALUE obj) {
30
26
  Mat2Data *data = NULL;
31
27
  TypedData_Get_Struct(obj, Mat2Data, &mat2_type, data);
@@ -53,28 +49,31 @@ VALUE mat2_alloc(VALUE klass) {
53
49
  }
54
50
 
55
51
  VALUE mat2_initialize(int argc, VALUE *argv, VALUE self) {
52
+ rb_check_frozen(self);
56
53
  VALUE data_arg = Qnil;
57
- Mat2Data *data = mat2_get(self);
58
-
54
+ Mat2Data values = {{1.0, 0.0, 0.0, 1.0}};
59
55
  rb_scan_args(argc, argv, "01", &data_arg);
60
- if (NIL_P(data_arg)) {
61
- data->data[0] = 1.0;
62
- data->data[1] = 0.0;
63
- data->data[2] = 0.0;
64
- data->data[3] = 1.0;
65
- return self;
56
+ if (argc != 0) {
57
+ VALUE ary = rb_check_array_type(data_arg);
58
+ if (NIL_P(ary)) {
59
+ rb_raise(rb_eTypeError, "expected Array");
60
+ }
61
+ if (RARRAY_LEN(ary) != 4) {
62
+ rb_raise(rb_eArgError, "expected 4 elements");
63
+ }
64
+ for (int i = 0; i < 4; i++) {
65
+ values.data[i] = NUM2DBL(rb_ary_entry(ary, i));
66
+ }
66
67
  }
68
+ rb_check_frozen(self);
69
+ *mat2_get(self) = values;
70
+ return self;
71
+ }
67
72
 
68
- VALUE ary = rb_check_array_type(data_arg);
69
- if (NIL_P(ary)) {
70
- rb_raise(rb_eTypeError, "expected Array");
71
- }
72
-
73
- data->data[0] = value_to_double(rb_ary_entry(ary, 0));
74
- data->data[1] = value_to_double(rb_ary_entry(ary, 1));
75
- data->data[2] = value_to_double(rb_ary_entry(ary, 2));
76
- data->data[3] = value_to_double(rb_ary_entry(ary, 3));
77
-
73
+ static VALUE mat2_initialize_copy(VALUE self, VALUE other) {
74
+ if (self == other) return self;
75
+ rb_obj_init_copy(self, other);
76
+ *mat2_get(self) = *mat2_get(other);
78
77
  return self;
79
78
  }
80
79
 
@@ -87,21 +86,21 @@ static VALUE mat2_class_zero(VALUE klass) {
87
86
  }
88
87
 
89
88
  static VALUE mat2_class_rotation(VALUE klass, VALUE radians) {
90
- double r = value_to_double(radians);
89
+ double r = NUM2DBL(radians);
91
90
  double c = cos(r);
92
91
  double s = sin(r);
93
92
  return mat2_build(klass, c, s, -s, c);
94
93
  }
95
94
 
96
95
  static VALUE mat2_class_scaling(VALUE klass, VALUE x, VALUE y) {
97
- return mat2_build(klass, value_to_double(x), 0.0, 0.0, value_to_double(y));
96
+ return mat2_build(klass, NUM2DBL(x), 0.0, 0.0, NUM2DBL(y));
98
97
  }
99
98
 
100
99
  static VALUE mat2_class_from_vec2(VALUE klass, VALUE v1, VALUE v2) {
101
- double x1 = value_to_double(rb_funcall(v1, rb_intern("x"), 0));
102
- double y1 = value_to_double(rb_funcall(v1, rb_intern("y"), 0));
103
- double x2 = value_to_double(rb_funcall(v2, rb_intern("x"), 0));
104
- double y2 = value_to_double(rb_funcall(v2, rb_intern("y"), 0));
100
+ double x1 = NUM2DBL(rb_funcall(v1, rb_intern("x"), 0));
101
+ double y1 = NUM2DBL(rb_funcall(v1, rb_intern("y"), 0));
102
+ double x2 = NUM2DBL(rb_funcall(v2, rb_intern("x"), 0));
103
+ double y2 = NUM2DBL(rb_funcall(v2, rb_intern("y"), 0));
105
104
  return mat2_build(klass, x1, y1, x2, y2);
106
105
  }
107
106
 
@@ -115,12 +114,15 @@ VALUE mat2_aref(VALUE self, VALUE index) {
115
114
  }
116
115
 
117
116
  VALUE mat2_aset(VALUE self, VALUE index, VALUE value) {
117
+ rb_check_frozen(self);
118
118
  Mat2Data *data = mat2_get(self);
119
119
  long idx = NUM2LONG(index);
120
120
  if (idx < 0 || idx > 3) {
121
121
  rb_raise(rb_eIndexError, "index %ld out of range", idx);
122
122
  }
123
- data->data[idx] = value_to_double(value);
123
+ double component = NUM2DBL(value);
124
+ rb_check_frozen(self);
125
+ data->data[idx] = component;
124
126
  return value;
125
127
  }
126
128
 
@@ -137,8 +139,8 @@ VALUE mat2_mul(VALUE self, VALUE other) {
137
139
  }
138
140
 
139
141
  if (rb_obj_is_kind_of(other, cVec2)) {
140
- double x = value_to_double(rb_funcall(other, rb_intern("x"), 0));
141
- double y = value_to_double(rb_funcall(other, rb_intern("y"), 0));
142
+ double x = NUM2DBL(rb_funcall(other, rb_intern("x"), 0));
143
+ double y = NUM2DBL(rb_funcall(other, rb_intern("y"), 0));
142
144
  VALUE vec2_class = rb_const_get(mLarb, rb_intern("Vec2"));
143
145
  return rb_funcall(vec2_class, rb_intern("new"), 2,
144
146
  DBL2NUM(a->data[0] * x + a->data[2] * y),
@@ -146,12 +148,12 @@ VALUE mat2_mul(VALUE self, VALUE other) {
146
148
  }
147
149
 
148
150
  if (rb_obj_is_kind_of(other, rb_cNumeric)) {
149
- double s = value_to_double(other);
151
+ double s = NUM2DBL(other);
150
152
  return mat2_build(rb_obj_class(self), a->data[0] * s, a->data[1] * s,
151
153
  a->data[2] * s, a->data[3] * s);
152
154
  }
153
155
 
154
- return Qnil;
156
+ rb_raise(rb_eTypeError, "unsupported operand for Mat2 multiplication");
155
157
  }
156
158
 
157
159
  VALUE mat2_add(VALUE self, VALUE other) {
@@ -176,15 +178,10 @@ VALUE mat2_determinant(VALUE self) {
176
178
  }
177
179
 
178
180
  VALUE mat2_inverse(VALUE self) {
179
- Mat2Data *a = mat2_get(self);
180
- double det = a->data[0] * a->data[3] - a->data[2] * a->data[1];
181
- if (fabs(det) < 1e-10) {
182
- rb_raise(rb_eRuntimeError, "Matrix is not invertible");
183
- }
184
- double inv_det = 1.0 / det;
185
- return mat2_build(rb_obj_class(self), a->data[3] * inv_det,
186
- -a->data[1] * inv_det, -a->data[2] * inv_det,
187
- a->data[0] * inv_det);
181
+ double inverse[4];
182
+ larb_matrix_inverse(mat2_get(self)->data, inverse, 2);
183
+ return mat2_build(rb_obj_class(self), inverse[0], inverse[1], inverse[2],
184
+ inverse[3]);
188
185
  }
189
186
 
190
187
  VALUE mat2_transpose(VALUE self) {
@@ -201,9 +198,11 @@ VALUE mat2_adjoint(VALUE self) {
201
198
 
202
199
  VALUE mat2_frobenius_norm(VALUE self) {
203
200
  Mat2Data *a = mat2_get(self);
204
- double sum = a->data[0] * a->data[0] + a->data[1] * a->data[1] +
205
- a->data[2] * a->data[2] + a->data[3] * a->data[3];
206
- return DBL2NUM(sqrt(sum));
201
+ double norm = 0.0;
202
+ for (int i = 0; i < 4; i++) {
203
+ norm = hypot(norm, a->data[i]);
204
+ }
205
+ return DBL2NUM(norm);
207
206
  }
208
207
 
209
208
  VALUE mat2_to_a(VALUE self) {
@@ -239,7 +238,10 @@ VALUE mat2_near(int argc, VALUE *argv, VALUE self) {
239
238
  rb_scan_args(argc, argv, "11", &other, &epsilon);
240
239
  Mat2Data *a = mat2_get(self);
241
240
  Mat2Data *b = mat2_get(other);
242
- double eps = NIL_P(epsilon) ? 1e-6 : value_to_double(epsilon);
241
+ double eps = argc < 2 ? 1e-6 : NUM2DBL(epsilon);
242
+ if (!isfinite(eps) || eps <= 0.0) {
243
+ rb_raise(rb_eArgError, "epsilon must be finite and positive");
244
+ }
243
245
 
244
246
  if (fabs(a->data[0] - b->data[0]) < eps &&
245
247
  fabs(a->data[1] - b->data[1]) < eps &&
@@ -274,6 +276,7 @@ void Init_mat2(VALUE module) {
274
276
 
275
277
  rb_define_alloc_func(cMat2, mat2_alloc);
276
278
  rb_define_method(cMat2, "initialize", mat2_initialize, -1);
279
+ rb_define_method(cMat2, "initialize_copy", mat2_initialize_copy, 1);
277
280
 
278
281
  rb_define_singleton_method(cMat2, "identity", mat2_class_identity, 0);
279
282
  rb_define_singleton_method(cMat2, "zero", mat2_class_zero, 0);