Chira Engine
A customizable MIT-licensed game engine.
TransformComponent.h
1 #pragma once
2 
3 #include <concepts>
4 #include <type_traits>
5 
6 #include <glm/glm.hpp>
7 #include <glm/gtx/matrix_decompose.hpp>
8 #include <glm/gtx/quaternion.hpp>
9 
10 namespace chira {
11 
13  static constexpr auto in_place_delete = true;
14 
15  explicit TransformComponent(TransformComponent* parent_ = nullptr,
16  glm::vec3 position_ = {},
17  glm::quat rotation_ = glm::identity<glm::quat>(),
18  glm::vec3 scale_ = glm::vec3{1})
19  : transform(glm::identity<glm::mat4>())
20  , rotationQuat(rotation_)
21  , rotationEuler(glm::eulerAngles(rotation_))
22  , position(position_)
23  , scale(scale_)
24  , parent(parent_)
25  , useEulerAngles(false)
26  , dirty(true) {}
27 
28  [[nodiscard]] glm::mat4 getMatrix() { // NOLINT(misc-no-recursion)
29  if (this->parent) {
30  return this->parent->getMatrix() * this->getMatrixLocal();
31  }
32  return this->getMatrixLocal();
33  }
34  [[nodiscard]] const glm::mat4& getMatrixLocal() {
35  if (dirty) {
36  this->transform = TransformComponent::createTransformMatrix(
37  glm::identity<glm::mat4>(),
38  this->getPositionLocal(),
39  this->getRotation(),
40  this->getScale()
41  );
42  this->dirty = false;
43  }
44  return this->transform;
45  }
46  void setMatrixLocal(const glm::mat4& transform_) {
47  glm::vec3 skew;
48  glm::vec4 perspective;
49  glm::decompose(transform_, this->scale, this->rotationQuat, this->position, skew, perspective);
50  // Fix euler angles
51  this->setRotation(this->rotationQuat);
52  }
53 
54  [[nodiscard]] glm::vec3 getPosition() const { // NOLINT(misc-no-recursion)
55  if (this->parent) {
56  return this->parent->getPosition() + this->getPositionLocal();
57  }
58  return this->position;
59  }
60  void setPosition(glm::vec3 newGlobalPosition) { // NOLINT(misc-no-recursion)
61  if (this->parent) {
62  this->position = newGlobalPosition - this->parent->getPosition();
63  } else {
64  this->position = newGlobalPosition;
65  }
66  this->dirty = true;
67  }
68 
69  [[nodiscard]] glm::vec3 getPositionLocal() const {
70  return this->position;
71  }
72  void setPositionLocal(glm::vec3 newLocalPosition) {
73  this->position = newLocalPosition;
74  this->dirty = true;
75  }
76 
77  [[nodiscard]] glm::quat getRotation() const {
78  return this->useEulerAngles ? glm::quat(this->rotationEuler) : this->rotationQuat;
79  }
80  [[nodiscard]] glm::vec3 getRotationEuler() const {
81  return this->useEulerAngles ? this->rotationEuler : glm::eulerAngles(this->rotationQuat);
82  }
84  [[nodiscard]] float getPitch() const {
85  return this->getRotationEuler().x;
86  }
88  [[nodiscard]] float getYaw() const {
89  return this->getRotationEuler().y;
90  }
92  [[nodiscard]] float getRoll() const {
93  return this->getRotationEuler().z;
94  }
95  void setRotation(glm::quat newRotation) {
96  this->rotationQuat = newRotation;
97  this->rotationEuler = glm::eulerAngles(newRotation);
98  this->useEulerAngles = false;
99  this->dirty = true;
100  }
101  void setRotation(glm::vec3 newRotation) {
102  this->rotationQuat = glm::quat(newRotation);
103  this->rotationEuler = newRotation;
104  this->useEulerAngles = true;
105  this->dirty = true;
106  }
108  void setPitch(float pitch) {
109  this->setRotation({pitch, this->getYaw(), this->getRoll()});
110  this->useEulerAngles = true;
111  this->dirty = true;
112  }
114  void setYaw(float yaw) {
115  this->setRotation({this->getPitch(), yaw, this->getRoll()});
116  this->useEulerAngles = true;
117  this->dirty = true;
118  }
120  void setRoll(float roll) {
121  this->setRotation({this->getPitch(), this->getYaw(), roll});
122  this->useEulerAngles = true;
123  this->dirty = true;
124  }
125 
126  [[nodiscard]] glm::vec3 getScale() const {
127  return this->scale;
128  }
129  void setScale(glm::vec3 newScale) {
130  this->scale = newScale;
131  this->dirty = true;
132  }
133 
134  void translate(glm::vec3 translateByAmount) {
135  this->position += translateByAmount;
136  this->dirty = true;
137  }
138  void translateWithRotation(glm::vec3 translateByAmount) {
139  glm::quat p{glm::length(translateByAmount), translateByAmount.x, translateByAmount.y, translateByAmount.z};
140  p = this->getRotation() * p * glm::conjugate(this->getRotation());
141  this->translate(glm::vec3{p.x, p.y, p.z});
142  }
143 
144  void rotate(glm::quat rotateByAmount) {
145  this->rotationQuat += rotateByAmount;
146  this->rotationEuler = glm::eulerAngles(this->rotationQuat);
147  this->useEulerAngles = false;
148  this->dirty = true;
149  }
150  void rotate(glm::vec3 rotateByAmount) {
151  this->pitch(rotateByAmount.x);
152  this->yaw(rotateByAmount.y);
153  this->roll(rotateByAmount.z);
154  this->rotationQuat = glm::quat(this->rotationEuler);
155  this->useEulerAngles = true;
156  this->dirty = true;
157  }
158  void pitch(float pitch) {
159  this->rotationEuler = glm::rotate(glm::angleAxis(pitch, glm::vec3{1,0,0}), this->rotationEuler);
160  this->rotationQuat = glm::quat(this->rotationEuler);
161  this->useEulerAngles = true;
162  this->dirty = true;
163  }
164  void yaw(float yaw) {
165  this->rotationEuler = glm::rotate(glm::angleAxis(yaw, glm::vec3{0,1,0}), this->rotationEuler);
166  this->rotationQuat = glm::quat(this->rotationEuler);
167  this->useEulerAngles = true;
168  this->dirty = true;
169  }
170  void roll(float roll) {
171  this->rotationEuler = glm::rotate(glm::angleAxis(roll, glm::vec3{0,0,1}), this->rotationEuler);
172  this->rotationQuat = glm::quat(this->rotationEuler);
173  this->useEulerAngles = true;
174  this->dirty = true;
175  }
176 
177  [[nodiscard]] glm::vec3 getFrontVector() const {
178  glm::vec3 out;
179  out.x = 2 * (this->rotationQuat.x * this->rotationQuat.z + this->rotationQuat.w * this->rotationQuat.y);
180  out.y = 2 * (this->rotationQuat.y * this->rotationQuat.z - this->rotationQuat.w * this->rotationQuat.x);
181  out.z = 1 - 2 * (this->rotationQuat.x * this->rotationQuat.x + this->rotationQuat.y * this->rotationQuat.y);
182  return -out;
183  }
184  [[nodiscard]] glm::vec3 getUpVector() const {
185  glm::vec3 out;
186  out.x = 2 * (this->rotationQuat.x * this->rotationQuat.y - this->rotationQuat.w * this->rotationQuat.z);
187  out.y = 1 - 2 * (this->rotationQuat.x * this->rotationQuat.x + this->rotationQuat.z * this->rotationQuat.z);
188  out.z = 2 * (this->rotationQuat.y * this->rotationQuat.z + this->rotationQuat.w * this->rotationQuat.x);
189  return out;
190  }
191  [[nodiscard]] glm::vec3 getRightVector() const {
192  glm::vec3 out;
193  out.x = 1 - 2 * (this->rotationQuat.y * this->rotationQuat.y + this->rotationQuat.z * this->rotationQuat.z);
194  out.y = 2 * (this->rotationQuat.x * this->rotationQuat.y + this->rotationQuat.w * this->rotationQuat.z);
195  out.z = 2 * (this->rotationQuat.x * this->rotationQuat.z - this->rotationQuat.w * this->rotationQuat.y);
196  return out;
197  }
198 
199  [[nodiscard]] bool operator==(const TransformComponent& other) const {
200  return this->parent == other.parent &&
201  this->position == other.position &&
202  this->rotationQuat == other.rotationQuat &&
203  this->scale == other.scale;
204  }
205 
206  [[nodiscard]] static glm::mat4 createTransformMatrix(const glm::mat4& start, glm::vec3 position, glm::quat rotation, glm::vec3 scale) {
207  return glm::scale(glm::translate(start, position) * glm::mat4_cast(rotation), scale);
208  }
209 
210 protected:
211  glm::mat4 transform;
212  glm::quat rotationQuat;
213  glm::vec3 rotationEuler;
214  glm::vec3 position;
215  glm::vec3 scale;
216 
217  TransformComponent* parent;
218  bool useEulerAngles;
219  bool dirty;
220 };
221 
222 template<typename T>
223 concept CComponentHasTransform = requires(T t) {
224  // no i don't know why it needs the & at the end but it does
225  {t.transform} -> std::same_as<TransformComponent*&>;
226 };
227 
228 } // namespace chira
float getYaw() const
Returns an angle in radians.
void setYaw(float yaw)
Takes an angle in radians.
void setRoll(float roll)
Takes an angle in radians.
void setPitch(float pitch)
Takes an angle in radians.
float getPitch() const
Returns an angle in radians.
float getRoll() const
Returns an angle in radians.