Repository: google/vector_math.dart

Issue:
Quaternion unary - takes the conjugate instead of memberwise negation
Currently quaternion negation takes the conjugate rather than scaling by -1, which was surprising to me and doesn't seem consistent with binary operator -, which indeed subtracts memberwise.

This seems to be a regression introduced many years ago at https://github.com/google/vector_math.dart/commit/e7bf99e668fd680d2c0deba9b5b61d406868abd3#diff-f11831298dd6e7900532989a327b8f1934d513eb3e33faa366c68c1a44227c5eR377

This applies to both `vector_math` and `vector_math_64`.


Discussion: Hi,

Please send a PR to fix the issue.
@AsturaPhoenix 

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ecfe9218..357977ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
 ## 2.1.5-wip
 
+- Fixed `operator -()` of Quaternion (Contributed by tlserver)
 - Fixed `Matrix3.rotationY` direction (Contributed by tlserver, moritzblume)
 - Added an operator== to Quaternion so that two instances of quaternions can
   be evaluated for equality.
diff --git a/lib/src/vector_math/quaternion.dart b/lib/src/vector_math/quaternion.dart
index ef17fd0d..e0363ac6 100644
--- a/lib/src/vector_math/quaternion.dart
+++ b/lib/src/vector_math/quaternion.dart
@@ -262,6 +262,12 @@ class Quaternion {
     return l;
   }
 
+  /// Negate this.
+  void negate() {
+    _qStorage[3] = -_qStorage[3];
+    conjugate();
+  }
+
   /// Conjugate this.
   void conjugate() {
     _qStorage[2] = -_qStorage[2];
@@ -281,6 +287,9 @@ class Quaternion {
   /// Normalized copy of this.
   Quaternion normalized() => clone()..normalize();
 
+  /// Negated copy of this.
+  Quaternion negated() => clone()..negate();
+
   /// Conjugated copy of this.
   Quaternion conjugated() => clone()..conjugate();
 
@@ -413,7 +422,7 @@ class Quaternion {
   Quaternion operator -(Quaternion other) => clone()..sub(other);
 
   /// Returns negated copy of this.
-  Quaternion operator -() => conjugated();
+  Quaternion operator -() => negated();
 
   /// Access the component of the quaternion at the index [i].
   double operator [](int i) => _qStorage[i];
diff --git a/lib/src/vector_math_64/quaternion.dart b/lib/src/vector_math_64/quaternion.dart
index e4f48424..ebb52523 100644
--- a/lib/src/vector_math_64/quaternion.dart
+++ b/lib/src/vector_math_64/quaternion.dart
@@ -262,6 +262,12 @@ class Quaternion {
     return l;
   }
 
+  /// Negate this.
+  void negate() {
+    _qStorage[3] = -_qStorage[3];
+    conjugate();
+  }
+
   /// Conjugate this.
   void conjugate() {
     _qStorage[2] = -_qStorage[2];
@@ -281,6 +287,9 @@ class Quaternion {
   /// Normalized copy of this.
   Quaternion normalized() => clone()..normalize();
 
+  /// Negated copy of this.
+  Quaternion negated() => clone()..negate();
+
   /// Conjugated copy of this.
   Quaternion conjugated() => clone()..conjugate();
 
@@ -413,7 +422,7 @@ class Quaternion {
   Quaternion operator -(Quaternion other) => clone()..sub(other);
 
   /// Returns negated copy of this.
-  Quaternion operator -() => conjugated();
+  Quaternion operator -() => negated();
 
   /// Access the component of the quaternion at the index [i].
   double operator [](int i) => _qStorage[i];


Test patch:
diff --git a/test/quaternion_test.dart b/test/quaternion_test.dart
index 6d014ccf..af1fab46 100644
--- a/test/quaternion_test.dart
+++ b/test/quaternion_test.dart
@@ -39,6 +39,16 @@ void testQuaternionInstacingFromByteBuffer() {
   expect(offsetVector.w, equals(5.0));
 }
 
+void testNegate(List<Quaternion> input, List<Quaternion> expectedOutput) {
+  assert(input.length == expectedOutput.length);
+  for (var i = 0; i < input.length; i++) {
+    final output1 = -input[i];
+    final output2 = input[i]..negate();
+    relativeTest(output1, expectedOutput[i]);
+    relativeTest(output2, expectedOutput[i]);
+  }
+}
+
 void testConjugate(List<Quaternion> input, List<Quaternion> expectedOutput) {
   assert(input.length == expectedOutput.length);
   for (var i = 0; i < input.length; i++) {
@@ -73,6 +83,18 @@ void testQuaternionVectorRotate(List<Quaternion> inputA, List<Vector3> inputB,
   }
 }
 
+void testQuaternionNegate() {
+  final input = <Quaternion>[];
+  input.add(Quaternion.identity());
+  input.add(Quaternion(0.18260, 0.54770, 0.73030, 0.36510));
+  input.add(Quaternion(0.9889, 0.0, 0.0, 0.14834));
+  final expectedOutput = <Quaternion>[];
+  expectedOutput.add(Quaternion(-0.0, -0.0, -0.0, -1.0));
+  expectedOutput.add(Quaternion(-0.18260, -0.54770, -0.73030, -0.36510));
+  expectedOutput.add(Quaternion(-0.9889, -0.0, -0.0, -0.1483));
+  testNegate(input, expectedOutput);
+}
+
 void testQuaternionConjugate() {
   final input = <Quaternion>[];
   input.add(Quaternion.identity());
@@ -229,6 +251,7 @@ void main() {
   group('Quaternion', () {
     test('Float32List instacing', testQuaternionInstacinfFromFloat32List);
     test('ByteBuffer instacing', testQuaternionInstacingFromByteBuffer);
+    test('Negate', testQuaternionNegate);
     test('Conjugate', testQuaternionConjugate);
     test('Matrix Quaternion Round Trip',
         testQuaternionMatrixQuaternionRoundTrip);