• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / vector_math.dart / 17181727464

04 Aug 2025 07:19PM UTC coverage: 26.702% (+0.3%) from 26.388%
17181727464

push

github

web-flow
Bump min SDK to 3.7, update dependencies, reformat (#348)

496 of 1182 new or added lines in 55 files covered. (41.96%)

18 existing lines in 8 files now uncovered.

4463 of 16714 relevant lines covered (26.7%)

1.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.05
/lib/src/vector_math_64/quaternion.dart
1
// Copyright (c) 2015, Google Inc. Please see the AUTHORS file for details.
2
// All rights reserved. Use of this source code is governed by a BSD-style
3
// license that can be found in the LICENSE file.
4

5
part of '../../vector_math_64.dart';
6

7
/// Defines a [Quaternion] (a four-dimensional vector) for efficient rotation
8
/// calculations.
9
///
10
/// Quaternion are better for interpolating between rotations and avoid the
11
/// [gimbal lock](http://de.wikipedia.org/wiki/Gimbal_Lock) problem compared to
12
/// euler rotations.
13
class Quaternion {
14
  final Float64List _qStorage;
15

16
  /// Access the internal [storage] of the quaternions components.
17
  Float64List get storage => _qStorage;
×
18

19
  /// Access the [x] component of the quaternion.
20
  double get x => _qStorage[0];
×
21
  set x(double x) {
×
22
    _qStorage[0] = x;
×
23
  }
24

25
  /// Access the [y] component of the quaternion.
26
  double get y => _qStorage[1];
×
27
  set y(double y) {
×
28
    _qStorage[1] = y;
×
29
  }
30

31
  /// Access the [z] component of the quaternion.
32
  double get z => _qStorage[2];
×
33
  set z(double z) {
×
34
    _qStorage[2] = z;
×
35
  }
36

37
  /// Access the [w] component of the quaternion.
38
  double get w => _qStorage[3];
×
39
  set w(double w) {
×
40
    _qStorage[3] = w;
×
41
  }
42

43
  Quaternion._() : _qStorage = Float64List(4);
2✔
44

45
  /// Constructs a quaternion using the raw values [x], [y], [z], and [w].
46
  factory Quaternion(double x, double y, double z, double w) =>
×
47
      Quaternion._()..setValues(x, y, z, w);
×
48

49
  /// Constructs a quaternion from a rotation matrix [rotationMatrix].
50
  factory Quaternion.fromRotation(Matrix3 rotationMatrix) =>
×
51
      Quaternion._()..setFromRotation(rotationMatrix);
×
52

53
  /// Constructs a quaternion from a rotation of [angle] around [axis].
54
  factory Quaternion.axisAngle(Vector3 axis, double angle) =>
1✔
55
      Quaternion._()..setAxisAngle(axis, angle);
2✔
56

57
  /// Constructs a quaternion to be the rotation that rotates vector [a] to [b].
58
  factory Quaternion.fromTwoVectors(Vector3 a, Vector3 b) =>
×
59
      Quaternion._()..setFromTwoVectors(a, b);
×
60

61
  /// Constructs a quaternion as a copy of [original].
62
  factory Quaternion.copy(Quaternion original) =>
×
63
      Quaternion._()..setFrom(original);
×
64

65
  /// Constructs a quaternion with a random rotation. The random number
66
  /// generator [rn] is used to generate the random numbers for the rotation.
67
  factory Quaternion.random(math.Random rn) => Quaternion._()..setRandom(rn);
×
68

69
  /// Constructs a quaternion set to the identity quaternion.
70
  factory Quaternion.identity() => Quaternion._().._qStorage[3] = 1.0;
×
71

72
  /// Constructs a quaternion from time derivative of [q] with angular
73
  /// velocity [omega].
74
  factory Quaternion.dq(Quaternion q, Vector3 omega) =>
×
75
      Quaternion._()..setDQ(q, omega);
×
76

77
  /// Constructs a quaternion from [yaw], [pitch] and [roll].
78
  factory Quaternion.euler(double yaw, double pitch, double roll) =>
×
79
      Quaternion._()..setEuler(yaw, pitch, roll);
×
80

81
  /// Constructs a quaternion with given Float64List as [storage].
82
  Quaternion.fromFloat64List(this._qStorage);
×
83

84
  /// Constructs a quaternion with a [storage] that views given [buffer]
85
  /// starting at [offset]. [offset] has to be multiple of
86
  /// [Float64List.bytesPerElement].
87
  Quaternion.fromBuffer(ByteBuffer buffer, int offset)
×
NEW
88
    : _qStorage = Float64List.view(buffer, offset, 4);
×
89

90
  /// Returns a new copy of this.
91
  Quaternion clone() => Quaternion.copy(this);
×
92

93
  /// Copy [source] into this.
94
  void setFrom(Quaternion source) {
×
95
    final sourceStorage = source._qStorage;
×
96
    _qStorage[0] = sourceStorage[0];
×
97
    _qStorage[1] = sourceStorage[1];
×
98
    _qStorage[2] = sourceStorage[2];
×
99
    _qStorage[3] = sourceStorage[3];
×
100
  }
101

102
  /// Set the quaternion to the raw values [x], [y], [z], and [w].
103
  void setValues(double x, double y, double z, double w) {
×
104
    _qStorage[0] = x;
×
105
    _qStorage[1] = y;
×
106
    _qStorage[2] = z;
×
107
    _qStorage[3] = w;
×
108
  }
109

110
  /// Set the quaternion with rotation of [radians] around [axis].
111
  void setAxisAngle(Vector3 axis, double radians) {
1✔
112
    final len = axis.length;
1✔
113
    if (len == 0.0) {
1✔
114
      return;
115
    }
116
    final halfSin = math.sin(radians * 0.5) / len;
3✔
117
    final axisStorage = axis.storage;
1✔
118
    _qStorage[0] = axisStorage[0] * halfSin;
4✔
119
    _qStorage[1] = axisStorage[1] * halfSin;
4✔
120
    _qStorage[2] = axisStorage[2] * halfSin;
4✔
121
    _qStorage[3] = math.cos(radians * 0.5);
4✔
122
  }
123

124
  /// Set the quaternion with rotation from a rotation matrix [rotationMatrix].
125
  void setFromRotation(Matrix3 rotationMatrix) {
×
126
    final rotationMatrixStorage = rotationMatrix.storage;
×
127
    final trace = rotationMatrix.trace();
×
128
    if (trace > 0.0) {
×
129
      var s = math.sqrt(trace + 1.0);
×
130
      _qStorage[3] = s * 0.5;
×
131
      s = 0.5 / s;
×
132
      _qStorage[0] = (rotationMatrixStorage[5] - rotationMatrixStorage[7]) * s;
×
133
      _qStorage[1] = (rotationMatrixStorage[6] - rotationMatrixStorage[2]) * s;
×
134
      _qStorage[2] = (rotationMatrixStorage[1] - rotationMatrixStorage[3]) * s;
×
135
    } else {
136
      final i =
NEW
137
          rotationMatrixStorage[0] < rotationMatrixStorage[4]
×
NEW
138
              ? (rotationMatrixStorage[4] < rotationMatrixStorage[8] ? 2 : 1)
×
NEW
139
              : (rotationMatrixStorage[0] < rotationMatrixStorage[8] ? 2 : 0);
×
140
      final j = (i + 1) % 3;
×
141
      final k = (i + 2) % 3;
×
NEW
142
      var s = math.sqrt(
×
NEW
143
        rotationMatrixStorage[rotationMatrix.index(i, i)] -
×
NEW
144
            rotationMatrixStorage[rotationMatrix.index(j, j)] -
×
NEW
145
            rotationMatrixStorage[rotationMatrix.index(k, k)] +
×
146
            1.0,
147
      );
148
      _qStorage[i] = s * 0.5;
×
149
      s = 0.5 / s;
×
NEW
150
      _qStorage[3] =
×
NEW
151
          (rotationMatrixStorage[rotationMatrix.index(k, j)] -
×
UNCOV
152
              rotationMatrixStorage[rotationMatrix.index(j, k)]) *
×
153
          s;
NEW
154
      _qStorage[j] =
×
NEW
155
          (rotationMatrixStorage[rotationMatrix.index(j, i)] +
×
UNCOV
156
              rotationMatrixStorage[rotationMatrix.index(i, j)]) *
×
157
          s;
NEW
158
      _qStorage[k] =
×
NEW
159
          (rotationMatrixStorage[rotationMatrix.index(k, i)] +
×
UNCOV
160
              rotationMatrixStorage[rotationMatrix.index(i, k)]) *
×
161
          s;
162
    }
163
  }
164

165
  void setFromTwoVectors(Vector3 a, Vector3 b) {
×
166
    final v1 = a.normalized();
×
167
    final v2 = b.normalized();
×
168

169
    final c = v1.dot(v2);
×
170
    var angle = math.acos(c);
×
171
    var axis = v1.cross(v2);
×
172

173
    if ((1.0 + c).abs() < 0.0005) {
×
174
      // c \approx -1 indicates 180 degree rotation
175
      angle = math.pi;
176

177
      // a and b are parallel in opposite directions. We need any
178
      // vector as our rotation axis that is perpendicular.
179
      // Find one by taking the cross product of v1 with an appropriate unit
180
      // axis
181
      if (v1.x > v1.y && v1.x > v1.z) {
×
182
        // v1 points in a dominantly x direction, so don't cross with that axis
183
        axis = v1.cross(Vector3(0.0, 1.0, 0.0));
×
184
      } else {
185
        // Predominantly points in some other direction, so x-axis should be
186
        // safe
187
        axis = v1.cross(Vector3(1.0, 0.0, 0.0));
×
188
      }
189
    } else if ((1.0 - c).abs() < 0.0005) {
×
190
      // c \approx 1 is 0-degree rotation, axis is arbitrary
191
      angle = 0.0;
192
      axis = Vector3(1.0, 0.0, 0.0);
×
193
    }
194

195
    setAxisAngle(axis.normalized(), angle);
×
196
  }
197

198
  /// Set the quaternion to a random rotation. The random number generator [rn]
199
  /// is used to generate the random numbers for the rotation.
200
  void setRandom(math.Random rn) {
×
201
    // From: "Uniform Random Rotations", Ken Shoemake, Graphics Gems III,
202
    // pg. 124-132.
203
    final x0 = rn.nextDouble();
×
204
    final r1 = math.sqrt(1.0 - x0);
×
205
    final r2 = math.sqrt(x0);
×
206
    final t1 = math.pi * 2.0 * rn.nextDouble();
×
207
    final t2 = math.pi * 2.0 * rn.nextDouble();
×
208
    final c1 = math.cos(t1);
×
209
    final s1 = math.sin(t1);
×
210
    final c2 = math.cos(t2);
×
211
    final s2 = math.sin(t2);
×
212
    _qStorage[0] = s1 * r1;
×
213
    _qStorage[1] = c1 * r1;
×
214
    _qStorage[2] = s2 * r2;
×
215
    _qStorage[3] = c2 * r2;
×
216
  }
217

218
  /// Set the quaternion to the time derivative of [q] with angular velocity
219
  /// [omega].
220
  void setDQ(Quaternion q, Vector3 omega) {
×
221
    final qStorage = q._qStorage;
×
222
    final omegaStorage = omega.storage;
×
223
    final qx = qStorage[0];
×
224
    final qy = qStorage[1];
×
225
    final qz = qStorage[2];
×
226
    final qw = qStorage[3];
×
227
    final ox = omegaStorage[0];
×
228
    final oy = omegaStorage[1];
×
229
    final oz = omegaStorage[2];
×
230
    final x = ox * qw + oy * qz - oz * qy;
×
231
    final y = oy * qw + oz * qx - ox * qz;
×
232
    final z = oz * qw + ox * qy - oy * qx;
×
233
    final w = -ox * qx - oy * qy - oz * qz;
×
234
    _qStorage[0] = x * 0.5;
×
235
    _qStorage[1] = y * 0.5;
×
236
    _qStorage[2] = z * 0.5;
×
237
    _qStorage[3] = w * 0.5;
×
238
  }
239

240
  /// Set quaternion with rotation of [yaw], [pitch] and [roll].
241
  void setEuler(double yaw, double pitch, double roll) {
×
242
    final halfYaw = yaw * 0.5;
×
243
    final halfPitch = pitch * 0.5;
×
244
    final halfRoll = roll * 0.5;
×
245
    final cosYaw = math.cos(halfYaw);
×
246
    final sinYaw = math.sin(halfYaw);
×
247
    final cosPitch = math.cos(halfPitch);
×
248
    final sinPitch = math.sin(halfPitch);
×
249
    final cosRoll = math.cos(halfRoll);
×
250
    final sinRoll = math.sin(halfRoll);
×
251
    _qStorage[0] = cosRoll * sinPitch * cosYaw + sinRoll * cosPitch * sinYaw;
×
252
    _qStorage[1] = cosRoll * cosPitch * sinYaw - sinRoll * sinPitch * cosYaw;
×
253
    _qStorage[2] = sinRoll * cosPitch * cosYaw - cosRoll * sinPitch * sinYaw;
×
254
    _qStorage[3] = cosRoll * cosPitch * cosYaw + sinRoll * sinPitch * sinYaw;
×
255
  }
256

257
  /// Normalize this.
258
  double normalize() {
×
259
    final l = length;
×
260
    if (l == 0.0) {
×
261
      return 0.0;
262
    }
263
    final d = 1.0 / l;
×
264
    _qStorage[0] *= d;
×
265
    _qStorage[1] *= d;
×
266
    _qStorage[2] *= d;
×
267
    _qStorage[3] *= d;
×
268
    return l;
269
  }
270

271
  /// Negate this.
272
  void negate() {
×
273
    _qStorage[3] = -_qStorage[3];
×
274
    conjugate();
×
275
  }
276

277
  /// Conjugate this.
278
  void conjugate() {
×
279
    _qStorage[2] = -_qStorage[2];
×
280
    _qStorage[1] = -_qStorage[1];
×
281
    _qStorage[0] = -_qStorage[0];
×
282
  }
283

284
  /// Invert this.
285
  void inverse() {
×
286
    final l = 1.0 / length2;
×
287
    _qStorage[3] = _qStorage[3] * l;
×
288
    _qStorage[2] = -_qStorage[2] * l;
×
289
    _qStorage[1] = -_qStorage[1] * l;
×
290
    _qStorage[0] = -_qStorage[0] * l;
×
291
  }
292

293
  /// Normalized copy of this.
294
  Quaternion normalized() => clone()..normalize();
×
295

296
  /// Negated copy of this.
297
  Quaternion negated() => clone()..negate();
×
298

299
  /// Conjugated copy of this.
300
  Quaternion conjugated() => clone()..conjugate();
×
301

302
  /// Inverted copy of this.
303
  Quaternion inverted() => clone()..inverse();
×
304

305
  /// [radians] of rotation around the [axis] of the rotation.
306
  double get radians => 2.0 * math.acos(_qStorage[3]);
5✔
307

308
  /// [axis] of rotation.
309
  Vector3 get axis {
1✔
310
    final den = 1.0 - (_qStorage[3] * _qStorage[3]);
6✔
311
    if (den < 1e-9) {
1✔
312
      // 0-angle rotation, so axis does not matter
313
      return Vector3.zero();
×
314
    }
315

316
    final scale = 1.0 / math.sqrt(den);
2✔
317
    return Vector3(
1✔
318
      _qStorage[0] * scale,
3✔
319
      _qStorage[1] * scale,
3✔
320
      _qStorage[2] * scale,
3✔
321
    );
322
  }
323

324
  /// Length squared.
325
  double get length2 {
×
326
    final x = _qStorage[0];
×
327
    final y = _qStorage[1];
×
328
    final z = _qStorage[2];
×
329
    final w = _qStorage[3];
×
330
    return (x * x) + (y * y) + (z * z) + (w * w);
×
331
  }
332

333
  /// Length.
334
  double get length => math.sqrt(length2);
×
335

336
  /// Returns a copy of [v] rotated by quaternion.
337
  Vector3 rotated(Vector3 v) {
×
338
    final out = v.clone();
×
339
    rotate(out);
×
340
    return out;
341
  }
342

343
  /// Rotates [v] by this.
344
  Vector3 rotate(Vector3 v) {
×
345
    // conjugate(this) * [v,0] * this
346
    final w = _qStorage[3];
×
347
    final z = _qStorage[2];
×
348
    final y = _qStorage[1];
×
349
    final x = _qStorage[0];
×
350
    final tiw = w;
351
    final tiz = -z;
×
352
    final tiy = -y;
×
353
    final tix = -x;
×
354
    final tx = tiw * v.x + tix * 0.0 + tiy * v.z - tiz * v.y;
×
355
    final ty = tiw * v.y + tiy * 0.0 + tiz * v.x - tix * v.z;
×
356
    final tz = tiw * v.z + tiz * 0.0 + tix * v.y - tiy * v.x;
×
357
    final tw = tiw * 0.0 - tix * v.x - tiy * v.y - tiz * v.z;
×
358
    final result_x = tw * x + tx * w + ty * z - tz * y;
×
359
    final result_y = tw * y + ty * w + tz * x - tx * z;
×
360
    final result_z = tw * z + tz * w + tx * y - ty * x;
×
361
    final vStorage = v.storage;
×
362
    vStorage[2] = result_z;
×
363
    vStorage[1] = result_y;
×
364
    vStorage[0] = result_x;
×
365
    return v;
366
  }
367

368
  /// Add [arg] to this.
369
  void add(Quaternion arg) {
×
370
    final argStorage = arg._qStorage;
×
371
    _qStorage[0] = _qStorage[0] + argStorage[0];
×
372
    _qStorage[1] = _qStorage[1] + argStorage[1];
×
373
    _qStorage[2] = _qStorage[2] + argStorage[2];
×
374
    _qStorage[3] = _qStorage[3] + argStorage[3];
×
375
  }
376

377
  /// Subtracts [arg] from this.
378
  void sub(Quaternion arg) {
×
379
    final argStorage = arg._qStorage;
×
380
    _qStorage[0] = _qStorage[0] - argStorage[0];
×
381
    _qStorage[1] = _qStorage[1] - argStorage[1];
×
382
    _qStorage[2] = _qStorage[2] - argStorage[2];
×
383
    _qStorage[3] = _qStorage[3] - argStorage[3];
×
384
  }
385

386
  /// Scales this by [scale].
387
  void scale(double scale) {
×
388
    _qStorage[3] = _qStorage[3] * scale;
×
389
    _qStorage[2] = _qStorage[2] * scale;
×
390
    _qStorage[1] = _qStorage[1] * scale;
×
391
    _qStorage[0] = _qStorage[0] * scale;
×
392
  }
393

394
  /// Scaled copy of this.
395
  Quaternion scaled(double scale) => clone()..scale(scale);
×
396

397
  /// this rotated by [other].
398
  Quaternion operator *(Quaternion other) {
×
399
    final w = _qStorage[3];
×
400
    final z = _qStorage[2];
×
401
    final y = _qStorage[1];
×
402
    final x = _qStorage[0];
×
403
    final otherStorage = other._qStorage;
×
404
    final ow = otherStorage[3];
×
405
    final oz = otherStorage[2];
×
406
    final oy = otherStorage[1];
×
407
    final ox = otherStorage[0];
×
408
    return Quaternion(
×
NEW
409
      w * ox + x * ow + y * oz - z * oy,
×
NEW
410
      w * oy + y * ow + z * ox - x * oz,
×
NEW
411
      w * oz + z * ow + x * oy - y * ox,
×
NEW
412
      w * ow - x * ox - y * oy - z * oz,
×
413
    );
414
  }
415

416
  /// Check if two quaternions are the same.
417
  @override
×
418
  bool operator ==(Object other) =>
419
      (other is Quaternion) &&
×
420
      (_qStorage[3] == other._qStorage[3]) &&
×
421
      (_qStorage[2] == other._qStorage[2]) &&
×
422
      (_qStorage[1] == other._qStorage[1]) &&
×
423
      (_qStorage[0] == other._qStorage[0]);
×
424

425
  @override
×
426
  int get hashCode => Object.hashAll(_qStorage);
×
427

428
  /// Returns copy of this + [other].
429
  Quaternion operator +(Quaternion other) => clone()..add(other);
×
430

431
  /// Returns copy of this - [other].
432
  Quaternion operator -(Quaternion other) => clone()..sub(other);
×
433

434
  /// Returns negated copy of this.
435
  Quaternion operator -() => negated();
×
436

437
  /// Access the component of the quaternion at the index [i].
438
  double operator [](int i) => _qStorage[i];
×
439

440
  /// Set the component of the quaternion at the index [i].
441
  void operator []=(int i, double arg) {
×
442
    _qStorage[i] = arg;
×
443
  }
444

445
  /// Returns a rotation matrix containing the same rotation as this.
446
  Matrix3 asRotationMatrix() => copyRotationInto(Matrix3.zero());
×
447

448
  /// Set [rotationMatrix] to a rotation matrix containing the same rotation as
449
  /// this.
450
  Matrix3 copyRotationInto(Matrix3 rotationMatrix) {
×
451
    final d = length2;
×
452
    assert(d != 0.0);
×
453
    final s = 2.0 / d;
×
454

455
    final x = _qStorage[0];
×
456
    final y = _qStorage[1];
×
457
    final z = _qStorage[2];
×
458
    final w = _qStorage[3];
×
459

460
    final xs = x * s;
×
461
    final ys = y * s;
×
462
    final zs = z * s;
×
463

464
    final wx = w * xs;
×
465
    final wy = w * ys;
×
466
    final wz = w * zs;
×
467

468
    final xx = x * xs;
×
469
    final xy = x * ys;
×
470
    final xz = x * zs;
×
471

472
    final yy = y * ys;
×
473
    final yz = y * zs;
×
474
    final zz = z * zs;
×
475

476
    final rotationMatrixStorage = rotationMatrix.storage;
×
477
    rotationMatrixStorage[0] = 1.0 - (yy + zz); // column 0
×
478
    rotationMatrixStorage[1] = xy + wz;
×
479
    rotationMatrixStorage[2] = xz - wy;
×
480
    rotationMatrixStorage[3] = xy - wz; // column 1
×
481
    rotationMatrixStorage[4] = 1.0 - (xx + zz);
×
482
    rotationMatrixStorage[5] = yz + wx;
×
483
    rotationMatrixStorage[6] = xz + wy; // column 2
×
484
    rotationMatrixStorage[7] = yz - wx;
×
485
    rotationMatrixStorage[8] = 1.0 - (xx + yy);
×
486
    return rotationMatrix;
487
  }
488

489
  /// Printable string.
490
  @override
×
NEW
491
  String toString() =>
×
NEW
492
      '${_qStorage[0]}, ${_qStorage[1]},'
×
UNCOV
493
      ' ${_qStorage[2]} @ ${_qStorage[3]}';
×
494

495
  /// Relative error between this and [correct].
496
  double relativeError(Quaternion correct) {
×
497
    final diff = correct - this;
×
498
    final norm_diff = diff.length;
×
499
    final correct_norm = correct.length;
×
500
    return norm_diff / correct_norm;
×
501
  }
502

503
  /// Absolute error between this and [correct].
504
  double absoluteError(Quaternion correct) {
×
505
    final this_norm = length;
×
506
    final correct_norm = correct.length;
×
507
    final norm_diff = (this_norm - correct_norm).abs();
×
508
    return norm_diff;
509
  }
510
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc