Wed, 02 Jul 2025 23:21:17 +0200
resolve TODOs regarding input.h
a) mouse position must be integer, because it can be negative (though rarely)
b) we should not trade "access complexity" for space in the scancodes array
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * Copyright 2023 Mike Becker. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "ascension/error.h" #include "ascension/context.h" #include "ascension/scene.h" #include "ascension/behavior.h" #include "ascension/shader.h" #include <cx/tree.h> #include <cx/array_list.h> #include <GL/glew.h> #include <assert.h> void asc_scene_init_(AscScene *scene, struct asc_camera_init_args args) { if (scene->root != NULL) { asc_wprintf("Scene %"PRIxPTR" is already initialized - initialization skipped.", (uintptr_t) scene); return; } asc_camera_init_(&scene->camera, args); scene->root = asc_scene_node_empty(); for (unsigned i = 0 ; i < ASC_RENDER_GROUP_COUNT ; i++) { scene->internal.render_groups[i] = cxArrayListCreateSimple(CX_STORE_POINTERS, 32); } asc_dprintf("Initialized scene %"PRIxPTR, (uintptr_t) scene); } void asc_scene_destroy(AscScene *scene) { if (scene->root == NULL) return; for (unsigned i = 0 ; i < ASC_RENDER_GROUP_COUNT ; i++) { cxListFree(scene->internal.render_groups[i]); scene->internal.render_groups[i] = NULL; } asc_dprintf("Destroyed scene %"PRIxPTR, (uintptr_t) scene); asc_scene_node_free(scene->root); } void asc_scene_execute_behaviors(AscScene *scene) { CxTreeVisitor iter = cx_tree_visitor(scene->root, offsetof(AscSceneNode, children), offsetof(AscSceneNode, next) ); cx_foreach(AscSceneNode*, node, iter) { CxIterator behavior_iter = cxListIterator(node->behaviors); cx_foreach(AscBehavior*, behavior, behavior_iter) { asc_behavior_trigger(behavior); } } } static void asc_scene_draw2d( const AscScene *scene, const CxList *opaque, const CxList *blend ) { glEnable(GL_DEPTH_TEST); glClear(GL_DEPTH_BUFFER_BIT); const AscCamera *camera = asc_scene_camera(scene); // render opaque nodes from front to back CxIterator iter_opaque = cxListBackwardsIterator(opaque); // render nodes with alpha blending from back to front CxIterator iter_blend = cxListIterator(blend); // TODO: implement interleaving by depth // TODO: implement sorting by shader ID to reduce shader switches if (cxIteratorValid(iter_opaque)) { glDisable(GL_BLEND); cx_foreach(const AscSceneNode*, node, iter_opaque) { node->draw_func(camera, node); } } if (cxIteratorValid(iter_blend)) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); cx_foreach(const AscSceneNode*, node, iter_blend) { node->draw_func(camera, node); } } } void asc_scene_draw(AscScene *scene) { if (scene->root == NULL) return; // if the window resized, we must update the viewport if (asc_active_window->resized) { asc_vec2u window_size = asc_active_window->dimensions; if (scene->camera.viewport_update_func == NULL) { // this assumes the viewport was initialized with zeros! scene->camera.viewport.size = window_size; } else { scene->camera.viewport = scene->camera.viewport_update_func(window_size); } if (scene->camera.projection_update_func != NULL) { scene->camera.projection_update_func(&scene->camera, window_size); } } // reset render groups CxList **render_group = scene->internal.render_groups; for (unsigned i = 0 ; i < ASC_RENDER_GROUP_COUNT ; i++) { cxListClear(render_group[i]); } // update the scene graph and add nodes to their render groups CxTreeVisitor iter = cx_tree_visitor(scene->root, offsetof(AscSceneNode, children), offsetof(AscSceneNode, next) ); cx_foreach(AscSceneNode*, node, iter) { // skip hidden nodes (and all their children) if (asc_test_flag(node->flags, ASC_SCENE_NODE_HIDDEN)) { cxTreeVisitorContinue(iter); } // TODO: implement culling // check if geometry needs update asc_clear_flag(node->flags, ASC_SCENE_NODE_GRAPHICS_UPDATED | ASC_SCENE_NODE_TRANSFORM_UPDATED); if (asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_GRAPHICS)) { asc_set_flag(node->flags, ASC_SCENE_NODE_GRAPHICS_UPDATED); asc_clear_flag(node->flags, ASC_SCENE_NODE_UPDATE_GRAPHICS); assert(node->update_func != NULL); node->update_func(node); } if (asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) { asc_set_flag(node->flags, ASC_SCENE_NODE_TRANSFORM_UPDATED); asc_clear_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM); // Only recalculate from components if not using custom transform if (!asc_test_flag(node->flags, ASC_SCENE_NODE_CUSTOM_TRANSFORM)) { asc_transform_from_vec3f( node->transform, node->position, node->scale, node->rotation ); } asc_mat4f_mulst( node->world_transform, node->transform, node->parent->world_transform ); } // add to render group if (node->render_group >= 0) { cxListAdd(render_group[node->render_group], node); } } // set the viewport glViewport( scene->camera.viewport.pos.x, scene->camera.viewport.pos.y, scene->camera.viewport.size.width, scene->camera.viewport.size.height ); if (scene->camera.viewport_clear) { glScissor( scene->camera.viewport.pos.x, scene->camera.viewport.pos.y, scene->camera.viewport.size.width, scene->camera.viewport.size.height ); glEnable(GL_SCISSOR_TEST); const asc_col4f col = scene->camera.clear_color; glClearColor(col.red, col.green, col.blue, col.alpha); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); } // ------------------------- // process the render groups // ------------------------- // clear any previously active shader / camera combination asc_shader_use(NULL, NULL); // 2D Elements asc_scene_draw2d(scene, render_group[ASC_RENDER_GROUP_2D_OPAQUE], render_group[ASC_RENDER_GROUP_2D_BLEND] ); } void asc_scene_add_node(AscScene *scene, AscSceneNode *node) { asc_scene_node_link(scene->root, node); } void asc_scene_remove_node(AscSceneNode *node) { asc_scene_node_unlink(node); }