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.
data/ext/larb/quat.c CHANGED
@@ -21,11 +21,6 @@ static const rb_data_type_t quat_type = {
21
21
  static VALUE cQuat = Qnil;
22
22
  static VALUE cVec3 = Qnil;
23
23
 
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
24
  static QuatData *quat_get(VALUE obj) {
30
25
  QuatData *data = NULL;
31
26
  TypedData_Get_Struct(obj, QuatData, &quat_type, data);
@@ -53,11 +48,19 @@ static double clamp_double(double value, double min, double max) {
53
48
  }
54
49
 
55
50
  static void normalize_quat(double *x, double *y, double *z, double *w) {
56
- double len = sqrt((*x) * (*x) + (*y) * (*y) + (*z) * (*z) + (*w) * (*w));
57
- *x /= len;
58
- *y /= len;
59
- *z /= len;
60
- *w /= len;
51
+ double values[4] = {*x, *y, *z, *w};
52
+ larb_normalize(values, 4);
53
+ *x = values[0];
54
+ *y = values[1];
55
+ *z = values[2];
56
+ *w = values[3];
57
+ }
58
+
59
+ static VALUE quat_initialize_copy(VALUE self, VALUE other) {
60
+ if (self == other) return self;
61
+ rb_obj_init_copy(self, other);
62
+ *quat_get(self) = *quat_get(other);
63
+ return self;
61
64
  }
62
65
 
63
66
  VALUE quat_alloc(VALUE klass) {
@@ -70,6 +73,7 @@ VALUE quat_alloc(VALUE klass) {
70
73
  }
71
74
 
72
75
  VALUE quat_initialize(int argc, VALUE *argv, VALUE self) {
76
+ rb_check_frozen(self);
73
77
  VALUE vx = Qnil;
74
78
  VALUE vy = Qnil;
75
79
  VALUE vz = Qnil;
@@ -77,18 +81,20 @@ VALUE quat_initialize(int argc, VALUE *argv, VALUE self) {
77
81
  QuatData *data = quat_get(self);
78
82
 
79
83
  rb_scan_args(argc, argv, "04", &vx, &vy, &vz, &vw);
80
- data->x = NIL_P(vx) ? 0.0 : value_to_double(vx);
81
- data->y = NIL_P(vy) ? 0.0 : value_to_double(vy);
82
- data->z = NIL_P(vz) ? 0.0 : value_to_double(vz);
83
- data->w = NIL_P(vw) ? 1.0 : value_to_double(vw);
84
+ QuatData values = {argc > 0 ? NUM2DBL(vx) : 0.0,
85
+ argc > 1 ? NUM2DBL(vy) : 0.0,
86
+ argc > 2 ? NUM2DBL(vz) : 0.0,
87
+ argc > 3 ? NUM2DBL(vw) : 1.0};
88
+ rb_check_frozen(self);
89
+ *data = values;
84
90
 
85
91
  return self;
86
92
  }
87
93
 
88
94
  static VALUE quat_class_bracket(VALUE klass, VALUE x, VALUE y, VALUE z,
89
95
  VALUE w) {
90
- return quat_build(klass, value_to_double(x), value_to_double(y),
91
- value_to_double(z), value_to_double(w));
96
+ return quat_build(klass, NUM2DBL(x), NUM2DBL(y),
97
+ NUM2DBL(z), NUM2DBL(w));
92
98
  }
93
99
 
94
100
  static VALUE quat_class_identity(VALUE klass) {
@@ -97,19 +103,19 @@ static VALUE quat_class_identity(VALUE klass) {
97
103
 
98
104
  static VALUE quat_class_from_axis_angle(VALUE klass, VALUE axis,
99
105
  VALUE radians) {
100
- double half = value_to_double(radians) * 0.5;
106
+ double half = NUM2DBL(radians) * 0.5;
101
107
  double s = sin(half);
102
108
  VALUE normalized = rb_funcall(axis, rb_intern("normalize"), 0);
103
- double x = value_to_double(rb_funcall(normalized, rb_intern("x"), 0));
104
- double y = value_to_double(rb_funcall(normalized, rb_intern("y"), 0));
105
- double z = value_to_double(rb_funcall(normalized, rb_intern("z"), 0));
109
+ double x = NUM2DBL(rb_funcall(normalized, rb_intern("x"), 0));
110
+ double y = NUM2DBL(rb_funcall(normalized, rb_intern("y"), 0));
111
+ double z = NUM2DBL(rb_funcall(normalized, rb_intern("z"), 0));
106
112
  return quat_build(klass, x * s, y * s, z * s, cos(half));
107
113
  }
108
114
 
109
115
  static VALUE quat_class_from_euler(VALUE klass, VALUE x, VALUE y, VALUE z) {
110
- double rx = value_to_double(x) * 0.5;
111
- double ry = value_to_double(y) * 0.5;
112
- double rz = value_to_double(z) * 0.5;
116
+ double rx = NUM2DBL(x) * 0.5;
117
+ double ry = NUM2DBL(y) * 0.5;
118
+ double rz = NUM2DBL(z) * 0.5;
113
119
  double cx = cos(rx);
114
120
  double sx = sin(rx);
115
121
  double cy = cos(ry);
@@ -136,15 +142,15 @@ static VALUE quat_class_look_rotation(int argc, VALUE *argv, VALUE klass) {
136
142
  VALUE up_final =
137
143
  rb_funcall(forward_norm, rb_intern("cross"), 1, right);
138
144
 
139
- double m00 = value_to_double(rb_funcall(right, rb_intern("x"), 0));
140
- double m01 = value_to_double(rb_funcall(up_final, rb_intern("x"), 0));
141
- double m02 = value_to_double(rb_funcall(forward_norm, rb_intern("x"), 0));
142
- double m10 = value_to_double(rb_funcall(right, rb_intern("y"), 0));
143
- double m11 = value_to_double(rb_funcall(up_final, rb_intern("y"), 0));
144
- double m12 = value_to_double(rb_funcall(forward_norm, rb_intern("y"), 0));
145
- double m20 = value_to_double(rb_funcall(right, rb_intern("z"), 0));
146
- double m21 = value_to_double(rb_funcall(up_final, rb_intern("z"), 0));
147
- double m22 = value_to_double(rb_funcall(forward_norm, rb_intern("z"), 0));
145
+ double m00 = NUM2DBL(rb_funcall(right, rb_intern("x"), 0));
146
+ double m01 = NUM2DBL(rb_funcall(up_final, rb_intern("x"), 0));
147
+ double m02 = NUM2DBL(rb_funcall(forward_norm, rb_intern("x"), 0));
148
+ double m10 = NUM2DBL(rb_funcall(right, rb_intern("y"), 0));
149
+ double m11 = NUM2DBL(rb_funcall(up_final, rb_intern("y"), 0));
150
+ double m12 = NUM2DBL(rb_funcall(forward_norm, rb_intern("y"), 0));
151
+ double m20 = NUM2DBL(rb_funcall(right, rb_intern("z"), 0));
152
+ double m21 = NUM2DBL(rb_funcall(up_final, rb_intern("z"), 0));
153
+ double m22 = NUM2DBL(rb_funcall(forward_norm, rb_intern("z"), 0));
148
154
 
149
155
  double trace = m00 + m11 + m22;
150
156
  if (trace > 0.0) {
@@ -173,8 +179,11 @@ static VALUE quat_get_x(VALUE self) {
173
179
  }
174
180
 
175
181
  static VALUE quat_set_x(VALUE self, VALUE value) {
182
+ rb_check_frozen(self);
176
183
  QuatData *data = quat_get(self);
177
- data->x = value_to_double(value);
184
+ double number = NUM2DBL(value);
185
+ rb_check_frozen(self);
186
+ data->x = number;
178
187
  return value;
179
188
  }
180
189
 
@@ -184,8 +193,11 @@ static VALUE quat_get_y(VALUE self) {
184
193
  }
185
194
 
186
195
  static VALUE quat_set_y(VALUE self, VALUE value) {
196
+ rb_check_frozen(self);
187
197
  QuatData *data = quat_get(self);
188
- data->y = value_to_double(value);
198
+ double number = NUM2DBL(value);
199
+ rb_check_frozen(self);
200
+ data->y = number;
189
201
  return value;
190
202
  }
191
203
 
@@ -195,8 +207,11 @@ static VALUE quat_get_z(VALUE self) {
195
207
  }
196
208
 
197
209
  static VALUE quat_set_z(VALUE self, VALUE value) {
210
+ rb_check_frozen(self);
198
211
  QuatData *data = quat_get(self);
199
- data->z = value_to_double(value);
212
+ double number = NUM2DBL(value);
213
+ rb_check_frozen(self);
214
+ data->z = number;
200
215
  return value;
201
216
  }
202
217
 
@@ -206,8 +221,11 @@ static VALUE quat_get_w(VALUE self) {
206
221
  }
207
222
 
208
223
  static VALUE quat_set_w(VALUE self, VALUE value) {
224
+ rb_check_frozen(self);
209
225
  QuatData *data = quat_get(self);
210
- data->w = value_to_double(value);
226
+ double number = NUM2DBL(value);
227
+ rb_check_frozen(self);
228
+ data->w = number;
211
229
  return value;
212
230
  }
213
231
 
@@ -224,9 +242,9 @@ VALUE quat_mul(VALUE self, VALUE other) {
224
242
  }
225
243
 
226
244
  if (rb_obj_is_kind_of(other, cVec3)) {
227
- double vx = value_to_double(rb_funcall(other, rb_intern("x"), 0));
228
- double vy = value_to_double(rb_funcall(other, rb_intern("y"), 0));
229
- double vz = value_to_double(rb_funcall(other, rb_intern("z"), 0));
245
+ double vx = NUM2DBL(rb_funcall(other, rb_intern("x"), 0));
246
+ double vy = NUM2DBL(rb_funcall(other, rb_intern("y"), 0));
247
+ double vz = NUM2DBL(rb_funcall(other, rb_intern("z"), 0));
230
248
 
231
249
  double uvx = a->y * vz - a->z * vy;
232
250
  double uvy = a->z * vx - a->x * vz;
@@ -245,12 +263,12 @@ VALUE quat_mul(VALUE self, VALUE other) {
245
263
  }
246
264
 
247
265
  if (rb_obj_is_kind_of(other, rb_cNumeric)) {
248
- double s = value_to_double(other);
266
+ double s = NUM2DBL(other);
249
267
  return quat_build(rb_obj_class(self), a->x * s, a->y * s, a->z * s,
250
268
  a->w * s);
251
269
  }
252
270
 
253
- return Qnil;
271
+ rb_raise(rb_eTypeError, "expected Quat, Vec3, or Numeric");
254
272
  }
255
273
 
256
274
  VALUE quat_add(VALUE self, VALUE other) {
@@ -280,7 +298,7 @@ VALUE quat_dot(VALUE self, VALUE other) {
280
298
 
281
299
  VALUE quat_length(VALUE self) {
282
300
  QuatData *a = quat_get(self);
283
- return DBL2NUM(sqrt(a->x * a->x + a->y * a->y + a->z * a->z + a->w * a->w));
301
+ return DBL2NUM(hypot(hypot(a->x, a->y), hypot(a->z, a->w)));
284
302
  }
285
303
 
286
304
  VALUE quat_length_squared(VALUE self) {
@@ -299,6 +317,7 @@ VALUE quat_normalize(VALUE self) {
299
317
  }
300
318
 
301
319
  VALUE quat_normalize_bang(VALUE self) {
320
+ rb_check_frozen(self);
302
321
  QuatData *a = quat_get(self);
303
322
  normalize_quat(&a->x, &a->y, &a->z, &a->w);
304
323
  return self;
@@ -311,27 +330,44 @@ VALUE quat_conjugate(VALUE self) {
311
330
 
312
331
  VALUE quat_inverse(VALUE self) {
313
332
  QuatData *a = quat_get(self);
314
- double len_sq = a->x * a->x + a->y * a->y + a->z * a->z + a->w * a->w;
315
- return quat_build(rb_obj_class(self), -a->x / len_sq, -a->y / len_sq,
316
- -a->z / len_sq, a->w / len_sq);
333
+ double values[4] = {a->x, a->y, a->z, a->w};
334
+ larb_normalize(values, 4);
335
+ double scale = fmax(fmax(fabs(a->x), fabs(a->y)),
336
+ fmax(fabs(a->z), fabs(a->w)));
337
+ double len = hypot(hypot(a->x / scale, a->y / scale),
338
+ hypot(a->z / scale, a->w / scale));
339
+ return quat_build(rb_obj_class(self), -values[0] / len / scale,
340
+ -values[1] / len / scale, -values[2] / len / scale,
341
+ values[3] / len / scale);
317
342
  }
318
343
 
319
344
  VALUE quat_lerp(VALUE self, VALUE other, VALUE t) {
320
- QuatData *a = quat_get(self);
321
- QuatData *b = quat_get(other);
322
- double s = value_to_double(t);
323
- double x = a->x + (b->x - a->x) * s;
324
- double y = a->y + (b->y - a->y) * s;
325
- double z = a->z + (b->z - a->z) * s;
326
- double w = a->w + (b->w - a->w) * s;
345
+ QuatData a = *quat_get(self);
346
+ QuatData b = *quat_get(other);
347
+ double s = NUM2DBL(t);
348
+ normalize_quat(&a.x, &a.y, &a.z, &a.w);
349
+ normalize_quat(&b.x, &b.y, &b.z, &b.w);
350
+ double sign = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w < 0.0
351
+ ? -1.0 : 1.0;
352
+ double x = a.x + (sign * b.x - a.x) * s;
353
+ double y = a.y + (sign * b.y - a.y) * s;
354
+ double z = a.z + (sign * b.z - a.z) * s;
355
+ double w = a.w + (sign * b.w - a.w) * s;
327
356
  normalize_quat(&x, &y, &z, &w);
328
357
  return quat_build(rb_obj_class(self), x, y, z, w);
329
358
  }
330
359
 
331
360
  VALUE quat_slerp(VALUE self, VALUE other, VALUE t) {
332
- QuatData *a = quat_get(self);
333
- QuatData *b = quat_get(other);
334
- double s = value_to_double(t);
361
+ QuatData left = *quat_get(self);
362
+ QuatData right = *quat_get(other);
363
+ normalize_quat(&left.x, &left.y, &left.z, &left.w);
364
+ normalize_quat(&right.x, &right.y, &right.z, &right.w);
365
+ QuatData *a = &left;
366
+ QuatData *b = &right;
367
+ double s = NUM2DBL(t);
368
+ if (!isfinite(s)) {
369
+ rb_raise(rb_eArgError, "interpolation amount must be finite");
370
+ }
335
371
  double dot = a->x * b->x + a->y * b->y + a->z * b->z + a->w * b->w;
336
372
 
337
373
  double ox = b->x;
@@ -362,23 +398,26 @@ VALUE quat_slerp(VALUE self, VALUE other, VALUE t) {
362
398
  double s0 = cos(theta) - dot * sin_theta / sin_theta0;
363
399
  double s1 = sin_theta / sin_theta0;
364
400
 
365
- return quat_build(rb_obj_class(self), a->x * s0 + ox * s1,
366
- a->y * s0 + oy * s1, a->z * s0 + oz * s1,
367
- a->w * s0 + ow * s1);
401
+ double x = a->x * s0 + ox * s1;
402
+ double y = a->y * s0 + oy * s1;
403
+ double z = a->z * s0 + oz * s1;
404
+ double w = a->w * s0 + ow * s1;
405
+ normalize_quat(&x, &y, &z, &w);
406
+ return quat_build(rb_obj_class(self), x, y, z, w);
368
407
  }
369
408
 
370
409
  VALUE quat_to_axis_angle(VALUE self) {
371
- QuatData *a = quat_get(self);
372
- double w = clamp_double(a->w, -1.0, 1.0);
373
- double angle = 2.0 * acos(w);
374
- double s = sqrt(1.0 - w * w);
410
+ QuatData a = *quat_get(self);
411
+ normalize_quat(&a.x, &a.y, &a.z, &a.w);
412
+ double s = hypot(hypot(a.x, a.y), a.z);
413
+ double angle = 2.0 * atan2(s, a.w);
375
414
  VALUE axis;
376
- if (s < 0.001) {
415
+ if (s == 0.0) {
377
416
  axis = rb_funcall(cVec3, rb_intern("new"), 3, DBL2NUM(1.0), DBL2NUM(0.0),
378
417
  DBL2NUM(0.0));
379
418
  } else {
380
- axis = rb_funcall(cVec3, rb_intern("new"), 3, DBL2NUM(a->x / s),
381
- DBL2NUM(a->y / s), DBL2NUM(a->z / s));
419
+ axis = rb_funcall(cVec3, rb_intern("new"), 3, DBL2NUM(a.x / s),
420
+ DBL2NUM(a.y / s), DBL2NUM(a.z / s));
382
421
  }
383
422
  VALUE ary = rb_ary_new_capa(2);
384
423
  rb_ary_push(ary, axis);
@@ -447,7 +486,10 @@ VALUE quat_near(int argc, VALUE *argv, VALUE self) {
447
486
  rb_scan_args(argc, argv, "11", &other, &epsilon);
448
487
  QuatData *a = quat_get(self);
449
488
  QuatData *b = quat_get(other);
450
- double eps = NIL_P(epsilon) ? 1e-6 : value_to_double(epsilon);
489
+ double eps = argc < 2 ? 1e-6 : NUM2DBL(epsilon);
490
+ if (!isfinite(eps) || eps <= 0.0) {
491
+ rb_raise(rb_eArgError, "epsilon must be finite and positive");
492
+ }
451
493
 
452
494
  if (fabs(a->x - b->x) < eps && fabs(a->y - b->y) < eps &&
453
495
  fabs(a->z - b->z) < eps && fabs(a->w - b->w) < eps) {
@@ -480,6 +522,8 @@ void Init_quat(VALUE module) {
480
522
 
481
523
  rb_define_alloc_func(cQuat, quat_alloc);
482
524
  rb_define_method(cQuat, "initialize", quat_initialize, -1);
525
+ rb_define_private_method(cQuat, "initialize_copy",
526
+ quat_initialize_copy, 1);
483
527
 
484
528
  rb_define_singleton_method(cQuat, "[]", quat_class_bracket, 4);
485
529
  rb_define_singleton_method(cQuat, "identity", quat_class_identity, 0);
data/ext/larb/quat2.c CHANGED
@@ -23,17 +23,59 @@ static VALUE cQuat = Qnil;
23
23
  static VALUE cVec3 = Qnil;
24
24
  static VALUE cMat4 = Qnil;
25
25
 
26
- static double value_to_double(VALUE value) {
27
- VALUE coerced = rb_funcall(value, rb_intern("to_f"), 0);
28
- return NUM2DBL(coerced);
29
- }
30
-
31
26
  static Quat2Data *quat2_get(VALUE obj) {
32
27
  Quat2Data *data = NULL;
33
28
  TypedData_Get_Struct(obj, Quat2Data, &quat2_type, data);
34
29
  return data;
35
30
  }
36
31
 
32
+ static VALUE quat2_initialize_copy(VALUE self, VALUE other) {
33
+ if (self == other) return self;
34
+ rb_obj_init_copy(self, other);
35
+ *quat2_get(self) = *quat2_get(other);
36
+ return self;
37
+ }
38
+
39
+ static void normalize_quat2(double *values) {
40
+ for (int i = 0; i < 8; i++) {
41
+ if (!isfinite(values[i])) {
42
+ rb_raise(rb_eArgError, "dual quaternion must be finite");
43
+ }
44
+ }
45
+ double scale = 0.0;
46
+ for (int i = 0; i < 4; i++) {
47
+ scale = fmax(scale, fabs(values[i]));
48
+ }
49
+ if (scale == 0.0) {
50
+ rb_raise(rb_eArgError, "real quaternion must be nonzero");
51
+ }
52
+ for (int i = 0; i < 4; i++) {
53
+ values[i] /= scale;
54
+ }
55
+ double len = hypot(hypot(values[0], values[1]), hypot(values[2], values[3]));
56
+ double dual_scale = 0.0;
57
+ for (int i = 0; i < 4; i++) {
58
+ values[i] /= len;
59
+ dual_scale = fmax(dual_scale, fabs(values[i + 4]));
60
+ }
61
+ if (dual_scale > 0.0) {
62
+ /* Remove the parallel component before scaling by the real length. */
63
+ double dot = 0.0;
64
+ for (int i = 0; i < 4; i++) {
65
+ dot += values[i] * (values[i + 4] / dual_scale);
66
+ }
67
+ double ratio = dual_scale / scale;
68
+ for (int i = 0; i < 4; i++) {
69
+ double component = (values[i + 4] / dual_scale - values[i] * dot) / len;
70
+ values[i + 4] = isfinite(ratio) ? component * ratio
71
+ : component * dual_scale / scale;
72
+ if (!isfinite(values[i + 4])) {
73
+ rb_raise(rb_eArgError, "normalized dual quaternion is not finite");
74
+ }
75
+ }
76
+ }
77
+ }
78
+
37
79
  static VALUE quat2_build(VALUE klass, const double *values) {
38
80
  VALUE obj = quat2_alloc(klass);
39
81
  Quat2Data *data = quat2_get(obj);
@@ -56,13 +98,13 @@ static VALUE quat2_class_identity(VALUE klass) {
56
98
 
57
99
  static VALUE quat2_class_from_rotation_translation(VALUE klass, VALUE rotation,
58
100
  VALUE translation) {
59
- double rx = value_to_double(rb_funcall(rotation, rb_intern("x"), 0));
60
- double ry = value_to_double(rb_funcall(rotation, rb_intern("y"), 0));
61
- double rz = value_to_double(rb_funcall(rotation, rb_intern("z"), 0));
62
- double rw = value_to_double(rb_funcall(rotation, rb_intern("w"), 0));
63
- double tx = value_to_double(rb_funcall(translation, rb_intern("x"), 0));
64
- double ty = value_to_double(rb_funcall(translation, rb_intern("y"), 0));
65
- double tz = value_to_double(rb_funcall(translation, rb_intern("z"), 0));
101
+ double rx = NUM2DBL(rb_funcall(rotation, rb_intern("x"), 0));
102
+ double ry = NUM2DBL(rb_funcall(rotation, rb_intern("y"), 0));
103
+ double rz = NUM2DBL(rb_funcall(rotation, rb_intern("z"), 0));
104
+ double rw = NUM2DBL(rb_funcall(rotation, rb_intern("w"), 0));
105
+ double tx = NUM2DBL(rb_funcall(translation, rb_intern("x"), 0));
106
+ double ty = NUM2DBL(rb_funcall(translation, rb_intern("y"), 0));
107
+ double tz = NUM2DBL(rb_funcall(translation, rb_intern("z"), 0));
66
108
 
67
109
  return quat2_build8(
68
110
  klass, rx, ry, rz, rw, (tx * rw + ty * rz - tz * ry) * 0.5,
@@ -77,10 +119,10 @@ static VALUE quat2_class_from_translation(VALUE klass, VALUE translation) {
77
119
  }
78
120
 
79
121
  static VALUE quat2_class_from_rotation(VALUE klass, VALUE rotation) {
80
- double rx = value_to_double(rb_funcall(rotation, rb_intern("x"), 0));
81
- double ry = value_to_double(rb_funcall(rotation, rb_intern("y"), 0));
82
- double rz = value_to_double(rb_funcall(rotation, rb_intern("z"), 0));
83
- double rw = value_to_double(rb_funcall(rotation, rb_intern("w"), 0));
122
+ double rx = NUM2DBL(rb_funcall(rotation, rb_intern("x"), 0));
123
+ double ry = NUM2DBL(rb_funcall(rotation, rb_intern("y"), 0));
124
+ double rz = NUM2DBL(rb_funcall(rotation, rb_intern("z"), 0));
125
+ double rw = NUM2DBL(rb_funcall(rotation, rb_intern("w"), 0));
84
126
  return quat2_build8(klass, rx, ry, rz, rw, 0.0, 0.0, 0.0, 0.0);
85
127
  }
86
128
 
@@ -104,31 +146,26 @@ VALUE quat2_alloc(VALUE klass) {
104
146
  }
105
147
 
106
148
  VALUE quat2_initialize(int argc, VALUE *argv, VALUE self) {
149
+ rb_check_frozen(self);
107
150
  VALUE data_arg = Qnil;
108
151
  Quat2Data *data = quat2_get(self);
152
+ Quat2Data values = {{0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0}};
109
153
 
110
154
  rb_scan_args(argc, argv, "01", &data_arg);
111
- if (NIL_P(data_arg)) {
112
- data->data[0] = 0.0;
113
- data->data[1] = 0.0;
114
- data->data[2] = 0.0;
115
- data->data[3] = 1.0;
116
- data->data[4] = 0.0;
117
- data->data[5] = 0.0;
118
- data->data[6] = 0.0;
119
- data->data[7] = 0.0;
120
- return self;
121
- }
122
-
123
- VALUE ary = rb_check_array_type(data_arg);
124
- if (NIL_P(ary)) {
125
- rb_raise(rb_eTypeError, "expected Array");
126
- }
127
-
128
- for (int i = 0; i < 8; i++) {
129
- data->data[i] = value_to_double(rb_ary_entry(ary, i));
155
+ if (argc > 0) {
156
+ VALUE ary = rb_check_array_type(data_arg);
157
+ if (NIL_P(ary)) {
158
+ rb_raise(rb_eTypeError, "expected Array");
159
+ }
160
+ if (RARRAY_LEN(ary) != 8) {
161
+ rb_raise(rb_eArgError, "expected 8 components");
162
+ }
163
+ for (int i = 0; i < 8; i++) {
164
+ values.data[i] = NUM2DBL(rb_ary_entry(ary, i));
165
+ }
130
166
  }
131
-
167
+ rb_check_frozen(self);
168
+ *data = values;
132
169
  return self;
133
170
  }
134
171
 
@@ -156,12 +193,15 @@ VALUE quat2_aref(VALUE self, VALUE index) {
156
193
  }
157
194
 
158
195
  VALUE quat2_aset(VALUE self, VALUE index, VALUE value) {
196
+ rb_check_frozen(self);
159
197
  Quat2Data *data = quat2_get(self);
160
198
  long idx = NUM2LONG(index);
161
199
  if (idx < 0 || idx > 7) {
162
200
  rb_raise(rb_eIndexError, "index %ld out of range", idx);
163
201
  }
164
- data->data[idx] = value_to_double(value);
202
+ double number = NUM2DBL(value);
203
+ rb_check_frozen(self);
204
+ data->data[idx] = number;
165
205
  return value;
166
206
  }
167
207
 
@@ -208,7 +248,7 @@ VALUE quat2_mul(VALUE self, VALUE other) {
208
248
  }
209
249
 
210
250
  if (rb_obj_is_kind_of(other, rb_cNumeric)) {
211
- double s = value_to_double(other);
251
+ double s = NUM2DBL(other);
212
252
  double values[8];
213
253
  for (int i = 0; i < 8; i++) {
214
254
  values[i] = a->data[i] * s;
@@ -216,7 +256,7 @@ VALUE quat2_mul(VALUE self, VALUE other) {
216
256
  return quat2_build(rb_obj_class(self), values);
217
257
  }
218
258
 
219
- return Qnil;
259
+ rb_raise(rb_eTypeError, "expected Quat2, Vec3, or Numeric");
220
260
  }
221
261
 
222
262
  VALUE quat2_add(VALUE self, VALUE other) {
@@ -254,40 +294,22 @@ VALUE quat2_length_squared(VALUE self) {
254
294
  }
255
295
 
256
296
  VALUE quat2_length(VALUE self) {
257
- double len_sq = NUM2DBL(quat2_length_squared(self));
258
- return DBL2NUM(sqrt(len_sq));
297
+ Quat2Data *a = quat2_get(self);
298
+ return DBL2NUM(hypot(hypot(a->data[0], a->data[1]),
299
+ hypot(a->data[2], a->data[3])));
259
300
  }
260
301
 
261
302
  VALUE quat2_normalize(VALUE self) {
262
- Quat2Data *a = quat2_get(self);
263
- double len = sqrt(a->data[0] * a->data[0] + a->data[1] * a->data[1] +
264
- a->data[2] * a->data[2] + a->data[3] * a->data[3]);
265
- if (len < 1e-10) {
266
- double values[8];
267
- for (int i = 0; i < 8; i++) {
268
- values[i] = a->data[i];
269
- }
270
- return quat2_build(rb_obj_class(self), values);
271
- }
272
- double inv_len = 1.0 / len;
273
- double values[8];
274
- for (int i = 0; i < 8; i++) {
275
- values[i] = a->data[i] * inv_len;
276
- }
277
- return quat2_build(rb_obj_class(self), values);
303
+ Quat2Data values = *quat2_get(self);
304
+ normalize_quat2(values.data);
305
+ return quat2_build(rb_obj_class(self), values.data);
278
306
  }
279
307
 
280
308
  VALUE quat2_normalize_bang(VALUE self) {
281
- Quat2Data *a = quat2_get(self);
282
- double len = sqrt(a->data[0] * a->data[0] + a->data[1] * a->data[1] +
283
- a->data[2] * a->data[2] + a->data[3] * a->data[3]);
284
- if (len < 1e-10) {
285
- return self;
286
- }
287
- double inv_len = 1.0 / len;
288
- for (int i = 0; i < 8; i++) {
289
- a->data[i] *= inv_len;
290
- }
309
+ rb_check_frozen(self);
310
+ Quat2Data values = *quat2_get(self);
311
+ normalize_quat2(values.data);
312
+ *quat2_get(self) = values;
291
313
  return self;
292
314
  }
293
315
 
@@ -300,17 +322,21 @@ VALUE quat2_conjugate(VALUE self) {
300
322
 
301
323
  VALUE quat2_inverse(VALUE self) {
302
324
  Quat2Data *a = quat2_get(self);
303
- double len_sq = a->data[0] * a->data[0] + a->data[1] * a->data[1] +
304
- a->data[2] * a->data[2] + a->data[3] * a->data[3];
305
- if (len_sq < 1e-10) {
306
- return quat2_conjugate(self);
325
+ for (int i = 0; i < 8; i++) {
326
+ if (!isfinite(a->data[i])) {
327
+ rb_raise(rb_eArgError, "dual quaternion must be finite");
328
+ }
307
329
  }
308
- VALUE conj = quat2_conjugate(self);
309
- Quat2Data *c = quat2_get(conj);
310
- double inv_len = 1.0 / len_sq;
330
+ VALUE real_inverse = rb_funcall(quat2_real(self), rb_intern("inverse"), 0);
331
+ VALUE dual_inverse = rb_funcall(real_inverse, rb_intern("*"), 1, quat2_dual(self));
332
+ dual_inverse = rb_funcall(dual_inverse, rb_intern("*"), 1, real_inverse);
333
+ VALUE real = rb_funcall(real_inverse, rb_intern("to_a"), 0);
334
+ VALUE dual = rb_funcall(dual_inverse, rb_intern("to_a"), 0);
335
+ /* (r + epsilon d)^-1 = r^-1 - epsilon r^-1 d r^-1. */
311
336
  double values[8];
312
- for (int i = 0; i < 8; i++) {
313
- values[i] = c->data[i] * inv_len;
337
+ for (int i = 0; i < 4; i++) {
338
+ values[i] = NUM2DBL(rb_ary_entry(real, i));
339
+ values[i + 4] = -NUM2DBL(rb_ary_entry(dual, i));
314
340
  }
315
341
  return quat2_build(rb_obj_class(self), values);
316
342
  }
@@ -347,15 +373,22 @@ VALUE quat2_transform_point(VALUE self, VALUE point) {
347
373
  }
348
374
 
349
375
  VALUE quat2_lerp(VALUE self, VALUE other, VALUE t) {
350
- Quat2Data *a = quat2_get(self);
351
- Quat2Data *b = quat2_get(other);
352
- double s = value_to_double(t);
376
+ Quat2Data a = *quat2_get(self);
377
+ Quat2Data b = *quat2_get(other);
378
+ double s = NUM2DBL(t);
379
+ normalize_quat2(a.data);
380
+ normalize_quat2(b.data);
381
+ double dot = 0.0;
382
+ for (int i = 0; i < 4; i++) {
383
+ dot += a.data[i] * b.data[i];
384
+ }
385
+ double sign = dot < 0.0 ? -1.0 : 1.0;
353
386
  double values[8];
354
387
  for (int i = 0; i < 8; i++) {
355
- values[i] = a->data[i] + (b->data[i] - a->data[i]) * s;
388
+ values[i] = a.data[i] + (sign * b.data[i] - a.data[i]) * s;
356
389
  }
357
- VALUE out = quat2_build(rb_obj_class(self), values);
358
- return quat2_normalize(out);
390
+ normalize_quat2(values);
391
+ return quat2_build(rb_obj_class(self), values);
359
392
  }
360
393
 
361
394
  VALUE quat2_to_mat4(VALUE self) {
@@ -367,7 +400,7 @@ VALUE quat2_to_mat4(VALUE self) {
367
400
  rb_funcall(trans, rb_intern("x"), 0),
368
401
  rb_funcall(trans, rb_intern("y"), 0),
369
402
  rb_funcall(trans, rb_intern("z"), 0));
370
- return rb_funcall(rot_m, rb_intern("*"), 1, trans_m);
403
+ return rb_funcall(trans_m, rb_intern("*"), 1, rot_m);
371
404
  }
372
405
 
373
406
  VALUE quat2_to_a(VALUE self) {
@@ -404,10 +437,13 @@ VALUE quat2_near(int argc, VALUE *argv, VALUE self) {
404
437
  rb_scan_args(argc, argv, "11", &other, &epsilon);
405
438
  Quat2Data *a = quat2_get(self);
406
439
  Quat2Data *b = quat2_get(other);
407
- double eps = NIL_P(epsilon) ? 1e-6 : value_to_double(epsilon);
440
+ double eps = argc < 2 ? 1e-6 : NUM2DBL(epsilon);
441
+ if (!isfinite(eps) || eps <= 0.0) {
442
+ rb_raise(rb_eArgError, "epsilon must be finite and positive");
443
+ }
408
444
 
409
445
  for (int i = 0; i < 8; i++) {
410
- if (fabs(a->data[i] - b->data[i]) >= eps) {
446
+ if (!(fabs(a->data[i] - b->data[i]) < eps)) {
411
447
  return Qfalse;
412
448
  }
413
449
  }
@@ -435,6 +471,8 @@ void Init_quat2(VALUE module) {
435
471
 
436
472
  rb_define_alloc_func(cQuat2, quat2_alloc);
437
473
  rb_define_method(cQuat2, "initialize", quat2_initialize, -1);
474
+ rb_define_private_method(cQuat2, "initialize_copy",
475
+ quat2_initialize_copy, 1);
438
476
 
439
477
  rb_define_singleton_method(cQuat2, "identity", quat2_class_identity, 0);
440
478
  rb_define_singleton_method(cQuat2, "from_rotation_translation",