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(
115 conditionedMatrix, component->transform.trs.scale, component->transform.trs.rotation, component->transform.trs.position, skew, perspective)) {
116
117 // decompose supports extracting skew and perspective from the transform matrix, which we don't support via
118 // transformcomponent properties. If we encounter such a matrix, warn the user and just use the matrix directly.
119 if (std::numeric_limits<float>::epsilon() <= dot(skew, skew)) {
120 LOG_WARNING(logger, "Transform matrix contains skew, which is not supported, using matrix directly. Check your models.");
121 component->transform.matrix = matrix;
122 component->transformFlags = 1;
123 }
124 else if (auto t = perspective - glm::vec4(0, 0, 0, 1); std::numeric_limits<float>::epsilon() <= dot(t, t)) {
125 LOG_WARNING(logger, "Transform matrix contains perspective, which is not supported, using matrix directly. Check your models.");
126 component->transform.matrix = matrix;
127 component->transformFlags = 1;
128 }
129
130 // Apply uniform scale factor
131 else {
132 component->transform.trs.scale *= uniformScale;
133 }
134
135 }
136 else {
137 LOG_WARNING(logger, "Failed to decompose matrix, using matrix directly and position/scale/rotation will have no effect. Check your models.");
138 component->transform.matrix = matrix;
139 component->transformFlags = 1;
140 }
141 }
142 else {
143 LOG_ERROR(logger, "Transform matrix probably totally broken, ignoring. Check your models.");
144 component->transform.trs.position = glm::vec3(0.f);
145 component->transform.trs.rotation = glm::quat();
146 component->transform.trs.scale = glm::vec3(1.f);
147 }
148
149 getLocalTransform(component) = matrix;
150
151 component->resetCarryChanged();
152 component->resetChanged();
153
154 getData<TransformState>(component).cached = false;
155}
156
157void Cogs::Core::TransformSystem::updateLocalToWorldTransform(const TransformComponent & component, bool dirty)
158{
159 auto & transformState = getData<Core::TransformState>(&component);
160
161 if (transformState.cached && !dirty) return;
162
163 if (dirty) updateLocalTransform(component);
164
165 if (component.parent) {
166 auto parent = component.parent.resolveComponent<TransformComponent>();
167
168 updateLocalToWorldTransform(*parent, dirty);
169
170 getLocalToWorld(&component) = getLocalToWorld(parent) * getLocalTransform(&component);
171 } else {
172 getLocalToWorld(&component) = getLocalTransform(&component);
173 }
174
175 transformState.changed = true;
176 transformState.cached = true;
177}
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.
The transform system handles TransformComponent instances, calculating local and global transform dat...
Log implementation class.
Definition: LogManager.h:140
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
ComponentType * resolveComponent() const
Definition: Component.h:90
glm::quat rotation
Rotation given as a quaternion.
glm::vec3 scale
Scale factor to apply to each of the axes.
glm::vec3 position
Local position relative to the global coordinates, or the parent coordinate system if the parent fiel...
Defines the state of a transform.
glm::mat4 matrix
Complete transformation.