--- a/src/scene.c Fri Mar 29 00:03:25 2024 +0100 +++ b/src/scene.c Mon Apr 01 18:54:19 2024 +0200 @@ -30,6 +30,8 @@ #include "ascension/context.h" +#include <cx/linked_list.h> +#include <cx/array_list.h> #include <cx/tree.h> #include <cx/utils.h> @@ -56,53 +58,26 @@ ); } -void asc_scene_init(AscScene *scene) { - if (scene->root != NULL) { - asc_error("Scene is already initialized."); - return; +struct asc_render_group_entry { + asc_scene_draw_func draw; + AscSceneNode const *node; +}; + +#define asc_draw_render_group(iter) \ + cx_foreach(struct asc_render_group_entry*, entry, iter) { \ + entry->draw(entry->node); \ } - // zero everything, first - memset(scene, 0, sizeof(AscScene)); - - // default viewport is the entire viewport of the active window - scene->viewport.size = asc_context.active_window->dimensions; - - // create the root node - scene->root = asc_scene_node_empty(); - - // initialize the render groups - cx_array_initialize(scene->rg_sprites_opaque, 8); - cx_array_initialize(scene->rg_sprites_blended, 8); -} - -void asc_scene_destroy(AscScene *scene) { - asc_scene_node_free(scene->root); -} - -void asc_scene_add(AscScene *scene, AscSceneNode *node) { - asc_scene_node_link(scene->root, node); - asc_node_update(node); -} - -#define asc_scene_draw_render_group(rg) \ - cx_for_n(i, rg##_size) { \ - rg[i].draw(rg[i].node); \ - } (void)0 - -#define asc_scene_draw_render_group_reversed(rg) \ - for(size_t i = rg##_size ; i > 0 ; i--) { \ - rg[i-1].draw(rg[i-1].node); \ - } (void)0 - -void asc_scene_draw(AscScene *scene) { - // reset render groups - // TODO: avoid recalculating the groups, if possible - scene->rg_sprites_opaque_size = 0; - scene->rg_sprites_blended_size = 0; +void asc_scene_draw(AscSceneNode *root, asc_recti viewport, AscCamera *camera) { + // create render groups + CxList *render_group[ASC_RENDER_GROUP_COUNT]; + cx_for_n(i, ASC_RENDER_GROUP_COUNT) { + render_group[i] = cxArrayListCreateSimple( + sizeof(struct asc_render_group_entry), 32); + } // skip the root node deliberately, we know it's just the container - CxTreeVisitor iter = asc_scene_node_visitor(scene->root); + CxTreeVisitor iter = asc_scene_node_visitor(root); cxIteratorNext(iter); // update the children and add them to the render groups @@ -110,12 +85,16 @@ node->depth = iter.depth; // execute behaviors, first - AscBehaviorNode *behavior = node->behaviors; - while (behavior) { - behavior->func(node); - behavior = behavior->next; + if (node->behaviors != NULL) { + CxIterator behavior_iter = cxListIterator(node->behaviors); + cx_foreach(asc_scene_update_func, behavior, behavior_iter) { + behavior(node); + } } + // TODO: implement culling + // TODO: implement a hidden flag (requires UCX tree-continue function) + // check if geometry needs update if (node->need_graphics_update) { assert(node->update_func != NULL); @@ -142,56 +121,51 @@ struct asc_render_group_entry entry = { node->draw_func, node }; - switch (node->render_group) { - case ASC_RENDER_GROUP_SPRITE_OPAQUE: - cx_array_simple_add(scene->rg_sprites_opaque, entry); - break; - case ASC_RENDER_GROUP_SPRITE_BLEND: - cx_array_simple_add(scene->rg_sprites_blended, entry); - break; - } + cxListAdd(render_group[node->render_group], &entry); } } // set the viewport (in OpenGL we need to invert the Y axis) glViewport( - scene->viewport.pos.x, - -scene->viewport.pos.y, - scene->viewport.size.width, - scene->viewport.size.height + viewport.pos.x, + -viewport.pos.y, + viewport.size.width, + viewport.size.height ); - glClear(GL_COLOR_BUFFER_BIT); - // ----------------------------------------- - // process the render groups for each camera - // ----------------------------------------- + // ------------------------- + // process the render groups + // ------------------------- AscShaderProgram *shader; - cx_for_n(cam_id, ASC_SCENE_CAMERAS_MAX) { - // update camera parameters, first - AscCamera *camera = &scene->cameras[cam_id]; - if (camera->update == NULL) continue; - camera->update(camera); + CxIterator render_iter; + + // 2D Elements + // =========== + glEnable(GL_DEPTH_TEST); + glClear(GL_DEPTH_BUFFER_BIT); - // 2D Elements - // =========== - glEnable(GL_DEPTH_TEST); - glClear(GL_DEPTH_BUFFER_BIT); + // Sprites + // ------- + // TODO: implement view matrix for 2D worlds + shader = &asc_context.active_window->glctx.shader.sprite.base; + glUseProgram(shader->id); + glUniformMatrix4fv(shader->projection, 1, + GL_FALSE, camera->projection); - // Sprites - // ------- - // TODO: see if we can really always ignore the view matrix - shader = &asc_context.active_window->glctx.shader.sprite.base; - glUseProgram(shader->id); - glUniformMatrix4fv(shader->projection, 1, - GL_FALSE, camera->projection); + // render opaque sprites from front to back + glDisable(GL_BLEND); + render_iter = cxListBackwardsIterator(render_group[ASC_RENDER_GROUP_SPRITE_OPAQUE]); + asc_draw_render_group(render_iter); - // render opaque sprites from front to back - glDisable(GL_BLEND); - asc_scene_draw_render_group_reversed(scene->rg_sprites_opaque); - // render sprites with alpha value from back to front - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - asc_scene_draw_render_group(scene->rg_sprites_blended); + // render sprites with alpha value from back to front + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + render_iter = cxListIterator(render_group[ASC_RENDER_GROUP_SPRITE_BLEND]); + asc_draw_render_group(render_iter); + + // destroy render groups + cx_for_n(i, ASC_RENDER_GROUP_COUNT) { + cxListDestroy(render_group[i]); } } @@ -213,6 +187,9 @@ CxTreeIterator iter = asc_scene_node_iterator(node, true); cx_foreach(AscSceneNode*, child, iter) { if (!iter.exiting) continue; + if (child->behaviors != NULL) { + cxListDestroy(child->behaviors); + } if (child->free_func != NULL) { child->free_func(child); } else { @@ -241,28 +218,23 @@ ); } -AscBehaviorNode *asc_scene_add_behavior(AscSceneNode *node, asc_scene_update_func behavior) { - AscBehaviorNode *behavior_node = calloc(1, sizeof(AscBehaviorNode)); - behavior_node->func = behavior; - cx_tree_link( - node, - behavior_node, - offsetof(AscBehaviorNode, parent), - offsetof(AscSceneNode, behaviors), - offsetof(AscBehaviorNode, prev), - offsetof(AscBehaviorNode, next) - ); - return behavior_node; +void asc_scene_add_behavior( + AscSceneNode *node, + asc_scene_update_func behavior +) { + if (node->behaviors == NULL) { + node->behaviors = cxLinkedListCreateSimple(CX_STORE_POINTERS); + } + cxListAdd(node->behaviors, behavior); } -void asc_scene_remove_behavior(AscBehaviorNode *node) { - cx_tree_unlink( - node, - offsetof(AscBehaviorNode, parent), - offsetof(AscSceneNode, behaviors), - offsetof(AscBehaviorNode, prev), - offsetof(AscBehaviorNode, next) - ); +void asc_scene_remove_behavior( + AscSceneNode *node, + asc_scene_update_func behavior +) { + if (node->behaviors != NULL) { + cxListFindRemove(node->behaviors, behavior); + } } void asc_update_transform(AscSceneNode *node) {