universe@21: /* universe@21: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. universe@21: * Copyright 2023 Mike Becker. All rights reserved. universe@21: * universe@21: * Redistribution and use in source and binary forms, with or without universe@21: * modification, are permitted provided that the following conditions are met: universe@21: * universe@21: * 1. Redistributions of source code must retain the above copyright universe@21: * notice, this list of conditions and the following disclaimer. universe@21: * universe@21: * 2. Redistributions in binary form must reproduce the above copyright universe@21: * notice, this list of conditions and the following disclaimer in the universe@21: * documentation and/or other materials provided with the distribution. universe@21: * universe@21: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" universe@21: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE universe@21: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE universe@21: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE universe@21: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR universe@21: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF universe@21: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS universe@21: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN universe@21: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) universe@21: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE universe@21: * POSSIBILITY OF SUCH DAMAGE. universe@21: */ universe@21: universe@21: #include "ascension/scene.h" universe@21: universe@37: #include "ascension/context.h" universe@37: universe@47: #include universe@47: #include universe@29: #include universe@37: #include universe@37: universe@37: #include "ascension/shader.h" universe@37: #include universe@29: universe@21: #include universe@21: universe@38: static CxTreeIterator asc_scene_node_iterator( universe@38: AscSceneNode *node, universe@38: bool visit_on_exit universe@38: ) { universe@38: return cx_tree_iterator( universe@38: node, visit_on_exit, universe@38: offsetof(AscSceneNode, children), universe@38: offsetof(AscSceneNode, next) universe@38: ); universe@38: } universe@38: universe@43: static CxTreeVisitor asc_scene_node_visitor(AscSceneNode *node) { universe@43: return cx_tree_visitor(node, universe@43: offsetof(AscSceneNode, children), universe@43: offsetof(AscSceneNode, next) universe@43: ); universe@43: } universe@43: universe@47: struct asc_render_group_entry { universe@47: asc_scene_draw_func draw; universe@47: AscSceneNode const *node; universe@47: }; universe@47: universe@47: #define asc_draw_render_group(iter) \ universe@47: cx_foreach(struct asc_render_group_entry*, entry, iter) { \ universe@47: entry->draw(entry->node); \ universe@21: } universe@37: universe@47: void asc_scene_draw(AscSceneNode *root, asc_recti viewport, AscCamera *camera) { universe@47: // create render groups universe@47: CxList *render_group[ASC_RENDER_GROUP_COUNT]; universe@47: cx_for_n(i, ASC_RENDER_GROUP_COUNT) { universe@47: render_group[i] = cxArrayListCreateSimple( universe@47: sizeof(struct asc_render_group_entry), 32); universe@47: } universe@32: universe@32: // skip the root node deliberately, we know it's just the container universe@47: CxTreeVisitor iter = asc_scene_node_visitor(root); universe@32: cxIteratorNext(iter); universe@32: universe@37: // update the children and add them to the render groups universe@30: cx_foreach(AscSceneNode*, node, iter) { universe@41: node->depth = iter.depth; universe@41: universe@55: // skip hidden nodes (and all their children) universe@55: if (node->hidden) { universe@55: cxTreeVisitorContinue(iter); universe@55: } universe@55: universe@33: // execute behaviors, first universe@47: if (node->behaviors != NULL) { universe@47: CxIterator behavior_iter = cxListIterator(node->behaviors); universe@47: cx_foreach(asc_scene_update_func, behavior, behavior_iter) { universe@47: behavior(node); universe@47: } universe@33: } universe@33: universe@47: // TODO: implement culling universe@47: // TODO: implement a hidden flag (requires UCX tree-continue function) universe@47: universe@33: // check if geometry needs update universe@39: if (node->need_graphics_update) { universe@37: assert(node->update_func != NULL); universe@39: node->need_graphics_update = false; universe@32: node->update_func(node); universe@32: } universe@37: if (node->need_transform_update) { universe@37: node->need_transform_update = false; universe@45: asc_transform_from_parts( universe@45: node->transform, universe@45: node->position, universe@45: node->scale, universe@45: node->rotation universe@45: ); universe@45: asc_mat4f_mulst( universe@45: node->world_transform, universe@45: node->transform, universe@45: node->parent->world_transform universe@45: ); universe@37: } universe@33: universe@37: // add to render group universe@30: if (node->draw_func != NULL) { universe@37: struct asc_render_group_entry entry = { universe@37: node->draw_func, node universe@37: }; universe@47: cxListAdd(render_group[node->render_group], &entry); universe@30: } universe@29: } universe@37: universe@37: // set the viewport (in OpenGL we need to invert the Y axis) universe@37: glViewport( universe@47: viewport.pos.x, universe@47: -viewport.pos.y, universe@47: viewport.size.width, universe@47: viewport.size.height universe@37: ); universe@37: universe@47: // ------------------------- universe@47: // process the render groups universe@47: // ------------------------- universe@44: AscShaderProgram *shader; universe@47: CxIterator render_iter; universe@37: universe@47: // 2D Elements universe@47: // =========== universe@47: glEnable(GL_DEPTH_TEST); universe@47: glClear(GL_DEPTH_BUFFER_BIT); universe@37: universe@47: // Sprites universe@47: // ------- universe@47: // TODO: implement view matrix for 2D worlds universe@47: shader = &asc_context.active_window->glctx.shader.sprite.base; universe@47: glUseProgram(shader->id); universe@47: glUniformMatrix4fv(shader->projection, 1, universe@47: GL_FALSE, camera->projection); universe@41: universe@47: // render opaque sprites from front to back universe@47: glDisable(GL_BLEND); universe@47: render_iter = cxListBackwardsIterator(render_group[ASC_RENDER_GROUP_SPRITE_OPAQUE]); universe@47: asc_draw_render_group(render_iter); universe@47: universe@47: // render sprites with alpha value from back to front universe@47: glEnable(GL_BLEND); universe@47: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); universe@47: render_iter = cxListIterator(render_group[ASC_RENDER_GROUP_SPRITE_BLEND]); universe@47: asc_draw_render_group(render_iter); universe@47: universe@47: // destroy render groups universe@47: cx_for_n(i, ASC_RENDER_GROUP_COUNT) { universe@47: cxListDestroy(render_group[i]); universe@37: } universe@29: } universe@29: universe@29: AscSceneNode *asc_scene_node_empty(void) { universe@27: AscSceneNode *node = calloc(1, sizeof(AscSceneNode)); universe@29: node->free_func = (asc_scene_free_func) free; universe@51: node->scale.x = node->scale.y = node->scale.z = 1; universe@45: asc_transform_identity(node->transform); universe@38: asc_transform_identity(node->world_transform); universe@21: return node; universe@21: } universe@21: universe@21: void asc_scene_node_free(AscSceneNode *node) { universe@21: if (node == NULL) return; universe@27: universe@27: // remove this node from its parent universe@27: asc_scene_node_unlink(node); universe@27: universe@31: // free the entire subtree universe@38: CxTreeIterator iter = asc_scene_node_iterator(node, true); universe@31: cx_foreach(AscSceneNode*, child, iter) { universe@31: if (!iter.exiting) continue; universe@47: if (child->behaviors != NULL) { universe@47: cxListDestroy(child->behaviors); universe@47: } universe@31: if (child->free_func != NULL) { universe@31: child->free_func(child); universe@31: } else { universe@31: free(child); universe@31: } universe@21: } universe@21: } universe@21: universe@29: void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node) { universe@33: cx_tree_link( universe@33: parent, node, universe@33: offsetof(AscSceneNode, parent), universe@33: offsetof(AscSceneNode, children), universe@33: offsetof(AscSceneNode, prev), universe@33: offsetof(AscSceneNode, next) universe@33: ); universe@29: } universe@29: universe@21: void asc_scene_node_unlink(AscSceneNode *node) { universe@33: cx_tree_unlink( universe@33: node, universe@33: offsetof(AscSceneNode, parent), universe@33: offsetof(AscSceneNode, children), universe@33: offsetof(AscSceneNode, prev), universe@33: offsetof(AscSceneNode, next) universe@33: ); universe@33: } universe@33: universe@47: void asc_scene_add_behavior( universe@47: AscSceneNode *node, universe@47: asc_scene_update_func behavior universe@47: ) { universe@47: if (node->behaviors == NULL) { universe@47: node->behaviors = cxLinkedListCreateSimple(CX_STORE_POINTERS); universe@47: } universe@47: cxListAdd(node->behaviors, behavior); universe@33: } universe@33: universe@47: void asc_scene_remove_behavior( universe@47: AscSceneNode *node, universe@47: asc_scene_update_func behavior universe@47: ) { universe@47: if (node->behaviors != NULL) { universe@47: cxListFindRemove(node->behaviors, behavior); universe@47: } universe@33: } universe@38: universe@45: void asc_update_transform(AscSceneNode *node) { universe@45: if (node->need_transform_update) return; universe@45: universe@38: CxTreeIterator iter = asc_scene_node_iterator(node, false); universe@38: cx_foreach(AscSceneNode*, n, iter) { universe@45: // TODO: break/continue when subtree is already marked for update universe@38: n->need_transform_update = true; universe@38: } universe@45: }