Skip to content

Instantly share code, notes, and snippets.

@SanderMertens
Last active August 24, 2021 01:57
Show Gist options
  • Select an option

  • Save SanderMertens/0a3bf790ecf5787b6f6fb33ac8da42b4 to your computer and use it in GitHub Desktop.

Select an option

Save SanderMertens/0a3bf790ecf5787b6f6fb33ac8da42b4 to your computer and use it in GitHub Desktop.
typedef float vec3[3];
typedef float quat[4];
typedef struct mat4 {
float value[4*4];
} mat4;
struct transform {
transform()
: pose({ })
, dirty(false)
, dirty_children(0)
, local_mat({ })
, world_mat({ })
{ }
/* Individual components */
struct {
vec3 scale;
quat rotation;
vec3 translation;
} pose;
/* Indicates that cached matrices should be refreshed */
bool dirty;
/* Number of dirty children in parent */
int dirty_children;
/* Cached combined matrices */
mat4 local_mat;
mat4 world_mat;
};
mat4 mat4_mul_mat4(mat4 m1, mat4 m2) { return (mat4){ 0 }; }
mat4 mat4_id() { return (mat4){ 0 }; }
mat4 mat4_world(vec3 t, vec3 s, quat r) { return (mat4){ 0 }; }
// Mark an entity dirty
void mark_dirty(flecs::world& world, flecs::entity e)
{
// Set dirty flag. This will cause the pose to be updated in the local transform
transform *t = e.get_mut<transform>();
t->dirty = true;
// Set children_dirty flag on parent. If not set, subtree can be skipped by system
auto parent = e.get_object(flecs::ChildOf);
if (parent) {
transform *t_parent = parent.get_mut<transform>();
t_parent->dirty_children ++;
}
}
// Transform children recursively, unconditionally. This happens if a parent
// was flagged as dirty.
void transform_children(
flecs::world& world,
flecs::entity parent,
transform *t_parent)
{
mat4 pm = t_parent->world_mat;
for (auto it : parent.children()) {
auto tarr = it.table_column<transform>();
// It is possible that a dirty tree contains dirty entities.
// If a child is dirty, just update its local transform. The
// next step will update the world transform.
for (auto i : it) {
std::cout << "Transform child entity " << it.entity(i).name() << std::endl;
transform *t = &tarr[i];
if (t->dirty) {
t->local_mat = mat4_world(
t->pose.translation,
t->pose.scale,
t->pose.rotation);
t->dirty = 0;
// Reduce dirty count, so that when we hit the
// parent in the system, we can skip it if it
// has no more dirty children.
t_parent->dirty_children --;
}
}
// Only update world_mat, local_mat did not change. This loop
// can be potentially vectorized if the mat library has
// support for it.
for (auto i : it) {
transform *t = &tarr[i];
t->world_mat = mat4_mul_mat4(pm, t->local_mat);
}
// Now recursively update children in separate loop so that
// we don't add branches to the transform loop
for (auto i : it) {
transform_children(world, it.entity(i), &tarr[i]);
}
}
}
void transform_system(flecs::iter& it, transform *tarr)
{
auto parent = it.term<transform>(2);
mat4 pm;
if (parent.is_set()) {
if (!parent->dirty_children) {
// None of its children are dirty, no need to iterate
// current table. This ensures that even as we visit
// tables of dirty parents that were already updated
// by this system, we won't re-evaluate them.
//
// This check won't be applied to entities in the root.
// If a large number of entities have no parent, it
// can make sense to add them under a single root
// entity with the identity matrix.
return;
}
pm = parent->world_mat;
} else {
pm = mat4_id();
}
for (auto i : it) {
transform *t = &tarr[i];
// Once a parent has been flagged as dirty, the whole subtree
// needs to be updated unconditionally.
if (t->dirty) {
std::cout << "Transform parent entity " << it.entity(i).name() << std::endl;
// Update local matrix, only when the entity is actually
// dirty (we don't need to do this for children).
mat4 m = mat4_world(
t->pose.translation,
t->pose.scale,
t->pose.rotation);
t->local_mat = m;
// Also update world matrix
t->world_mat = mat4_mul_mat4(pm, m);
t->dirty = 0;
// pose got updated, need to update children unconditionally
auto world = it.world();
transform_children(world, it.entity(i), t);
// Reduce dirty count of parent
if (parent.is_set()) {
parent->dirty_children --;
}
}
}
}
int main(int argc, char *argv[]) {
flecs::world ecs;
ecs.system<transform, transform>("transform_system")
// select 2nd transform argument
.arg(2)
// SuperSet,ChildOf selects from parent, Cascade orders breadth-first
.set(flecs::Cascade | flecs::SuperSet, flecs::ChildOf)
// Add optional so that root entities are also matched
.oper(flecs::Optional)
.iter(transform_system);
auto e1 = ecs.entity("e1")
.add<transform>();
auto e2 = ecs.entity("e2")
.add<transform>()
.child_of(e1);
auto e3 = ecs.entity("e3")
.add<transform>()
.child_of(e2);
auto e4 = ecs.entity("e4")
.add<transform>()
.child_of(e3);
// Invalidate entire tree
mark_dirty(ecs, e1);
// This will not cause part of the subtree to get evaluated twice
mark_dirty(ecs, e3);
// 1st iteration, subtree of E1 will get updated
ecs.progress();
// 2nd iteration, nothing is dirty & nothing is transformed
ecs.progress();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment