Cogs.Core
TransformSystem.cpp
1#include "Foundation/Logging/Logger.h"
2
3#include "TransformSystem.h"
4
5#include <glm/vec3.hpp>
6#include <glm/vec4.hpp>
7#include <glm/mat4x4.hpp>
8#include <glm/ext/quaternion_float.hpp>
9#include <glm/gtx/matrix_decompose.hpp>
10
11#include "Services/Variables.h"
12
13#include "Utilities/Parallel.h"
14
15namespace
16{
17 using namespace Cogs::Core;
18 Cogs::Logging::Log logger = Cogs::Logging::getLogger("TransformSystem");
19
20 auto transformChainChanged(TransformSystem * system, TransformComponent * component) -> bool
21 {
22 const auto & transformState = system->getData<TransformState>(component);
23
24 bool componentChanged = !transformState.cached || component->hasChanged();
25
26 if(auto * parentTransform = component->parent.resolveComponent<TransformComponent>(); parentTransform) {
27 return componentChanged || transformChainChanged(system, parentTransform);
28 } else {
29 return componentChanged;
30 }
31 }
32}
33
35{
36 const bool workParallel = context->engine->workParallel();
37
38 if (workParallel) {
39 auto group = Parallel::processComponents(context, pool, "TransformSystem::transformChain", [this](TransformComponent & component, size_t) {
40 TransformState & transformState = this->getData<TransformState>(&component);
41
42 transformState.cached = !transformChainChanged(this, &component);
43 transformState.changed = !transformState.cached;
44 });
45
46 context->taskManager->destroy(group);
47
48 group = Parallel::processComponents(context, pool, "TransformSystem::updateLocal", [this](TransformComponent & component, size_t) {
49 if (component.hasChanged()) {
50 updateLocalTransform(component);
51 }
52 });
53
54 context->taskManager->destroy(group);
55 } else {
56 for (auto & component : pool) {
57 TransformState & transformState = this->getData<TransformState>(&component);
58
59 transformState.cached = !transformChainChanged(this, &component);
60 transformState.changed = !transformState.cached;
61 }
62
63 for (auto & component : pool) {
64 if (component.hasChanged()) {
65 updateLocalTransform(component);
66 }
67 }
68 }
69
70 {
71 CpuInstrumentationScope(SCOPE_SYSTEMS, "TransformSystem::updateLocalToWorldTransform");
72 for (auto & component : pool) {
73 updateLocalToWorldTransform(component);
74 }
75 }
76}
77
78void Cogs::Core::TransformSystem::handleOriginUpdate(class Context* /*context*/)
79{
80 if (origin.x == originUpdate.x && origin.y == originUpdate.y && origin.z == originUpdate.z) {
81 // Origin update is exactly identical current origin, so no need to do anything
82 return;
83 }
84
85 // Swap in new origin and trigger calculation of new transforms for all components.
86 origin = originUpdate;
87 for (auto& component : pool) {
88 component.setChanged();
89 }
90}
91
92void Cogs::Core::TransformSystem::setLocalTransform(TransformComponent* component, const glm::mat4& matrix)
93{
94 // The matrix decompose function uses the determinant of the matrix to check if it is singular (i.e. broken),
95 // and since this is numerics, there needs to be a threshold set somewhere. GLM has chosen FLT_MIN (~1.2e-7).
96 //
97 // If the matrix contains a uniform scale of e.g. 3/1000, the determinant of this matrix is (3/1000)^3=2.7e-8,
98 // and the matrix will fail the test in the decompose function, even though it is definitely non-singular.
99 //
100 // To circument this, we factor out the uniform scale before passing the matrix to decompose (and thus remove
101 // the scaling of the determinant which caused the problem), and apply the scaling afterwards.
102 //
103 // We set the sanity check on scale factor to that we should be able to calculate the inverse scale matrix.
104 float uniformScale = std::cbrt(std::abs(glm::determinant(glm::mat3(matrix))));
105 float reciprocalUniformScale = 1.f / uniformScale;
106 if (std::isfinite(reciprocalUniformScale)) {
107 glm::mat4 conditionedMatrix = matrix * glm::mat4(reciprocalUniformScale, 0.f, 0.f, 0.f,
108 0.f, reciprocalUniformScale, 0.f, 0.f,
109 0.f, 0.f, reciprocalUniformScale, 0.f,
110 0.f, 0.f, 0.f, 1.f);
111
112 glm::vec3 skew;
113 glm::vec4 perspective;
114 if (glm::decompose(conditionedMatrix, component->scale, component->rotation, component->position, skew, perspective)) {
115
116 // decompose supports extracting skew and perspective from the transform matrix, which we don't support via
117 // transformcomponent properties. If we encounter such a matrix, warn the user and just use the matrix directly.
118 if (std::numeric_limits<float>::epsilon() <= dot(skew, skew)) {
119 LOG_WARNING(logger, "Transform matrix contains skew, which is not supported, using matrix directly. Check your models.");
120 component->transform = matrix;
121 component->transformFlags = 1;
122 }
123 else if (auto t = perspective - glm::vec4(0, 0, 0, 1); std::numeric_limits<float>::epsilon() <= dot(t, t)) {
124 LOG_WARNING(logger, "Transform matrix contains perspective, which is not supported, using matrix directly. Check your models.");
125 component->transform = matrix;
126 component->transformFlags = 1;
127 }
128
129 // Apply uniform scale factor
130 else {
131 component->scale *= uniformScale;
132 }
133
134 }
135 else {
136 LOG_WARNING(logger, "Failed to decompose matrix, using matrix directly and position/scale/rotation will have no effect. Check your models.");
137 component->transform = matrix;
138 component->transformFlags = 1;
139 }
140 }
141 else {
142 LOG_ERROR(logger, "Transform matrix probably totally broken, ignoring. Check your models.");
143 component->position = glm::vec3(0.f);
144 component->rotation = glm::quat();
145 component->scale = glm::vec3(1.f);
146 }
147
148 getLocalTransform(component) = matrix;
149
150 component->resetCarryChanged();
151 component->resetChanged();
152
153 getData<TransformState>(component).cached = false;
154}
155
156void Cogs::Core::TransformSystem::updateLocalToWorldTransform(const TransformComponent & component, bool dirty)
157{
158 auto & transformState = getData<Core::TransformState>(&component);
159
160 if (transformState.cached && !dirty) return;
161
162 if (dirty) updateLocalTransform(component);
163
164 if (component.parent) {
165 auto parent = component.parent.resolveComponent<TransformComponent>();
166
167 updateLocalToWorldTransform(*parent, dirty);
168
169 getLocalToWorld(&component) = getLocalToWorld(parent) * getLocalTransform(&component);
170 } else {
171 getLocalToWorld(&component) = getLocalTransform(&component);
172 }
173
174 transformState.changed = true;
175 transformState.cached = true;
176}
void resetCarryChanged()
Reset the CarryChanged flag. Called at start of redraw. See ComponentFlags::CarryChanged.
Definition: Component.h:296
void resetChanged()
Resets the changed state of the component, respecting any carry state set.
Definition: Component.h:309
Context * context
Pointer to the Context instance the system lives in.
void update()
Updates the system state to that of the current frame.
ComponentPool< ComponentType > pool
Pool of components managed by the system.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class TaskManager > taskManager
TaskManager service instance.
Definition: Context.h:186
std::unique_ptr< class Engine > engine
Engine instance.
Definition: Context.h:222
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
uint32_t transformFlags
Transform flags.
ComponentModel::ComponentHandle parent
Parent transform of the component.
glm::vec3 scale
Scale factor to apply to each of the axes.
glm::mat4 transform
Complete transformation.
glm::quat rotation
Rotation given as a quaternion.
glm::vec3 position
Local position relative to the global coordinates, or the parent coordinate system if the parent fiel...
The transform system handles TransformComponent instances, calculating local and global transform dat...
Log implementation class.
Definition: LogManager.h:139
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
ComponentType * resolveComponent() const
Definition: Component.h:90
Defines the state of a transform.