Last active
August 24, 2021 01:57
-
-
Save SanderMertens/0a3bf790ecf5787b6f6fb33ac8da42b4 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
| 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