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: #include "ascension/error.h" universe@21: universe@37: #include "ascension/context.h" universe@37: 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@21: void asc_scene_init(AscScene *scene) { universe@21: if (scene->root != NULL) { universe@21: asc_error("Scene is already initialized."); universe@21: return; universe@21: } universe@37: universe@37: // zero everything, first universe@37: memset(scene, 0, sizeof(AscScene)); universe@37: universe@37: // default viewport is the entire viewport of the active window universe@37: scene->viewport.size = asc_context.active_window->dimensions; universe@37: universe@37: // create the root node universe@29: scene->root = asc_scene_node_empty(); universe@37: universe@37: // initialize the render groups universe@37: cx_array_initialize(scene->rg_none, 8); universe@37: cx_array_initialize(scene->rg_fonts, 8); universe@21: } universe@21: universe@21: void asc_scene_destroy(AscScene *scene) { universe@21: asc_scene_node_free(scene->root); universe@21: } universe@21: universe@29: void asc_scene_add(AscScene *scene, AscSceneNode *node) { universe@29: asc_scene_node_link(scene->root, node); universe@32: asc_node_update(node); universe@29: } universe@29: universe@37: #define asc_scene_draw_render_group(rg) \ universe@37: cx_for_n(i, rg##_size) { \ universe@37: rg[i].draw(rg[i].node); \ universe@37: } (void)0 universe@37: universe@37: void asc_scene_draw(AscScene *scene) { universe@37: // reset render groups universe@37: scene->rg_none_size = 0; universe@37: scene->rg_fonts_size = 0; universe@32: universe@32: // skip the root node deliberately, we know it's just the container universe@38: CxTreeIterator iter = asc_scene_node_iterator(scene->root, false); 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@33: // execute behaviors, first universe@33: AscBehaviorNode *behavior = node->behaviors; universe@33: while (behavior) { universe@33: behavior->func(node); universe@33: behavior = behavior->next; universe@33: } universe@33: universe@33: // check if geometry needs update universe@37: if (node->need_full_update) { universe@37: assert(node->update_func != NULL); universe@37: node->need_full_update = false; universe@32: node->update_func(node); universe@32: } universe@37: if (node->need_transform_update) { universe@37: assert(node->transform_update_func != NULL); universe@37: node->need_transform_update = false; universe@38: asc_transform_identity(node->local_transform); universe@38: asc_transform_copy(node->world_transform, node->parent->world_transform); universe@37: node->transform_update_func(node); universe@38: asc_mat4f_mulst(node->final_transform, node->local_transform, node->world_transform); 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@37: switch (node->render_group) { universe@37: case ASC_RENDER_GROUP_NONE: universe@37: cx_array_simple_add(scene->rg_none, entry); universe@37: break; universe@37: case ASC_RENDER_GROUP_FONTS: universe@37: cx_array_simple_add(scene->rg_fonts, entry); universe@37: break; universe@37: } universe@30: } universe@29: } universe@37: universe@37: // set the viewport (in OpenGL we need to invert the Y axis) universe@37: glViewport( universe@37: scene->viewport.pos.x, universe@37: -scene->viewport.pos.y, universe@37: scene->viewport.size.width, universe@37: scene->viewport.size.height universe@37: ); universe@37: universe@37: // ----------------------------------------- universe@37: // process the render groups for each camera universe@37: // ----------------------------------------- universe@37: cx_for_n(cam_id, ASC_SCENE_CAMERAS_MAX) { universe@37: // update camera parameters, first universe@37: AscCamera *camera = &scene->cameras[cam_id]; universe@37: if (camera->update == NULL) continue; universe@37: camera->update(camera); universe@37: universe@37: // for the NONE group, the draw func is expected to do everything universe@37: asc_scene_draw_render_group(scene->rg_none); universe@37: universe@37: // draw the FONTS group universe@37: // TODO: see if we can really always ignore the view matrix universe@37: glUseProgram(ASC_SHADER_FONT.base.id); universe@37: glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1, universe@37: GL_FALSE, camera->projection); universe@37: asc_scene_draw_render_group(scene->rg_fonts); 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@38: asc_transform_identity(node->local_transform); universe@38: asc_transform_identity(node->world_transform); universe@38: asc_transform_identity(node->final_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@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@33: AscBehaviorNode *asc_scene_add_behavior(AscSceneNode *node, asc_scene_update_func behavior) { universe@33: AscBehaviorNode *behavior_node = calloc(1, sizeof(AscBehaviorNode)); universe@33: behavior_node->func = behavior; universe@33: cx_tree_link( universe@33: node, universe@33: behavior_node, universe@33: offsetof(AscBehaviorNode, parent), universe@33: offsetof(AscSceneNode, behaviors), universe@33: offsetof(AscBehaviorNode, prev), universe@33: offsetof(AscBehaviorNode, next) universe@33: ); universe@33: return behavior_node; universe@33: } universe@33: universe@33: void asc_scene_remove_behavior(AscBehaviorNode *node) { universe@33: cx_tree_unlink( universe@33: node, universe@33: offsetof(AscBehaviorNode, parent), universe@33: offsetof(AscSceneNode, behaviors), universe@33: offsetof(AscBehaviorNode, prev), universe@33: offsetof(AscBehaviorNode, next) universe@33: ); universe@33: } universe@38: universe@38: void asc_node_update_transform(AscSceneNode *node) { universe@38: CxTreeIterator iter = asc_scene_node_iterator(node, false); universe@38: cx_foreach(AscSceneNode*, n, iter) { universe@38: n->need_transform_update = true; universe@38: } universe@38: }