diff --git a/README.md b/README.md
index 174da02..59991e1 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ between corresponding points from either point cloud, where RMSD is defined as:
```
T = a translation vector (a 1-D array containing x,y,z displacements),
R = a rotation matrix (a 3x3 array whose determinant = 1),
- c = a scale factor (a number, optional, 1 by default)
+ c = a scalar (a number, optional, 1 by default)
```
After invoking Superpose3D::Superpose(), the optimal translation, rotation and
scale factor are stored in Superpose3D data members named
@@ -35,7 +35,7 @@ scale factor are stored in Superpose3D data members named
(*T* is implemented as a C-style array, and
*R* is implemented as a C-style 3x3 array in pointer-to-pointer format.)
-A *weighted* version of the RMSD minimization algorithm is also available
+A weighted version of the RMSD minimization algorithm is also available
if the caller supplies an extra argument specifying the weight of every
point in the cloud (*wn*). In that case, RMSD is defined as:
@@ -94,6 +94,7 @@ double rmsd = superposer.Superpose(X, x);
// Note: The optimal rotation, translation, and scale factor will be stored in
// superposer.R, superposer.T, and superposer.c, respectively.
+// (A quaternion describing the rotation is stored in superpose.p.)
```
*(A complete working example can be found [here](tests/test_superpose3d.cpp).)*
@@ -103,6 +104,16 @@ If you want to allow scale transformations, then use:
superposer.Superpose(X, x, true);
```
+#### Rotation angle, axis, and quaternion
+If the corresponding rotation angle and rotation axis are also needed, they
+can be inferred from the ***q*** data member ("*superposer.q*" in the
+example above). After invoking Superpose(), the *q* member will store the
+[quaternion corresponding to rotation *R*](https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation).
+The first element of *q* will store *cos(θ/2)* (where *θ* is the
+rotation angle). The remaining 3 elements of *q* will store the
+axis of rotation (with length *sin(θ/2)*).
+
+#### Weighted RMSD
By default point in the point cloud will be given equal weights when
calculating RMSD. If you want to specify different weights for each point
(ie. *wn* in the formula above), then see the following example:
diff --git a/include/superpose3d.hpp b/include/superpose3d.hpp
index f3f1a9b..a4c0c8e 100644
--- a/include/superpose3d.hpp
+++ b/include/superpose3d.hpp
@@ -41,6 +41,10 @@ class Superpose3D {
Scalar **R; //!< store optimal rotation here (this is a 3x3 array).
Scalar T[3]; //!< store optimal translation here
Scalar c; //!< store optimal scale (typically 1 unless requested by the user)
+ Scalar q[4]; //!< quaternion corresponding to the rotation stored in R.
+ // The first entry of q is cos(θ/2). The remaining 3 entries
+ // of q are the axis of rotation (with length sin(θ/2)).
+ // (Note: This is not the same as "p" from Diamond's 1988 paper.)
Superpose3D(size_t N = 0); //!< N=number of points in both point clouds
@@ -217,8 +221,8 @@ Superpose(ConstArrayOfCoords aaXf, // coords for the "frozen" object
P[3][2] = V[2];
P[3][3] = 0.0;
- // The vector "p" will contain the optimal rotation (in quaternion format)
Scalar p[4];
+ // The vector "p" will contain the optimal rotation (in quaternion format)
Scalar eval_max = eigen_calc.PrincipalEigen(P, p, true);
// Now normalize p
@@ -242,6 +246,11 @@ Superpose(ConstArrayOfCoords aaXf, // coords for the "frozen" object
R[0][2] = 2*(p[0]*p[2] + p[1]*p[3]);
R[2][0] = 2*(p[0]*p[2] - p[1]*p[3]);
+ q[0] = p[3]; // Note: The "p" variable is not a quaternion in the
+ q[1] = p[0]; // conventional sense because its elements
+ q[2] = p[1]; // are in the wrong order. I correct for that here.
+ q[3] = p[2]; // "q" is the quaternion correspond to rotation R.
+
Scalar pPp = eval_max;
// Optional: Decide the scale factor, c