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 +4 -4
- data/CHANGELOG.md +9 -1
- data/README.md +49 -0
- data/ext/larb/color.c +85 -82
- data/ext/larb/extconf.rb +1 -1
- data/ext/larb/larb.h +23 -0
- data/ext/larb/mat2.c +50 -47
- data/ext/larb/mat2d.c +84 -67
- data/ext/larb/mat3.c +73 -87
- data/ext/larb/mat4.c +180 -200
- data/ext/larb/matrix_utils.h +97 -0
- data/ext/larb/quat.c +110 -66
- data/ext/larb/quat2.c +123 -85
- data/ext/larb/vec2.c +52 -29
- data/ext/larb/vec3.c +106 -71
- data/ext/larb/vec4.c +55 -34
- data/lib/larb/version.rb +1 -1
- data/test/larb/color_test.rb +21 -0
- data/test/larb/mat2d_test.rb +25 -0
- data/test/larb/mat4_test.rb +77 -0
- data/test/larb/matrix_test.rb +135 -0
- data/test/larb/native_value_test.rb +157 -0
- data/test/larb/quat2_test.rb +93 -0
- data/test/larb/quat_test.rb +60 -0
- data/test/larb/vec2_test.rb +23 -0
- data/test/larb/vec3_test.rb +63 -0
- data/test/larb/vec4_test.rb +8 -1
- metadata +8 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e661682c31f5246004965e4c9883a15fae7aae30fccb2e9a56a6e0b5e28f7ad1
|
|
4
|
+
data.tar.gz: b4095f395b19e7c38d10659c7ae05169972c329087be2fa4481c97b456872b62
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
57
|
-
if (value
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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,
|
|
143
|
-
|
|
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,
|
|
148
|
-
|
|
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,
|
|
153
|
-
|
|
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 =
|
|
164
|
-
double g =
|
|
165
|
-
double b =
|
|
166
|
-
double a =
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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 =
|
|
238
|
-
double g =
|
|
239
|
-
double b =
|
|
240
|
-
double a =
|
|
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 =
|
|
249
|
-
double g =
|
|
250
|
-
double b =
|
|
251
|
-
double a =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
304
|
-
|
|
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 =
|
|
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
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
|
|
58
|
-
|
|
54
|
+
Mat2Data values = {{1.0, 0.0, 0.0, 1.0}};
|
|
59
55
|
rb_scan_args(argc, argv, "01", &data_arg);
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
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 =
|
|
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,
|
|
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 =
|
|
102
|
-
double y1 =
|
|
103
|
-
double x2 =
|
|
104
|
-
double y2 =
|
|
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
|
-
|
|
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 =
|
|
141
|
-
double y =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
|
205
|
-
|
|
206
|
-
|
|
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 =
|
|
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);
|