This repository has been archived by the owner on Dec 20, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
entt_scene.cpp
217 lines (163 loc) · 5.85 KB
/
entt_scene.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#include <algorithm>
#include <iostream>
#include <optional>
#include <type_traits>
#include <vector>
#include <cassert>
#include <cmath>
#include "entt/entt.hpp"
//////////////////////////////////////////////////////////////////////////
// Just a very minimal definition of a 3D vector.
struct Vec3 {
float x = 0;
float y = 0;
float z = 0;
static const Vec3 zero;
static const Vec3 one;
};
const Vec3 Vec3::zero = {0, 0, 0};
const Vec3 Vec3::one = {1, 1, 1};
Vec3 operator+(const Vec3 &a, const Vec3 &b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
std::ostream &operator<<(std::ostream &out, const Vec3 &v)
{
return out << "Vec3: " << v.x << " " << v.y << " " << v.z;
}
//////////////////////////////////////////////////////////////////////////
// In this minimal example, Transform only contains the position.
struct Transform {
Vec3 position = Vec3::zero;
};
// Operator for combining Transforms.
Transform operator*(const Transform &a, const Transform &b) { return {a.position + b.position}; }
std::ostream &operator<<(std::ostream &out, const Transform &t) { return out << "Transform: " << t.position; }
//////////////////////////////////////////////////////////////////////////
// A SceneNode contains an entity's local Transform as well as references to
// parent and child nodes. Additionally it provides a reference to the
// corresponding entity. Ownership is managed by the entity component system.
//
// The following invariants are maintained:
// - Parent and child references are kept consistent.
// - Combined parent transforms are cached. This cache is invalided
// automatically.
class SceneNode
{
public:
~SceneNode()
{
if (m_parent) {
m_parent->removeChild(this);
}
for (const auto &child : m_children) {
child->clearParent();
}
}
entt::entity entity() const { return m_entity; }
const Transform &transform() const { return m_transform; }
void setTransform(const Transform &transform)
{
invalidateChildrenCachedParentTransform();
m_transform = transform;
}
Transform parentTransform() const
{
if (!m_cachedParentTransform) {
m_cachedParentTransform = m_parent ? m_parent->globalTransform() : Transform{};
}
return *m_cachedParentTransform;
}
Transform globalTransform() const { return parentTransform() * m_transform; }
SceneNode *parent() const { return m_parent; }
const std::vector<SceneNode *> &children() const { return m_children; }
void addChild(SceneNode *child)
{
// For simplicity we only allow adding orphans.
assert(!child->m_parent);
child->setParent(this);
m_children.push_back(child);
}
void removeChild(SceneNode *child)
{
assert(child->m_parent == this);
auto it = std::find(m_children.cbegin(), m_children.cend(), child);
if (it == m_children.cend()) {
assert(false && "Parent-child-invariant is broken!");
return;
}
child->clearParent();
m_children.erase(it);
}
private:
entt::entity m_entity;
Transform m_transform;
SceneNode *m_parent = nullptr;
std::vector<SceneNode *> m_children;
void setParent(SceneNode *parent)
{
invalidateCachedParentTransform();
m_parent = parent;
}
void clearParent() { setParent(nullptr); }
mutable std::optional<Transform> m_cachedParentTransform;
void invalidateCachedParentTransform()
{
m_cachedParentTransform.reset();
invalidateChildrenCachedParentTransform();
}
void invalidateChildrenCachedParentTransform()
{
for (const auto &child : m_children) {
child->invalidateCachedParentTransform();
}
}
friend void linkSceneNodeWithEntity(entt::registry &, entt::entity);
};
//////////////////////////////////////////////////////////////////////////
// Ensure components are not relocated in memory. This allows us to use regular
// pointers pointing to them.
template <>
struct entt::component_traits<SceneNode> : entt::basic_component_traits {
using in_place_delete = std::true_type;
};
// Links an entity with its corresponding SceneNode. This function is used
// automatically by the registry using the provide callback mechanism.
void linkSceneNodeWithEntity(entt::registry ®, entt::entity e) { reg.get<SceneNode>(e).m_entity = e; }
void registerSceneNodeCallbacks(entt::registry ®)
{
reg.on_construct<SceneNode>().connect<&linkSceneNodeWithEntity>();
reg.on_update<SceneNode>().connect<&linkSceneNodeWithEntity>();
}
void unregisterSceneNodeCallbacks(entt::registry ®)
{
reg.on_construct<SceneNode>().disconnect<&linkSceneNodeWithEntity>();
reg.on_update<SceneNode>().disconnect<&linkSceneNodeWithEntity>();
}
//////////////////////////////////////////////////////////////////////////
int main()
{
entt::registry reg;
registerSceneNodeCallbacks(reg);
// Note the use of pointers for SceneNodes as the SceneNode interface
// prefers pointers over references.
auto ship = reg.create();
auto *shipNode = ®.emplace<SceneNode>(ship);
auto captain = reg.create();
auto *captainNode = ®.emplace<SceneNode>(captain);
// connect captain with his ship
{
shipNode->addChild(captainNode);
assert(shipNode->children().at(0)->entity() == captain);
assert(captainNode->parent()->entity() == ship);
}
// sail the sea
{
shipNode->setTransform({42, 42, 42});
assert(captainNode->transform().position.x == 0); // local
assert(captainNode->globalTransform().position.x == 42);
}
// ship sinks :(
{
reg.destroy(ship);
assert(captainNode->parent() == nullptr);
assert(captainNode->transform().position.x == captainNode->globalTransform().position.x);
}
}