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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -1
- data/README.md +63 -1
- data/ext/larb/color.c +449 -0
- data/ext/larb/color.h +35 -0
- data/ext/larb/extconf.rb +11 -0
- data/ext/larb/larb.c +27 -0
- data/ext/larb/larb.h +31 -0
- data/ext/larb/mat2.c +303 -0
- data/ext/larb/mat2.h +30 -0
- data/ext/larb/mat2d.c +397 -0
- data/ext/larb/mat2d.h +35 -0
- data/ext/larb/mat3.c +455 -0
- data/ext/larb/mat3.h +33 -0
- data/ext/larb/mat4.c +651 -0
- data/ext/larb/mat4.h +31 -0
- data/ext/larb/matrix_utils.h +97 -0
- data/ext/larb/quat.c +567 -0
- data/ext/larb/quat.h +39 -0
- data/ext/larb/quat2.c +511 -0
- data/ext/larb/quat2.h +39 -0
- data/ext/larb/vec2.c +365 -0
- data/ext/larb/vec2.h +43 -0
- data/ext/larb/vec3.c +538 -0
- data/ext/larb/vec3.h +52 -0
- data/ext/larb/vec4.c +361 -0
- data/ext/larb/vec4.h +38 -0
- data/lib/larb/version.rb +5 -0
- data/lib/larb.rb +2 -14
- data/test/larb/color_test.rb +299 -0
- data/test/larb/mat2_test.rb +144 -0
- data/test/larb/mat2d_test.rb +197 -0
- data/test/larb/mat3_test.rb +147 -0
- data/test/larb/mat4_test.rb +347 -0
- data/test/larb/matrix_test.rb +135 -0
- data/test/larb/native_value_test.rb +157 -0
- data/test/larb/quat2_test.rb +254 -0
- data/test/larb/quat_test.rb +284 -0
- data/test/larb/vec2_test.rb +274 -0
- data/test/larb/vec3_test.rb +373 -0
- data/test/larb/vec4_test.rb +196 -0
- data/test/test_helper.rb +4 -0
- metadata +60 -18
- data/Rakefile +0 -11
- data/lib/larb/color.rb +0 -148
- data/lib/larb/mat2.rb +0 -119
- data/lib/larb/mat2d.rb +0 -180
- data/lib/larb/mat3.rb +0 -238
- data/lib/larb/mat4.rb +0 -329
- data/lib/larb/quat.rb +0 -238
- data/lib/larb/quat2.rb +0 -193
- data/lib/larb/vec2.rb +0 -150
- data/lib/larb/vec3.rb +0 -218
- data/lib/larb/vec4.rb +0 -125
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,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 [](https://badge.fury.io/rb/larb) [](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
|
data/ext/larb/extconf.rb
ADDED
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
|