I am writing a header file on C++ that implements interaction with quaternion. Quaternion can be interpreted as a + bi + cj + dk
, so its components are stored in m_value
in b, c, d, a
order. I also support representing a quaternion as a scalar & vector. I've noticed that in method updateComponents()
I use 2x more memory than I actually need. How can I fix that? m_value
and scalar & vector representation are required.
#ifndef QUAT_HPP
#define QUAT_HPP
#include <cmath>
template<typename T>
struct matrix_t {
T data[16];
};
template<typename T>
struct vector3_t {
T x, y, z;
};
template<typename T>
class Quat {
public:
Quat() : scalar(0), vector{0, 0, 0} {
updateComponents();
}
Quat(T a, T b, T c, T d) : scalar(a), vector{b, c, d} {
updateComponents();
}
Quat(T angle, bool mode, vector3_t<T> axes) {
if (!mode) {
angle = angle * M_PI / 180;
}
T norm = std::sqrt(axes.x * axes.x + axes.y * axes.y + axes.z * axes.z);
scalar = std::cos(angle / 2);
vector.x = axes.x / norm * std::sin(angle / 2);
vector.y = axes.y / norm * std::sin(angle / 2);
vector.z = axes.z / norm * std::sin(angle / 2);
updateComponents();
}
const T *data() const { return m_value; };
Quat<T> operator+(const Quat<T> &q2) const {
return Quat(scalar + q2.scalar, vector.x + q2.vector.x, vector.y + q2.vector.y, vector.z + q2.vector.z);
}
Quat<T> &operator+=(const Quat<T> &q2) {
*this = *this + q2;
updateComponents();
return *this;
}
Quat<T> operator-(Quat q2) const {
return Quat(scalar - q2.scalar, vector.x - q2.vector.x, vector.y - q2.vector.y, vector.z - q2.vector.z);
}
Quat<T> &operator-=(Quat q2) {
*this = *this - q2;
updateComponents();
return *this;
}
Quat<T> operator*(Quat q2) const {
T a1 = scalar, b1 = vector.x, c1 = vector.y, d1 = vector.z;
T a2 = q2.scalar, b2 = q2.vector.x, c2 = q2.vector.y, d2 = q2.vector.z;
return Quat(
a1 * a2 - b1 * b2 - c1 * c2 - d1 * d2,
a1 * b2 + a2 * b1 + c1 * d2 - c2 * d1,
a1 * c2 + a2 * c1 + d1 * b2 - d2 * b1,
a1 * d2 + a2 * d1 + b1 * c2 - b2 * c1);
}
Quat<T> operator*(T s) const { return Quat(scalar * s, vector.x * s, vector.y * s, vector.z * s); }
Quat<T> operator*(vector3_t<T> vec) const {
Quat<T> q2 = Quat(0, vec.x, vec.y, vec.z);
return *this * q2;
}
Quat<T> operator~() const { return Quat(scalar, -vector.x, -vector.y, -vector.z); }
bool operator==(Quat q2) const {
return (scalar == q2.scalar && vector.x == q2.vector.x && vector.y == q2.vector.y && vector.z == q2.vector.z);
}
bool operator!=(Quat q2) const { return !(*this == q2); }
explicit operator T() const {
return std::sqrt(scalar * scalar + vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
}
matrix_t<T> rotation_matrix() const {
Quat<T> q = normalize();
T s = q.scalar, x = q.vector.x, y = q.vector.y, z = q.vector.z;
T r_11 = 1 - 2 * y * y - 2 * z * z, r_12 = 2 * x * y - 2 * z * s, r_13 = 2 * x * z + 2 * y * s,
r_21 = 2 * x * y + 2 * z * s, r_22 = 1 - 2 * x * x - 2 * z * z, r_23 = 2 * y * z - 2 * x * s,
r_31 = 2 * x * z - 2 * y * s, r_32 = 2 * y * z + 2 * x * s, r_33 = 1 - 2 * x * x - 2 * y * y;
return {r_11, r_21, r_31, 0, r_12, r_22, r_32, 0, r_13, r_23, r_33, 0, 0, 0, 0, 1};
}
matrix_t<T> matrix() const {
return {scalar, -vector.x, -vector.y, -vector.z, vector.x, scalar, -vector.z, vector.y,
vector.y, vector.z, scalar, -vector.x, vector.z, -vector.y, vector.x, scalar};
}
T angle(bool mode = true) const { return mode ? 2 * std::acos(scalar) : 2 * std::acos(scalar) * 180 / M_PI; }
vector3_t<T> apply(vector3_t<T> vec) const {
Quat<T> q = normalize();
Quat<T> qv = q * vec;
Quat<T> qq = qv * (~q);
return {qq.vector.x, qq.vector.y, qq.vector.z};
}
private:
T m_value[4];
T scalar;
vector3_t<T> vector;
void updateComponents() {
m_value[3] = scalar;
m_value[0] = vector.x;
m_value[1] = vector.y;
m_value[2] = vector.z;
}
Quat<T> normalize() const {
T norm = T(*this);
return *this * (1 / norm);
}
};
#endif
I've read about union
, but I think it won't be as memory efficient as I expect.
If you need *data()
to return what it is you have 2 options. Use m_value
for everything, it wouldn't look quite as nice using [0]
instead of .x
ect but it would do what you want it to do and otherwise the code would function identically.
Or, you could get into the weeds of manual memory management by new
ing and assigning an array that would have been m_value
with every *data()
call. This would have the potential to very quickly use alot more memory than what you have now if you weren't careful with it but it does have the advantage of not letting other code mess with your private member as it can now