Unravel Engine C++ Reference
Loading...
Searching...
No Matches
transform.hpp
Go to the documentation of this file.
1#pragma once
2//-----------------------------------------------------------------------------
3// transform Header Includes
4//-----------------------------------------------------------------------------
6#include <string>
7
8namespace math
9{
10using namespace glm;
11
12#ifdef _MSC_VER
13#define TRANSFORM_INLINE //__forceinline
14#elif defined(__GNUC__) || defined(__clang__)
15#define TRANSFORM_INLINE //inline
16#else
17#define TRANSFORM_INLINE //inline
18#endif
19
25template<typename T, precision Q = defaultp>
27{
28public:
29 using mat3_t = mat<3, 3, T, Q>;
30 using mat4_t = mat<4, 4, T, Q>;
31 using vec2_t = vec<2, T, Q>;
32 using vec3_t = vec<3, T, Q>;
33 using vec4_t = vec<4, T, Q>;
34 using quat_t = qua<T, Q>;
35 using length_t = typename mat4_t::length_type;
36 using col_t = typename mat4_t::col_type;
37
38 //-------------------------------------------------------------------------
39 // Constructors & Destructors
40 //-------------------------------------------------------------------------
44 transform_t() = default;
45 ~transform_t() = default;
46
50 transform_t(const transform_t& t) noexcept = default;
51
55 transform_t(transform_t&& t) noexcept = default;
56
60 auto operator=(const transform_t& m) noexcept -> transform_t& = default;
61
65 auto operator=(transform_t&& m) noexcept -> transform_t& = default;
66
70 transform_t(const mat4_t& m) noexcept;
71
72 //-------------------------------------------------------------------------
73 // Translation Methods
74 //-------------------------------------------------------------------------
75
80 auto get_position() const noexcept -> const vec3_t&;
81
86 auto get_translation() const noexcept -> const vec3_t&;
87
92 void set_translation(const vec3_t& position) noexcept;
93
100 void set_translation(T x, T y, T z = T(0)) noexcept;
101
105 void reset_translation() noexcept;
106
111 void set_position(const vec3_t& position) noexcept;
112
119 void set_position(T x, T y, T z = T(0)) noexcept;
120
124 void reset_position() noexcept;
125
126 //-------------------------------------------------------------------------
127 // Rotation Methods
128 //-------------------------------------------------------------------------
129
134 auto get_rotation_euler() const noexcept -> vec3_t;
135
140 auto get_rotation_euler_degrees() const noexcept -> vec3_t;
141
147 auto get_rotation_euler_degrees(vec3_t hint) const noexcept -> vec3_t;
148
153 void set_rotation_euler(const vec3_t& euler_angles) noexcept;
154
161 void set_rotation_euler(T x, T y, T z) noexcept;
162
167 void set_rotation_euler_degrees(const vec3_t& euler_angles) noexcept;
168
175 void set_rotation_euler_degrees(T x, T y, T z) noexcept;
176
181 auto get_rotation() const noexcept -> const quat_t&;
182
187 void set_rotation(const quat_t& rotation) noexcept;
188
195 void set_rotation(const vec3_t& x, const vec3_t& y, const vec3_t& z) noexcept;
196
200 void reset_rotation() noexcept;
201
202 //-------------------------------------------------------------------------
203 // Scale Methods
204 //-------------------------------------------------------------------------
205
210 auto get_scale() const noexcept -> const vec3_t&;
211
216 void set_scale(const vec3_t& scale) noexcept;
217
224 void set_scale(T x, T y, T z = T(1)) noexcept;
225
229 void reset_scale() noexcept;
230
231 //-------------------------------------------------------------------------
232 // Skew Methods
233 //-------------------------------------------------------------------------
234
239 auto get_skew() const noexcept -> const vec3_t&;
240
245 void set_skew(const vec3_t& skew) noexcept;
246
253 void set_skew(T x, T y, T z) noexcept;
254
258 void reset_skew() noexcept;
259
260 //-------------------------------------------------------------------------
261 // Perspective Methods
262 //-------------------------------------------------------------------------
263
268 auto get_perspective() const noexcept -> const vec4_t&;
269
274 void set_perspective(const vec4_t& perspective) noexcept;
275
283 void set_perspective(T x, T y, T z, T w) noexcept;
284
288 void reset_perspective() noexcept;
289
290 //-------------------------------------------------------------------------
291 // Axis Methods
292 //-------------------------------------------------------------------------
293
298 auto x_axis() const noexcept -> vec3_t;
299
304 auto y_axis() const noexcept -> vec3_t;
305
310 auto z_axis() const noexcept -> vec3_t;
311
316 auto x_unit_axis() const noexcept -> vec3_t;
317
322 auto y_unit_axis() const noexcept -> vec3_t;
323
328 auto z_unit_axis() const noexcept -> vec3_t;
329
330 //-------------------------------------------------------------------------
331 // Transformation Methods
332 //-------------------------------------------------------------------------
333
339 void rotate_axis(T a, const vec3_t& v) noexcept;
340
345 void rotate(const quat_t& q) noexcept;
346
353 void rotate(T x, T y, T z) noexcept;
354
359 void rotate(const vec3_t& v) noexcept;
360
367 void rotate_local(T x, T y, T z) noexcept;
368
373 void rotate_local(const vec3_t& v) noexcept;
374
381 void scale(T x, T y, T z = T(1)) noexcept;
382
387 void scale(const vec3_t& v) noexcept;
388
395 void translate(T x, T y, T z = T(0)) noexcept;
396
401 void translate(const vec3_t& v) noexcept;
402
409 void translate_local(T x, T y, T z = T(0)) noexcept;
410
415 void translate_local(const vec3_t& v) noexcept;
416
417 //-------------------------------------------------------------------------
418 // Comparison Methods
419 //-------------------------------------------------------------------------
420
426 auto compare(const transform_t& rhs) const noexcept -> int;
427
434 auto compare(const transform_t& rhs, T tolerance) const noexcept -> int;
435
436 //-------------------------------------------------------------------------
437 // Coordinate Transformation Methods
438 //-------------------------------------------------------------------------
439
445 auto transform_coord(const vec2_t& v) const noexcept -> vec2_t;
446
452 auto inverse_transform_coord(const vec2_t& v) const noexcept -> vec2_t;
453
459 auto transform_normal(const vec2_t& v) const noexcept -> vec2_t;
460
466 auto inverse_transform_normal(const vec2_t& v) const noexcept -> vec2_t;
467
473 auto transform_coord(const vec3_t& v) const noexcept -> vec3_t;
474
480 auto inverse_transform_coord(const vec3_t& v) const noexcept -> vec3_t;
481
487 auto transform_normal(const vec3_t& v) const noexcept -> vec3_t;
488
494 auto inverse_transform_normal(const vec3_t& v) const noexcept -> vec3_t;
495
496 //-------------------------------------------------------------------------
497 // Identity and Factory Methods
498 //-------------------------------------------------------------------------
499
504 static auto identity() noexcept -> const transform_t&;
505
511 static auto scaling(const vec2_t& scale) noexcept -> transform_t;
512
518 static auto scaling(const vec3_t& scale) noexcept -> transform_t;
519
525 static auto rotation(const quat_t& rotation) noexcept -> transform_t;
526
532 static auto rotation_euler(const vec3_t& euler_angles) noexcept -> transform_t;
533
539 static auto translation(const vec2_t& trans) noexcept -> transform_t;
540
546 static auto translation(const vec3_t& trans) noexcept -> transform_t;
547
548 //-------------------------------------------------------------------------
549 // Public Operator Overloads
550 //-------------------------------------------------------------------------
551
559 operator const mat4_t&() const noexcept;
560
568 operator const mat4_t*() const noexcept;
569
578 operator const T*() const noexcept;
579
585 auto operator*(const transform_t& t) const noexcept -> transform_t;
586
592 auto operator==(const transform_t& t) const noexcept -> bool;
593
599 auto operator!=(const transform_t& t) const noexcept -> bool;
600
606 auto operator[](length_t i) const noexcept -> const col_t&;
607
613 auto operator*(const vec4_t& v) const noexcept -> vec4_t;
614
619 auto get_matrix() const noexcept -> const mat4_t&;
620
621private:
629 void update_components() const noexcept;
630
638 void update_matrix() const noexcept;
639
647 void make_matrix_dirty() noexcept;
648 void make_components_dirty() noexcept;
649
650 // Helper functions to check if skew and perspective are zero/identity
651 auto is_skew_zero() const noexcept -> bool;
652 auto is_perspective_identity() const noexcept -> bool;
653 auto is_scale_uniform() const noexcept -> bool;
654
655 auto can_use_simplified_calculations() const noexcept -> bool;
656 auto can_use_simplified_calculations_without_uniform_scale() const noexcept -> bool;
664 mutable mat4_t matrix_ = glm::identity<mat4_t>();
665
672 mutable vec3_t position_ = glm::zero<vec3_t>();
673
680 mutable quat_t rotation_ = glm::identity<quat_t>();
681
688 mutable vec3_t scale_ = glm::one<vec3_t>();
689
696 mutable vec3_t skew_ = glm::zero<vec3_t>();
697
704 mutable vec4_t perspective_ = vec4_t(0, 0, 0, 1);
705
712 mutable bool matrix_needs_recompute_ = false;
713
721 mutable bool components_need_recompute_ = false;
722
723 mutable bool is_skew_zero_cached_ = true;
724 mutable bool is_perspective_identity_cached_ = true;
725 mutable bool is_scale_uniform_cached_ = true;
726};
727
728template<typename T, precision Q>
729auto inverse(transform_t<T, Q> const& t) noexcept -> transform_t<T, Q>
730{
731 const auto& m = t.get_matrix();
732 return glm::inverse(m);
733}
734
735template<typename T, precision Q>
737{
738 const auto& m = t.get_matrix();
739 return glm::transpose(m);
740}
741
742template<typename T, precision Q>
744 : matrix_(m)
745 , is_skew_zero_cached_(false)
746 , is_perspective_identity_cached_(false)
747 , is_scale_uniform_cached_(false)
748{
749 make_components_dirty();
750}
751
752template<typename T, precision Q>
753TRANSFORM_INLINE auto transform_t<T, Q>::get_position() const noexcept -> const typename transform_t<T, Q>::vec3_t&
754{
755 update_components();
756 return position_;
757}
758
759template<typename T, precision Q>
760TRANSFORM_INLINE auto transform_t<T, Q>::get_translation() const noexcept -> const typename transform_t<T, Q>::vec3_t&
761{
762 return get_position();
763}
764
765template<typename T, precision Q>
767{
768 set_position(position);
769}
770
771template<typename T, precision Q>
773{
774 set_position(x, y, z);
775}
776
777template<typename T, precision Q>
782
783template<typename T, precision Q>
785{
786 update_components();
787 position_ = position;
788 make_matrix_dirty();
789}
790
791template<typename T, precision Q>
793{
794 set_position({x, y, z});
795}
796
797template<typename T, precision Q>
802
803template<typename T, precision Q>
805{
806 return eulerAngles(get_rotation());
807}
808
809template<typename T, precision Q>
811 typename transform_t<T, Q>::vec3_t
812{
813 auto angles = degrees(get_rotation_euler());
814 return angles;
815}
816
817template<typename T, precision Q>
820{
821 auto angles = get_rotation_euler_degrees();
822
823 static auto repeat_working = [](float t, float length)
824 {
825 return (t - (floor(t / length) * length));
826 };
827
828 angles.x = repeat_working(angles.x - hint.x + T(180), T(360)) + hint.x - T(180);
829 angles.y = repeat_working(angles.y - hint.y + T(180), T(360)) + hint.y - T(180);
830 angles.z = repeat_working(angles.z - hint.z + T(180), T(360)) + hint.z - T(180);
831
832 return angles;
833}
834
835template<typename T, precision Q>
837{
838 set_rotation(quat_t(euler_angles));
839}
840
841template<typename T, precision Q>
846
847template<typename T, precision Q>
849{
850 set_rotation_euler(radians(euler_angles));
851}
852
853template<typename T, precision Q>
858
859template<typename T, precision Q>
860TRANSFORM_INLINE auto transform_t<T, Q>::get_scale() const noexcept -> const typename transform_t<T, Q>::vec3_t&
861{
862 update_components();
863 return scale_;
864}
865
866template<typename T, precision Q>
868{
869 update_components();
870 scale_ = scale;
871 is_scale_uniform_cached_ = is_scale_uniform();
872
873 make_matrix_dirty();
874}
875
876template<typename T, precision Q>
878{
879 set_scale({x, y, z});
880}
881
882template<typename T, precision Q>
884{
885 set_scale(T(1), T(1), T(1));
886}
887
888template<typename T, precision Q>
890{
891 update_components();
892 skew_ = skew;
893
894 is_skew_zero_cached_ = is_skew_zero();
895 make_matrix_dirty();
896}
897
898template<typename T, precision Q>
900{
901 set_skew({x, y, z});
902}
903
904template<typename T, precision Q>
906{
907 set_skew(0, 0, 0);
908}
909
910template<typename T, precision Q>
912{
913 update_components();
914 perspective_ = perspective;
915 is_perspective_identity_cached_ = is_perspective_identity();
916
917 make_matrix_dirty();
918}
919
920template<typename T, precision Q>
922{
923 set_perspective({x, y, z, w});
924}
925
926template<typename T, precision Q>
931
932template<typename T, precision Q>
933TRANSFORM_INLINE auto transform_t<T, Q>::get_rotation() const noexcept -> const typename transform_t<T, Q>::quat_t&
934{
935 update_components();
936 return rotation_;
937}
938
939template<typename T, precision Q>
940TRANSFORM_INLINE auto transform_t<T, Q>::get_skew() const noexcept -> const typename transform_t<T, Q>::vec3_t&
941{
942 update_components();
943 return skew_;
944}
945
946template<typename T, precision Q>
947TRANSFORM_INLINE auto transform_t<T, Q>::get_perspective() const noexcept -> const typename transform_t<T, Q>::vec4_t&
948{
949 update_components();
950 return perspective_;
951}
952
953template<typename T, precision Q>
955{
956 update_components();
957 rotation_ = glm::normalize(rotation);
958 make_matrix_dirty();
959}
960
961template<typename T, precision Q>
963{
964 quat_t quat = quat_cast(mat3_t(x, y, z));
965 set_rotation(quat);
966}
967
968template<typename T, precision Q>
970{
971 set_rotation(glm::identity<quat_t>());
972}
973
974template<typename T, precision Q>
975TRANSFORM_INLINE auto transform_t<T, Q>::x_axis() const noexcept -> typename transform_t<T, Q>::vec3_t
976{
977 if(can_use_simplified_calculations_without_uniform_scale())
978 {
979 // X axis is the first column of the rotation matrix scaled
980 return get_rotation() * vec3_t(get_scale().x, 0, 0);
981 }
982
983 return get_matrix()[0];
984}
985
986template<typename T, precision Q>
987TRANSFORM_INLINE auto transform_t<T, Q>::y_axis() const noexcept -> typename transform_t<T, Q>::vec3_t
988{
989 if(can_use_simplified_calculations_without_uniform_scale())
990 {
991 // Y axis is the second column of the rotation matrix scaled
992 return get_rotation() * vec3_t(0, get_scale().y, 0);
993 }
994
995 return get_matrix()[1];
996}
997
998template<typename T, precision Q>
999TRANSFORM_INLINE auto transform_t<T, Q>::z_axis() const noexcept -> typename transform_t<T, Q>::vec3_t
1000{
1001 if(can_use_simplified_calculations_without_uniform_scale())
1002 {
1003 // Z axis is the third column of the rotation matrix scaled
1004 return get_rotation() * vec3_t(0, 0, get_scale().z);
1005 }
1006
1007 return get_matrix()[2];
1008}
1009
1010template<typename T, precision Q>
1012{
1013 return normalize(x_axis());
1014}
1015
1016template<typename T, precision Q>
1018{
1019 return normalize(y_axis());
1020}
1021
1022template<typename T, precision Q>
1024{
1025 return normalize(z_axis());
1026}
1027
1028template<typename T, precision Q>
1030{
1031 quat_t result = q * get_rotation();
1032 set_rotation(result);
1033}
1034
1035template<typename T, precision Q>
1037{
1038 quat_t q = glm::angleAxis(a, v) * get_rotation();
1039 set_rotation(q);
1040}
1041
1042template<typename T, precision Q>
1044{
1045 rotate({x, y, z});
1046}
1047
1048template<typename T, precision Q>
1050{
1051 // Convert Euler angles to quaternion
1052 quat_t delta_rotation(v);
1053 set_rotation(delta_rotation * get_rotation());
1054}
1055
1056template<typename T, precision Q>
1058{
1059 rotate_local({x, y, z});
1060}
1061
1062template<typename T, precision Q>
1064{
1065 // Convert Euler angles to quaternion
1066 quat_t delta_rotation(v);
1067 set_rotation(get_rotation() * delta_rotation);
1068}
1069
1070template<typename T, precision Q>
1072{
1073 scale({x, y, z});
1074}
1075
1076template<typename T, precision Q>
1078{
1079 set_scale(get_scale() * v);
1080}
1081
1082template<typename T, precision Q>
1084{
1085 translate({x, y, z});
1086}
1087
1088template<typename T, precision Q>
1090{
1092}
1093
1094template<typename T, precision Q>
1096{
1097 translate_local({x, y, z});
1098}
1099
1100template<typename T, precision Q>
1102{
1103 // Transform local translation vector by current rotation
1104 vec3_t world_translation = get_rotation() * v;
1105 translate(world_translation);
1106}
1107
1108template<typename T, precision Q>
1109TRANSFORM_INLINE auto transform_t<T, Q>::compare(const transform_t& rhs) const noexcept -> int
1110{
1111 return compare(rhs, epsilon<T>());
1112}
1113
1114template<typename T, precision Q>
1115TRANSFORM_INLINE auto transform_t<T, Q>::compare(const transform_t& rhs, T tolerance) const noexcept -> int
1116{
1117 // bool comps_valid = !components_need_recompute_;
1118 // bool rhs_comps_valid = !rhs.components_need_recompute_;
1119
1120 // if(comps_valid && rhs_comps_valid)
1121 // {
1122 // if(!math::all(math::epsilonEqual(get_position(), rhs.get_position(), tolerance)))
1123 // {
1124 // return 1;
1125 // }
1126 // if(!math::all(math::epsilonEqual(get_scale(), rhs.get_scale(), tolerance)))
1127 // {
1128 // return 1;
1129 // }
1130
1131 // // Compare rotation quaternions
1132 // T dot = math::dot(get_rotation(), rhs.get_rotation());
1133 // T angle_diff = math::abs(dot);
1134 // if(angle_diff < (1 - tolerance))
1135 // {
1136 // return 1;
1137 // }
1138
1139 // if(!math::all(math::epsilonEqual(get_skew(), rhs.get_skew(), tolerance)))
1140 // {
1141 // return 1;
1142 // }
1143 // if(!math::all(math::epsilonEqual(get_perspective(), rhs.get_perspective(), tolerance)))
1144 // {
1145 // return 1;
1146 // }
1147 // return 0;
1148 // }
1149
1150 // otherwise do a matrix compare
1151
1152 const auto& m1 = get_matrix();
1153 const auto& m2 = rhs.get_matrix();
1154
1155 // Compare matrices
1156 for(int i = 0; i < 4; ++i)
1157 {
1158 vec4_t diff = m1[i] - m2[i];
1159
1160 if(i == 3)
1161 {
1162 diff.w = 0.0f;
1163
1164 if(!glm::epsilonEqual(m1[i].w, m2[i].w, T(0.001)))
1165 {
1166 return 1;
1167 }
1168 }
1169
1170 if(!glm::all(glm::epsilonEqual(diff, glm::zero<vec4_t>(), tolerance)))
1171 {
1172 return 1;
1173 }
1174 }
1175
1176 return 0;
1177}
1178
1179template<typename T, precision Q>
1182{
1183 return transform_coord(vec3_t(v, T(0)));
1184}
1185
1186template<typename T, precision Q>
1189{
1190 return inverse_transform_coord(vec3_t(v, T(0)));
1191}
1192
1193template<typename T, precision Q>
1196{
1197 return transform_normal(vec3_t(v, T(0)));
1198}
1199
1200template<typename T, precision Q>
1203{
1204 return inverse_transform_normal(vec3_t(v, T(0)));
1205}
1206
1207template<typename T, precision Q>
1210{
1211 if(can_use_simplified_calculations_without_uniform_scale())
1212 {
1213 // Direct transformation using components
1214 return get_position() + (get_rotation() * (get_scale() * v));
1215 }
1216
1217 // Use matrix multiplication
1218 vec4_t result = get_matrix() * vec4_t(v, T(1));
1219 return vec3_t(result) / result.w;
1220}
1221
1222template<typename T, precision Q>
1225{
1226 if(can_use_simplified_calculations_without_uniform_scale())
1227 {
1229 // Compute inverse scale
1230 vec3_t inv_scale = T(1) / scale;
1231
1232 // Compute inverse rotation
1233 // Using conjugate
1234 quat_t inv_rotation = glm::conjugate(get_rotation()); // Efficient and correct for unit quaternions
1235
1236 // Using inverse
1237 // quat_t inv_rotation = glm::inverse(get_rotation()); // Also correct but less efficient for unit quaternions
1238
1239 // Apply inverse transformations
1240 vec3_t result = inv_rotation * (v - get_position());
1241 result *= inv_scale;
1242
1243 return result;
1244 }
1245
1246 // Use matrix multiplication
1247 vec4_t result = glm::inverse(get_matrix()) * vec4_t(v, T(1));
1248 return vec3_t(result) / result.w;
1249}
1250
1251template<typename T, precision Q>
1254{
1255 // here skew can affect the direction of the normal
1256 if(can_use_simplified_calculations())
1257 {
1258 // Direct transformation using components (normals are not affected by translation)
1259 return get_rotation() * v;
1260 }
1261
1262 // Use matrix multiplication
1263 // Extract the linear (rotation, scaling, skew) part of the transformation matrix
1264 mat3_t linear_matrix = mat3_t(get_matrix());
1265
1266 // Compute the inverse transpose of the linear matrix
1267 mat3_t normal_matrix = glm::transpose(glm::inverse(linear_matrix));
1268
1269 // Transform the normal vector using the normal matrix
1270 return normal_matrix * v;
1271}
1272
1273template<typename T, precision Q>
1276{
1277 if(can_use_simplified_calculations())
1278 {
1279 // Uniform scaling and no skew/perspective; inverse rotate the normal
1280 return glm::conjugate(get_rotation()) * v;
1281 }
1282
1283 // Use transpose of the linear transformation matrix
1284 mat3_t linear_matrix = mat3_t(get_matrix());
1285 mat3_t normal_matrix = glm::transpose(linear_matrix);
1286
1287 // Transform the normal vector using the normal matrix
1288 return normal_matrix * v;
1289}
1290
1291template<typename T, precision Q>
1293{
1294 static transform_t identity;
1295 return identity;
1296}
1297
1298template<typename T, precision Q>
1300{
1301 return scaling(vec3_t(scale, T(1)));
1302}
1303
1304template<typename T, precision Q>
1306{
1307 transform_t result{};
1308 result.set_scale(scale);
1309 return result;
1310}
1311
1312template<typename T, precision Q>
1314{
1315 transform_t result{};
1316 result.set_rotation(rotation);
1317 return result;
1318}
1319
1320template<typename T, precision Q>
1322{
1323 transform_t result{};
1324 result.set_rotation_euler(euler_angles);
1325 return result;
1326}
1327
1328template<typename T, precision Q>
1330{
1331 return translation(vec3_t(trans, T(0)));
1332}
1333
1334template<typename T, precision Q>
1336{
1337 transform_t result{};
1338 result.set_position(trans);
1339 return result;
1340}
1341
1342template<typename T, precision Q>
1344{
1345 // if(!matrix_needs_recompute_ && !t.matrix_needs_recompute_)
1346 // {
1347 // return get_matrix() * t.get_matrix();
1348 // }
1349
1350 // // Check if skew and perspective components are zero for both transforms
1351 // if(can_use_simplified_calculations() && t.can_use_simplified_calculations())
1352 // {
1353 // // Perform component-wise multiplication
1354 // transform_t result;
1355 // // Component-wise multiplication
1356 // result.scale_ = get_scale() * t.get_scale();
1357 // result.is_scale_uniform_cached_ = result.is_scale_uniform();
1358 // // Quaternion multiplication
1359 // result.rotation_ = get_rotation() * t.get_rotation();
1360 // // Position calculation
1361 // result.position_ = get_position() + get_rotation() * (get_scale() * t.get_position());
1362 // result.make_matrix_dirty();
1363
1364 // return result;
1365 // }
1366
1367 // Fallback to matrix multiplication and decomposition
1368 return get_matrix() * t.get_matrix();
1369}
1370
1371template<typename T, precision Q>
1373{
1374 return compare(t, math::epsilon<T>()) == 0;
1375}
1376
1377template<typename T, precision Q>
1379{
1380 return compare(t, math::epsilon<T>()) != 0;
1381}
1382
1383template<typename T, precision Q>
1385{
1386 update_matrix();
1387 return matrix_;
1388}
1389
1390template<typename T, precision Q>
1392{
1393 if(components_need_recompute_)
1394 {
1395 glm_decompose(get_matrix(), scale_, rotation_, position_, skew_, perspective_);
1396
1397 components_need_recompute_ = false;
1398
1399 is_perspective_identity_cached_ = is_perspective_identity();
1400 is_skew_zero_cached_ = is_skew_zero();
1401 is_scale_uniform_cached_ = is_scale_uniform();
1402 }
1403}
1404
1405template<typename T, precision Q>
1406TRANSFORM_INLINE void transform_t<T, Q>::update_matrix() const noexcept
1407{
1408 if(matrix_needs_recompute_)
1409 {
1410 if(can_use_simplified_calculations())
1411 {
1412 // Simplified recomposition without skew and perspective
1413 const auto identity_matrix = glm::identity<mat4_t>();
1414 matrix_ = glm::translate(identity_matrix, get_position()) * glm::mat4_cast(get_rotation()) *
1415 glm::scale(identity_matrix, get_scale());
1416
1417 // // Set the upper-left 3x3 part of the matrix using the transformed axes
1418 // matrix_[0] = vec4_t(x_axis(), T(0)); // First column
1419 // matrix_[1] = vec4_t(y_axis(), T(0)); // Second column
1420 // matrix_[2] = vec4_t(z_axis(), T(0)); // Third column
1421
1422 // // Set the translation component
1423 // matrix_[3] = vec4_t(get_position(), T(1)); // Fourth column
1424 }
1425 else
1426 {
1428 }
1429
1430 matrix_needs_recompute_ = false;
1431 }
1432}
1433
1434template<typename T, precision Q>
1435TRANSFORM_INLINE void transform_t<T, Q>::make_matrix_dirty() noexcept
1436{
1437 matrix_needs_recompute_ = true;
1438}
1439
1440template<typename T, precision Q>
1441TRANSFORM_INLINE void transform_t<T, Q>::make_components_dirty() noexcept
1442{
1443 components_need_recompute_ = true;
1444}
1445
1446template<typename T, precision Q>
1447TRANSFORM_INLINE auto transform_t<T, Q>::is_skew_zero() const noexcept -> bool
1448{
1449 return glm::all(glm::epsilonEqual(get_skew(), glm::zero<vec3_t>(), glm::epsilon<T>()));
1450}
1451
1452template<typename T, precision Q>
1453TRANSFORM_INLINE auto transform_t<T, Q>::is_perspective_identity() const noexcept -> bool
1454{
1455 return glm::all(glm::epsilonEqual(get_perspective(), vec4_t(0, 0, 0, 1), glm::epsilon<T>()));
1456}
1457
1458template<typename T, precision Q>
1459TRANSFORM_INLINE auto transform_t<T, Q>::is_scale_uniform() const noexcept -> bool
1460{
1461 const T epsilon = glm::epsilon<T>();
1462 const auto& scale = get_scale();
1463 return glm::abs(scale.x - scale.y) < epsilon && glm::abs(scale.y - scale.z) < epsilon;
1464}
1465
1466template<typename T, precision Q>
1467TRANSFORM_INLINE auto transform_t<T, Q>::can_use_simplified_calculations() const noexcept -> bool
1468{
1469 return can_use_simplified_calculations_without_uniform_scale() && is_scale_uniform_cached_;
1470}
1471
1472template<typename T, precision Q>
1473
1474TRANSFORM_INLINE auto transform_t<T, Q>::can_use_simplified_calculations_without_uniform_scale() const noexcept -> bool
1475{
1476 return !components_need_recompute_ && is_skew_zero_cached_ && is_perspective_identity_cached_;
1477}
1478
1479template<typename T, precision Q>
1481{
1482 return get_matrix()[i];
1483}
1484
1485template<typename T, precision Q>
1487{
1488 vec4_t result = get_matrix() * v;
1489 return result;
1490}
1491
1492template<typename T, precision Q>
1494{
1495 return value_ptr(get_matrix());
1496}
1497
1498template<typename T, precision Q>
1500{
1501 return &get_matrix();
1502}
1503
1504template<typename T, precision Q>
1505TRANSFORM_INLINE transform_t<T, Q>::operator const typename transform_t<T, Q>::mat4_t &() const noexcept
1506{
1507 return get_matrix();
1508}
1509
1511
1512extern template class transform_t<float, defaultp>;
1513
1514template<typename T>
1515TRANSFORM_INLINE auto to_string(const T& v) -> std::string
1516{
1517 return glm::to_string(v);
1518}
1519} // namespace math
1520
1521namespace glm
1522{
1523namespace detail
1524{
1525template<typename T, qualifier Q>
1526struct compute_to_string<math::transform_t<T, Q>>
1527{
1528 GLM_FUNC_QUALIFIER static auto call(math::transform_t<T, Q> const& x) -> std::string
1529 {
1530 char const* prefix_str = prefix<T>::value();
1531 return detail::format("%stransform((translation:%s, scale:%s, rotation:%s/rotation_euler:%s, skew:%s))",
1532 prefix_str,
1533 to_string(x.get_position()).c_str(),
1534 to_string(x.get_scale()).c_str(),
1535 to_string(x.get_rotation()).c_str(),
1536 to_string(x.get_rotation_euler()).c_str(),
1537 to_string(x.get_skew()).c_str());
1538 }
1539};
1540} // namespace detail
1541
1542// Unity-like LookRotation: +Z = forward, +Y = upwards (approx).
1543inline auto look_rotation(const glm::vec3& forward, const glm::vec3& upwards) -> glm::quat
1544{
1545 auto view = glm::lookAt(
1546 math::zero<math::vec3>(),
1547 forward,
1548 upwards
1549 );
1550
1551 glm::mat4 model = glm::inverse(view);
1552 return glm::quat_cast(model);
1553}
1554
1555inline auto from_to_rotation(const glm::vec3& from, const glm::vec3& to) -> glm::quat
1556{
1557 // 1. Normalize inputs
1558 glm::vec3 f = glm::normalize(from);
1559 glm::vec3 t = glm::normalize(to);
1560
1561 // 2. Dot product -> angle
1562 float dot_product = glm::dot(f, t);
1563 dot_product = glm::clamp(dot_product, -1.0f, 1.0f);
1564 float angle = glm::acos(dot_product);
1565
1566 // 3. If vectors are nearly the same, return identity
1567 if(glm::epsilonEqual(dot_product, 1.0f, 1e-5f))
1568 {
1569 // Angle is 0 -> no rotation needed
1570 return glm::identity<glm::quat>(); // Identity
1571 }
1572
1573 // 4. If vectors are nearly opposite
1574 if(glm::epsilonEqual(dot_product, -1.0f, 1e-5f))
1575 {
1576 // Angle is pi -> pick an orthonormal axis
1577 // The axis must be perpendicular to 'from'
1578 // Choose any vector orthonormal to 'f'
1579 glm::vec3 orth = glm::abs(f.x) > glm::abs(f.z) ? glm::vec3(-f.y, f.x, 0.0f) : glm::vec3(0.0f, -f.z, f.y);
1580 orth = glm::normalize(orth);
1581 return glm::angleAxis(glm::pi<float>(), orth);
1582 }
1583
1584 // 5. Otherwise, use cross product for the axis
1585 glm::vec3 axis = glm::cross(f, t);
1586
1587 // If numerical drift caused near-zero cross magnitude, safe-guard:
1588 float len_sq = glm::dot(axis, axis);
1589 if(len_sq < 1e-12f)
1590 {
1591 // 'from' and 'to' differ by a small angle, but cross is near zero
1592 // fallback: identity or very small rotation
1593 return glm::identity<glm::quat>(); // Identity
1594 }
1595
1596 axis = glm::normalize(axis);
1597
1598 // 6. Convert angle/axis to a quaternion
1599 return glm::angleAxis(angle, axis);
1600}
1601
1602inline void set_position_relative(glm::mat4& matrix, const glm::vec3& camera_position)
1603{
1604 matrix[3] = glm::vec4(glm::vec3(matrix[3]) - camera_position, 1.0f);
1605}
1606} // namespace glm
entt::handle a
General purpose transformation class designed to maintain each component of the transformation separa...
Definition transform.hpp:27
void reset_skew() noexcept
Reset the skew component to zero.
void set_perspective(const vec4_t &perspective) noexcept
Set the perspective component.
auto get_matrix() const noexcept -> const mat4_t &
Get the transform matrix.
auto get_position() const noexcept -> const vec3_t &
Get the position component.
auto operator[](length_t i) const noexcept -> const col_t &
Get a column of the transform matrix.
static auto rotation(const quat_t &rotation) noexcept -> transform_t
Create a rotation transform.
vec< 3, T, Q > vec3_t
Definition transform.hpp:32
void set_scale(const vec3_t &scale) noexcept
Set the scale component.
auto operator*(const transform_t &t) const noexcept -> transform_t
Multiply two transforms.
void set_skew(const vec3_t &skew) noexcept
Set the skew component.
void rotate_local(T x, T y, T z) noexcept
Rotate the transform locally using Euler angles.
void reset_perspective() noexcept
Reset the perspective component to default.
auto get_scale() const noexcept -> const vec3_t &
Get the scale component.
auto transform_normal(const vec2_t &v) const noexcept -> vec2_t
Transform a 2D normal.
auto x_unit_axis() const noexcept -> vec3_t
Get the unit X axis of the transform.
void reset_scale() noexcept
Reset the scale component to one.
void reset_position() noexcept
Reset the position component to zero.
auto get_translation() const noexcept -> const vec3_t &
Get the translation component.
void set_translation(const vec3_t &position) noexcept
Set the translation component.
void set_rotation_euler_degrees(const vec3_t &euler_angles) noexcept
Set the rotation component using Euler angles in degrees.
auto operator=(transform_t &&m) noexcept -> transform_t &=default
Move assignment operator.
auto operator=(const transform_t &m) noexcept -> transform_t &=default
Copy assignment operator.
auto y_axis() const noexcept -> vec3_t
Get the Y axis of the transform.
void rotate(const quat_t &q) noexcept
Rotate the transform using a quaternion.
transform_t(transform_t &&t) noexcept=default
Move constructor.
void translate(T x, T y, T z=T(0)) noexcept
Translate the transform.
auto operator!=(const transform_t &t) const noexcept -> bool
Compare two transforms for inequality.
auto z_unit_axis() const noexcept -> vec3_t
Get the unit Z axis of the transform.
void scale(T x, T y, T z=T(1)) noexcept
Scale the transform.
vec< 2, T, Q > vec2_t
Definition transform.hpp:31
auto get_rotation() const noexcept -> const quat_t &
Get the rotation component.
transform_t(const transform_t &t) noexcept=default
Copy constructor.
auto get_perspective() const noexcept -> const vec4_t &
Get the perspective component.
auto y_unit_axis() const noexcept -> vec3_t
Get the unit Y axis of the transform.
qua< T, Q > quat_t
Definition transform.hpp:34
mat< 3, 3, T, Q > mat3_t
Definition transform.hpp:29
void translate_local(T x, T y, T z=T(0)) noexcept
Translate the transform locally.
auto inverse_transform_coord(const vec2_t &v) const noexcept -> vec2_t
Inverse transform a 2D coordinate.
auto inverse_transform_normal(const vec2_t &v) const noexcept -> vec2_t
Inverse transform a 2D normal.
static auto translation(const vec2_t &trans) noexcept -> transform_t
Create a translation transform.
static auto rotation_euler(const vec3_t &euler_angles) noexcept -> transform_t
Create a rotation transform from Euler angles.
auto get_skew() const noexcept -> const vec3_t &
Get the skew component.
auto x_axis() const noexcept -> vec3_t
Get the X axis of the transform.
typename mat4_t::col_type col_t
Definition transform.hpp:36
void set_rotation(const quat_t &rotation) noexcept
Set the rotation component.
auto transform_coord(const vec2_t &v) const noexcept -> vec2_t
Transform a 2D coordinate.
auto operator==(const transform_t &t) const noexcept -> bool
Compare two transforms for equality.
typename mat4_t::length_type length_t
Definition transform.hpp:35
void reset_rotation() noexcept
Reset the rotation component to identity.
static auto identity() noexcept -> const transform_t &
Get the identity transform.
void set_rotation_euler(const vec3_t &euler_angles) noexcept
Set the rotation component using Euler angles.
void reset_translation() noexcept
Reset the translation component to zero.
mat< 4, 4, T, Q > mat4_t
Definition transform.hpp:30
auto get_rotation_euler() const noexcept -> vec3_t
Get the rotation component as Euler angles.
void set_position(const vec3_t &position) noexcept
Set the position component.
vec< 4, T, Q > vec4_t
Definition transform.hpp:33
auto get_rotation_euler_degrees() const noexcept -> vec3_t
Get the rotation component as Euler angles in degrees.
void rotate_axis(T a, const vec3_t &v) noexcept
Rotate the transform around an axis.
auto compare(const transform_t &rhs) const noexcept -> int
Compare this transform with another.
static auto scaling(const vec2_t &scale) noexcept -> transform_t
Create a scaling transform.
~transform_t()=default
transform_t()=default
Default constructor.
auto z_axis() const noexcept -> vec3_t
Get the Z axis of the transform.
#define TRANSFORM_INLINE
Definition transform.hpp:17
float scale
Definition hub.cpp:25
auto look_rotation(const glm::vec3 &forward, const glm::vec3 &upwards) -> glm::quat
void set_position_relative(glm::mat4 &matrix, const glm::vec3 &camera_position)
auto from_to_rotation(const glm::vec3 &from, const glm::vec3 &to) -> glm::quat
GLM_FUNC_QUALIFIER T scale_fix(T &in)
Definition bbox.cpp:5
auto inverse(transform_t< T, Q > const &t) noexcept -> transform_t< T, Q >
GLM_FUNC_QUALIFIER void glm_recompose(mat< 4, 4, T, Q > &model_matrix, vec< 3, T, Q > const &in_scale, qua< T, Q > const &in_orientation, vec< 3, T, Q > const &in_translation, vec< 3, T, Q > const &in_skew, vec< 4, T, Q > const &in_perspective)
TRANSFORM_INLINE auto to_string(const T &v) -> std::string
auto transpose(transform_t< T, Q > const &t) noexcept -> transform_t< T, Q >
GLM_FUNC_QUALIFIER bool glm_decompose(mat< 4, 4, T, Q > const &ModelMatrix, vec< 3, T, Q > &Scale, qua< T, Q > &Orientation, vec< 3, T, Q > &Translation, vec< 3, T, Q > &Skew, vec< 4, T, Q > &Perspective)
float y
float x
float z
static GLM_FUNC_QUALIFIER auto call(math::transform_t< T, Q > const &x) -> std::string