Last active
July 18, 2020 07:49
-
-
Save SanderMertens/1925c396378a3c9ccaf50b64b01ebc4a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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