Я пишу заголовочный файл на C++, реализующий взаимодействие с кватернионом. Кватернион можно интерпретировать как a + bi + cj + dk
, поэтому его компоненты хранятся в m_value
в порядке b, c, d, a
. Я также поддерживаю представление кватерниона в виде скаляра и вектора. Я заметил, что в методе updateComponents()
я использую в 2 раза больше памяти, чем мне действительно нужно. Как я могу это исправить? m_value
и требуется скалярное и векторное представление.
#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
Я читал о union
, но думаю, что он не будет так эффективно использовать память, как я ожидаю.
Как используется показанный вами код? (например, каковы потребности программы, использующей ваш класс) Как вы определили, какая «память вам нужна»? И как вы определили объем памяти, который на самом деле использует ваша программа? Расчет использования памяти — это гораздо больше, чем просто сложение памяти, выделяемой вашей программой, или используемых вами структур данных. Память также необходима для представления самой программы (инструкций и т. д.) — объем накладных расходов зависит от вашей хост-системы, операционной системы, архитектуры набора команд и т. д. и т. п.
Я использую в 2 раза больше памяти, чем мне действительно нужно. -- Как вы это определили? Просматривали ли вы сгенерированный ассемблерный код оптимизированной версии вашего кода C++?
Зачем тебе вообще T m_value[4];
?
Quat(T angle, bool mode, vector3_t<T> axes)
-- Почему вы не обратили внимание на то, что это неэффективно или потребляет память? axes
передается по значению, что означает, что создается полная временная копия axes
. То же самое здесь: Quat<T> operator-(Quat q2)
и в других вызовах функций. Вы должны передавать константную ссылку, а не значение.
@AhmedAEK с плавающей запятой и двойной фиксацией
Вы сохраняете данные дважды: один раз как 4 элемента, а другой раз как скаляр+вектор. Выберите одно представление, чтобы сократить размер объекта.
кажется дубликатом stackoverflow.com/a/702679/15649230
@PaulMcKenzie спасибо за это замечание, я это исправил
@NathanOliver это требуется по техническому заданию. В методе *data()
мне нужно вернуть указатель на 4 компонента кватерниона.
@Джон Тогда это единственный участник, который тебе нужен.
@NathanOliver Я уже думал об этом, но задача такая: «Обратите внимание, что кватернион можно представить как комбинацию скаляра и вектора из трех компонентов. Соответственно, кватернионные и скалярно-векторные операции следует рассматривать именно с этой точки зрения. ", так что я немного в замешательстве
Ваш массив из 4 членов по-прежнему квалифицируется как таковой. То, как это реализовано внутри, не имеет особого значения, если принять жесткие требования, такие как наличие функции data
. Поскольку оно у вас есть, вам понадобится хранилище массива. Однако вы можете сделать ссылки на эти элементы массива и назвать их именами скаляров и векторов в ваших функциях, чтобы их было более естественно использовать, поскольку вы «используете» скаляр и вектор вместо необработанного массива при написании всех ваших функции.
Есть одна реализация в стадии разработки. Он также предоставляет класс окторниона.
@Джон — float и double — к вашему сведению, ваша программа компилируется, если T
есть std::string
, но умирает при запуске. Посмотрите это. Чтобы избежать ошибки, вам необходимо реализовать проверку числовых типов во время компиляции.
Если вам нужно *data()
вернуть то, что есть, у вас есть 2 варианта. Используйте m_value
для всего, это будет выглядеть не так красиво, если использовать [0]
вместо .x
и т. д., но он будет делать то, что вы хотите, а в остальном код будет работать идентично.
Или вы можете разобраться в тонкостях ручного управления памятью, new
назначив и назначив массив, который будет m_value
при каждом вызове *data()
. Это могло бы очень быстро использовать гораздо больше памяти, чем то, что у вас есть сейчас, если бы вы не были осторожны с этим, но у него есть преимущество, заключающееся в том, что он не позволяет другому коду возиться с вашим частным членом, как это возможно сейчас.
какие допустимые значения для
T
?!