Wed, 10 Apr 2024 19:29:09 +0200
remove check for alpha == zero because that will almost never happen
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 * Copyright 2023 Mike Becker. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 * POSSIBILITY OF SUCH DAMAGE.
26 */
28 #include "ascension/scene.h"
30 #include "ascension/context.h"
32 #include <cx/linked_list.h>
33 #include <cx/array_list.h>
34 #include <cx/tree.h>
35 #include <cx/utils.h>
37 #include "ascension/shader.h"
38 #include <GL/glew.h>
40 #include <assert.h>
42 static CxTreeIterator asc_scene_node_iterator(
43 AscSceneNode *node,
44 bool visit_on_exit
45 ) {
46 return cx_tree_iterator(
47 node, visit_on_exit,
48 offsetof(AscSceneNode, children),
49 offsetof(AscSceneNode, next)
50 );
51 }
53 static CxTreeVisitor asc_scene_node_visitor(AscSceneNode *node) {
54 return cx_tree_visitor(node,
55 offsetof(AscSceneNode, children),
56 offsetof(AscSceneNode, next)
57 );
58 }
60 struct asc_render_group_entry {
61 asc_scene_draw_func draw;
62 AscSceneNode const *node;
63 };
65 #define asc_draw_render_group(iter) \
66 cx_foreach(struct asc_render_group_entry*, entry, iter) { \
67 entry->draw(entry->node); \
68 }
70 void asc_scene_draw(AscSceneNode *root, asc_recti viewport, AscCamera *camera) {
71 // create render groups
72 CxList *render_group[ASC_RENDER_GROUP_COUNT];
73 cx_for_n(i, ASC_RENDER_GROUP_COUNT) {
74 render_group[i] = cxArrayListCreateSimple(
75 sizeof(struct asc_render_group_entry), 32);
76 }
78 // skip the root node deliberately, we know it's just the container
79 CxTreeVisitor iter = asc_scene_node_visitor(root);
80 cxIteratorNext(iter);
82 // update the children and add them to the render groups
83 cx_foreach(AscSceneNode*, node, iter) {
84 node->depth = iter.depth;
86 // execute behaviors, first
87 if (node->behaviors != NULL) {
88 CxIterator behavior_iter = cxListIterator(node->behaviors);
89 cx_foreach(asc_scene_update_func, behavior, behavior_iter) {
90 behavior(node);
91 }
92 }
94 // TODO: implement culling
95 // TODO: implement a hidden flag (requires UCX tree-continue function)
97 // check if geometry needs update
98 if (node->need_graphics_update) {
99 assert(node->update_func != NULL);
100 node->need_graphics_update = false;
101 node->update_func(node);
102 }
103 if (node->need_transform_update) {
104 node->need_transform_update = false;
105 asc_transform_from_parts(
106 node->transform,
107 node->position,
108 node->scale,
109 node->rotation
110 );
111 asc_mat4f_mulst(
112 node->world_transform,
113 node->transform,
114 node->parent->world_transform
115 );
116 }
118 // add to render group
119 if (node->draw_func != NULL) {
120 struct asc_render_group_entry entry = {
121 node->draw_func, node
122 };
123 cxListAdd(render_group[node->render_group], &entry);
124 }
125 }
127 // set the viewport (in OpenGL we need to invert the Y axis)
128 glViewport(
129 viewport.pos.x,
130 -viewport.pos.y,
131 viewport.size.width,
132 viewport.size.height
133 );
135 // -------------------------
136 // process the render groups
137 // -------------------------
138 AscShaderProgram *shader;
139 CxIterator render_iter;
141 // 2D Elements
142 // ===========
143 glEnable(GL_DEPTH_TEST);
144 glClear(GL_DEPTH_BUFFER_BIT);
146 // Sprites
147 // -------
148 // TODO: implement view matrix for 2D worlds
149 shader = &asc_context.active_window->glctx.shader.sprite.base;
150 glUseProgram(shader->id);
151 glUniformMatrix4fv(shader->projection, 1,
152 GL_FALSE, camera->projection);
154 // render opaque sprites from front to back
155 glDisable(GL_BLEND);
156 render_iter = cxListBackwardsIterator(render_group[ASC_RENDER_GROUP_SPRITE_OPAQUE]);
157 asc_draw_render_group(render_iter);
159 // render sprites with alpha value from back to front
160 glEnable(GL_BLEND);
161 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
162 render_iter = cxListIterator(render_group[ASC_RENDER_GROUP_SPRITE_BLEND]);
163 asc_draw_render_group(render_iter);
165 // destroy render groups
166 cx_for_n(i, ASC_RENDER_GROUP_COUNT) {
167 cxListDestroy(render_group[i]);
168 }
169 }
171 AscSceneNode *asc_scene_node_empty(void) {
172 AscSceneNode *node = calloc(1, sizeof(AscSceneNode));
173 node->free_func = (asc_scene_free_func) free;
174 node->scale.x = node->scale.y = node->scale.z = 1;
175 asc_transform_identity(node->transform);
176 asc_transform_identity(node->world_transform);
177 return node;
178 }
180 void asc_scene_node_free(AscSceneNode *node) {
181 if (node == NULL) return;
183 // remove this node from its parent
184 asc_scene_node_unlink(node);
186 // free the entire subtree
187 CxTreeIterator iter = asc_scene_node_iterator(node, true);
188 cx_foreach(AscSceneNode*, child, iter) {
189 if (!iter.exiting) continue;
190 if (child->behaviors != NULL) {
191 cxListDestroy(child->behaviors);
192 }
193 if (child->free_func != NULL) {
194 child->free_func(child);
195 } else {
196 free(child);
197 }
198 }
199 }
201 void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node) {
202 cx_tree_link(
203 parent, node,
204 offsetof(AscSceneNode, parent),
205 offsetof(AscSceneNode, children),
206 offsetof(AscSceneNode, prev),
207 offsetof(AscSceneNode, next)
208 );
209 }
211 void asc_scene_node_unlink(AscSceneNode *node) {
212 cx_tree_unlink(
213 node,
214 offsetof(AscSceneNode, parent),
215 offsetof(AscSceneNode, children),
216 offsetof(AscSceneNode, prev),
217 offsetof(AscSceneNode, next)
218 );
219 }
221 void asc_scene_add_behavior(
222 AscSceneNode *node,
223 asc_scene_update_func behavior
224 ) {
225 if (node->behaviors == NULL) {
226 node->behaviors = cxLinkedListCreateSimple(CX_STORE_POINTERS);
227 }
228 cxListAdd(node->behaviors, behavior);
229 }
231 void asc_scene_remove_behavior(
232 AscSceneNode *node,
233 asc_scene_update_func behavior
234 ) {
235 if (node->behaviors != NULL) {
236 cxListFindRemove(node->behaviors, behavior);
237 }
238 }
240 void asc_update_transform(AscSceneNode *node) {
241 if (node->need_transform_update) return;
243 CxTreeIterator iter = asc_scene_node_iterator(node, false);
244 cx_foreach(AscSceneNode*, n, iter) {
245 // TODO: break/continue when subtree is already marked for update
246 n->need_transform_update = true;
247 }
248 }