Skip to content

Instantly share code, notes, and snippets.

@SanderMertens
Last active July 18, 2020 07:49
Show Gist options
  • Select an option

  • Save SanderMertens/1925c396378a3c9ccaf50b64b01ebc4a to your computer and use it in GitHub Desktop.

Select an option

Save SanderMertens/1925c396378a3c9ccaf50b64b01ebc4a to your computer and use it in GitHub Desktop.
#include <transform.h>
typedef float vec3[3];
typedef float quat[4];
typedef struct mat4 {
float value[4*4];
} mat4;
typedef struct transform {
/* 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;
} transform;
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
static
void mark_dirty(ecs_world_t *world, ecs_entity_t e, ecs_entity_t ecs_entity(transform))
{
// Set dirty flag. This will cause the pose to be updated in the local transform
transform *t = ecs_get_mut(world, e, transform, 0);
t->dirty = true;
// Set children_dirty flag on parent. If not set, subtree can be skipped by system
ecs_entity_t parent = ecs_get_parent(world, e, transform);
if (parent) {
transform *t_parent = ecs_get_mut(world, e, transform, 0);
t_parent->dirty_children ++;
}
}
// Transform children recursively, unconditionally. This happens if a parent
// was flagged as dirty.
static
void transform_children(ecs_world_t *world, ecs_entity_t parent, transform *t_parent, ecs_entity_t ecs_entity(transform))
{
mat4 pm = t_parent->world_mat;
ecs_iter_t it = ecs_scope_iter(world, parent);
while (ecs_scope_next(&it)) {
int32_t t_index = ecs_table_component_index(&it, ecs_entity(transform));
transform *tarr = ecs_table_column(&it, t_index);
// 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 (int32_t i = 0; i < it.count; i ++) {
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 (int32_t i = 0; i < it.count; i ++) {
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 (int32_t i = 0; i < it.count; i ++) {
transform_children(world, it.entities[i], &tarr[i], ecs_entity(transform));
}
}
}
// Transform system
static
void transform_system(ecs_iter_t* it)
{
ecs_entity_t ecs_entity(transform) = ecs_column_entity(it, 1);
transform* tarr = ecs_column(it, transform, 1);
transform* parent = ecs_column(it, transform, 2);
mat4 pm;
if (parent) {
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 (int32_t i = 0; i < it->count; ++i) {
transform* t = &tarr[i];
// Once a parent has been flagged as dirty, the whole subtree
// needs to be updated unconditionally.
if (t->dirty) {
printf("Transform dirty entity '%s'\n", ecs_get_name(it->world, it->entities[i]));
// 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
transform_children(it->world, it->entities[i], t, ecs_entity(transform));
// Reduce dirty count of parent
if (parent) {
parent->dirty_children --;
}
}
}
}
int main(int argc, char *argv[]) {
ecs_world_t *world = ecs_init();
ECS_COMPONENT(world, transform);
ECS_SYSTEM(world, transform_system, EcsOnUpdate, transform, CASCADE:transform);
ECS_ENTITY(world, E1, transform);
ECS_ENTITY(world, E2, transform, CHILDOF | E1);
ECS_ENTITY(world, E3, transform, CHILDOF | E1.E2);
ECS_ENTITY(world, E4, transform, CHILDOF | E1.E2.E3);
// Invalidate entire tree
mark_dirty(world, E1, ecs_entity(transform));
// This will not cause part of the subtree to get evaluated twice
mark_dirty(world, E3, ecs_entity(transform));
// 1st iteration, subtree of E1 will get updated
ecs_progress(world, 0);
// 2nd iteration, nothing is dirty & nothing is transformed
ecs_progress(world, 0);
ecs_fini(world);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment