Thu, 18 Apr 2024 22:53:55 +0200
consistently refer to windows by ID - fixes #381
This change discovered that the font cache is completely broken. We created issue #387 for this.
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"
31 #include "ascension/utils.h"
33 #include <cx/linked_list.h>
34 #include <cx/array_list.h>
35 #include <cx/tree.h>
36 #include <cx/utils.h>
38 #include "ascension/shader.h"
39 #include <GL/glew.h>
41 #include <assert.h>
43 static CxTreeIterator asc_scene_node_iterator(
44 AscSceneNode *node,
45 bool visit_on_exit
46 ) {
47 return cx_tree_iterator(
48 node, visit_on_exit,
49 offsetof(AscSceneNode, children),
50 offsetof(AscSceneNode, next)
51 );
52 }
54 static CxTreeVisitor asc_scene_node_visitor(AscSceneNode *node) {
55 return cx_tree_visitor(node,
56 offsetof(AscSceneNode, children),
57 offsetof(AscSceneNode, next)
58 );
59 }
61 struct asc_render_group_entry {
62 asc_scene_draw_func draw;
63 AscSceneNode const *node;
64 };
66 #define asc_draw_render_group(iter) \
67 cx_foreach(struct asc_render_group_entry*, entry, iter) { \
68 entry->draw(entry->node); \
69 }
71 void asc_scene_draw(AscSceneNode *root, asc_recti viewport, AscCamera *camera) {
72 // create render groups
73 CxList *render_group[ASC_RENDER_GROUP_COUNT];
74 cx_for_n(i, ASC_RENDER_GROUP_COUNT) {
75 render_group[i] = cxArrayListCreateSimple(
76 sizeof(struct asc_render_group_entry), 32);
77 }
79 // skip the root node deliberately, we know it's just the container
80 CxTreeVisitor iter = asc_scene_node_visitor(root);
81 cxIteratorNext(iter);
83 // update the children and add them to the render groups
84 cx_foreach(AscSceneNode*, node, iter) {
85 node->depth = iter.depth;
87 // skip hidden nodes (and all their children)
88 if (asc_test_flag(node->flags, ASC_SCENE_NODE_HIDDEN)) {
89 cxTreeVisitorContinue(iter);
90 }
92 // execute behaviors, first
93 if (node->behaviors != NULL) {
94 CxIterator behavior_iter = cxListIterator(node->behaviors);
95 cx_foreach(asc_scene_update_func, behavior, behavior_iter) {
96 behavior(node);
97 }
98 }
100 // TODO: implement culling
102 // check if geometry needs update
103 if (asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_GRAPHICS)) {
104 assert(node->update_func != NULL);
105 asc_clear_flag(node->flags, ASC_SCENE_NODE_UPDATE_GRAPHICS);
106 node->update_func(node);
107 }
108 if (asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) {
109 asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM);
110 asc_transform_from_parts(
111 node->transform,
112 node->position,
113 node->scale,
114 node->rotation
115 );
116 asc_mat4f_mulst(
117 node->world_transform,
118 node->transform,
119 node->parent->world_transform
120 );
121 }
123 // add to render group
124 if (node->draw_func != NULL) {
125 struct asc_render_group_entry entry = {
126 node->draw_func, node
127 };
128 cxListAdd(render_group[node->render_group], &entry);
129 }
130 }
132 // set the viewport (in OpenGL we need to invert the Y axis)
133 glViewport(
134 viewport.pos.x,
135 -viewport.pos.y,
136 viewport.size.width,
137 viewport.size.height
138 );
140 // -------------------------
141 // process the render groups
142 // -------------------------
143 AscShaderProgram *shader;
144 CxIterator render_iter;
146 // 2D Elements
147 // ===========
148 glEnable(GL_DEPTH_TEST);
149 glClear(GL_DEPTH_BUFFER_BIT);
151 // Sprites
152 // -------
153 // TODO: implement view matrix for 2D worlds
154 shader = &asc_active_window->glctx.shader.sprite.base;
155 glUseProgram(shader->id);
156 glUniformMatrix4fv(shader->projection, 1,
157 GL_FALSE, camera->projection);
159 // render opaque sprites from front to back
160 glDisable(GL_BLEND);
161 render_iter = cxListBackwardsIterator(render_group[ASC_RENDER_GROUP_SPRITE_OPAQUE]);
162 asc_draw_render_group(render_iter);
164 // render sprites with alpha value from back to front
165 glEnable(GL_BLEND);
166 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
167 render_iter = cxListIterator(render_group[ASC_RENDER_GROUP_SPRITE_BLEND]);
168 asc_draw_render_group(render_iter);
170 // destroy render groups
171 cx_for_n(i, ASC_RENDER_GROUP_COUNT) {
172 cxListDestroy(render_group[i]);
173 }
174 }
176 AscSceneNode *asc_scene_node_empty(void) {
177 AscSceneNode *node = calloc(1, sizeof(AscSceneNode));
178 node->free_func = (asc_scene_free_func) free;
179 node->scale.x = node->scale.y = node->scale.z = 1;
180 asc_transform_identity(node->transform);
181 asc_transform_identity(node->world_transform);
182 return node;
183 }
185 void asc_scene_node_free(AscSceneNode *node) {
186 if (node == NULL) return;
188 // remove this node from its parent
189 asc_scene_node_unlink(node);
191 // free the entire subtree
192 CxTreeIterator iter = asc_scene_node_iterator(node, true);
193 cx_foreach(AscSceneNode*, child, iter) {
194 if (!iter.exiting) continue;
195 if (child->behaviors != NULL) {
196 cxListDestroy(child->behaviors);
197 }
198 if (child->free_func != NULL) {
199 child->free_func(child);
200 } else {
201 free(child);
202 }
203 }
204 }
206 void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node) {
207 cx_tree_link(
208 parent, node,
209 offsetof(AscSceneNode, parent),
210 offsetof(AscSceneNode, children),
211 offsetof(AscSceneNode, prev),
212 offsetof(AscSceneNode, next)
213 );
214 }
216 void asc_scene_node_unlink(AscSceneNode *node) {
217 cx_tree_unlink(
218 node,
219 offsetof(AscSceneNode, parent),
220 offsetof(AscSceneNode, children),
221 offsetof(AscSceneNode, prev),
222 offsetof(AscSceneNode, next)
223 );
224 }
226 void asc_scene_add_behavior(
227 AscSceneNode *node,
228 asc_scene_update_func behavior
229 ) {
230 if (node->behaviors == NULL) {
231 node->behaviors = cxLinkedListCreateSimple(CX_STORE_POINTERS);
232 }
233 cxListAdd(node->behaviors, behavior);
234 }
236 void asc_scene_remove_behavior(
237 AscSceneNode *node,
238 asc_scene_update_func behavior
239 ) {
240 if (node->behaviors != NULL) {
241 cxListFindRemove(node->behaviors, behavior);
242 }
243 }
245 void asc_node_update(AscSceneNode *node) {
246 asc_set_flag(node->flags, ASC_SCENE_NODE_UPDATE_GRAPHICS);
247 }
249 void asc_node_update_transform(AscSceneNode *node) {
250 // fast skip if node is already marked
251 if (asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) {
252 return;
253 }
255 CxTreeIterator iter = asc_scene_node_iterator(node, false);
256 cx_foreach(AscSceneNode*, n, iter) {
257 if (asc_test_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) {
258 cxTreeIteratorContinue(iter);
259 }
260 asc_set_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM);
261 }
262 }