diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/btree.c | 850 | ||||
| -rw-r--r-- | src/engine.c | 686 | ||||
| -rw-r--r-- | src/fov.c | 97 | ||||
| -rw-r--r-- | src/hashmap.c | 5 | ||||
| -rw-r--r-- | src/logging.c | 80 | ||||
| -rw-r--r-- | src/memory.c | 62 | ||||
| -rw-r--r-- | src/rendering.c | 152 | ||||
| -rw-r--r-- | src/stack.c | 88 | ||||
| -rw-r--r-- | src/state.c | 91 | ||||
| -rw-r--r-- | src/ui_positioning.c | 877 | ||||
| -rw-r--r-- | src/ui_rendering.c | 295 | ||||
| -rw-r--r-- | src/utils.c | 85 | ||||
| -rw-r--r-- | src/vector.c | 28 |
13 files changed, 3396 insertions, 0 deletions
diff --git a/src/btree.c b/src/btree.c new file mode 100644 index 0000000..63c2513 --- /dev/null +++ b/src/btree.c @@ -0,0 +1,850 @@ +#include <engine/btree.h> + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sys/types.h> + +/* Definitions */ +typedef unsigned char byte; + +struct node { + ssize_t n; /* number of items/keys/elements */ + ssize_t c; /* number of children */ + byte *items; + struct node **children; +}; + +struct btree { + /* Memory stuffs */ + void *(*alloc)(size_t); + void (*dealloc)(void*); + + /* Size stuffs */ + size_t elem_size; + ssize_t degree; + + struct node *root; + + /* comparison */ + int (*cmp)(const void *a, const void *b); +}; + +struct btree_iter_t { + size_t head; + struct stack { + int pos; + struct node* node; + } stack[512]; + /* This heavily relies on the assumption that a tree never grows deeper than + * 512 nodes */ +}; + +/**********************/ +/* Node functionality */ +/**********************/ +#define \ +node_leaf(node) (node->children == NULL) + +#define \ +node_maxdegree(t) (2 * t - 1) + +#define \ +node_mindegree(t) (t - 1) + +#define \ +node_full(degree, t) (t->n >= 2 * degree - 1) + +/* Node memory */ + +/* `node_new` allocates a new leaf node, children should be added and allocated + * when the node is no longer a leaf node */ +struct node* node_new(const ssize_t degree, const size_t elem_size) { + const size_t max_items = 2 * degree; + struct node *retval = calloc(1, sizeof(struct node)); + + retval->n = 0; + retval->c = 0; + retval->items = calloc(max_items, elem_size); + retval->children = NULL; + + return retval; +} + +/* `node_transition` turns a leaf node into a non-leaf. Children are not + * allocated. + * returnvalue: `false` if we we're unable to allocate space for the new + * children. */ +bool node_transition(struct node *node, const ssize_t degree) { + const int max_children = 2 * degree + 1; + int c; + + if (!node_leaf(node)) { + perror("node_transition was called on an already non-leaf node?"); + return false; + } + + /* Allocate pointers for children */ + node->children = calloc(max_children, sizeof(struct node*)); + + if (node->children == NULL) { + perror("could not allocate space for children pointers"); + return false; + } + + /* Allocate children */ + for (c = 0; c < max_children; c++) { + node->children[c] = NULL; + } + + return true; +} + +void node_free(struct node **node, size_t elem_size, void (*dealloc)(void*)) { + if (*node == NULL) return; + + if (!node_leaf((*node))) { + ssize_t i; + for (i = 0; i < (*node)->c; i++) { + node_free(&((*node)->children[i]), elem_size, dealloc); + } + dealloc((*node)->children); + } + + dealloc((*node)->items); + (*node)->items = NULL; + + dealloc(*node); + *node = NULL; +} + + +/* `node_tree_split_child` splits a _full_ node (c = 2t-1 items) into two nodes + * with t-1 items each. + * The median key/item/element moves up to the original nodes parent, to signify + * the split. If the parent is full too, we need to split it before inserting + * the median key. + * This can potentially split full nodes all the way up throughout the tree. */ +/* Instead of waiting to find out wether we should split the nodes, we split the + * full nodes we encounter on the way down, including the leafs themselves. + * By doing this, we are assured that whenever we split a node, its parent has + * room for the median key. */ +void node_tree_split_child( + const ssize_t t, + const size_t elem_size, + struct node *nonfull, + ssize_t i) { + struct node *z = node_new(t, elem_size); + struct node *y = nonfull->children[i]; + ssize_t j; + + /* `z` should be a branching node if `y` is */ + if (!node_leaf(y)) { + node_transition(z, t); + } + + z->n = t - 1; + + /* Move last `t-1` items to new node `z` */ + /* TODO This can be done with one memcpy */ + for (j = 0; j < t-1; j++) { + const size_t offset_dst = elem_size * j; + const size_t offset_src = elem_size * (t+j); + memcpy((z->items) + offset_dst, (y->items) + offset_src, elem_size); + } + /* Set unused item-memory to zero? */ + + /* Move children t..2t, if applicable*/ + if (!node_leaf(y)) { + for (j = 0; j < t+1; j++) { + z->children[j] = y->children[j + t]; + } + y->c = t; + z->c = t; + } + + y->n = t - 1; + + /* Move children +1 */ + for (j = nonfull->n; j > i; j--) { + nonfull->children[j+1] = nonfull->children[j]; + } + + /* new child */ + nonfull->children[i+1] = z; + nonfull->c++; + + /* moving keys i..n + 1*/ + /* TODO This can be done with one memcpy */ + for (j = nonfull->n; j >= i; j--) { + const size_t offset = j * elem_size; + memcpy((nonfull->items) + offset + elem_size, + (nonfull->items) + offset, + elem_size); + } + + /* Lastly, copy the median element to nonfull-parent*/ + memcpy((nonfull->items) + i * elem_size, + (y->items) + (t-1) * elem_size, + elem_size); + + nonfull->n++; +} + +/* `node_child_merge`: Merges two children around the key at index `i` (k) + * by appending k to the left child (y) followed by + * appending the right child (z) to y + * + * `x`: The parent node of y and z + * `i`: Index of the item that acts as the new median of the merged node + * + * WARNING: THIS FUNCTION ASSUMES THAT `i` IS A VALID INDEX + */ +void node_child_merge( + struct node *x, + ssize_t i, + const size_t elem_size, void (*dealloc)(void*)) { + struct node* y = x->children[i ]; + struct node* z = x->children[i+1]; + int j = 0; + + /* append k to y */ + memcpy(y->items + (elem_size * y->n++), + x->items + (elem_size * i), + elem_size); + + /* append keys in z to y */ + memcpy(y->items + (elem_size * y->n), + z->items, + elem_size * z->n); + y->n += z->n; + + /* Move children from z to y */ + for (j = 0; j < z->c; j++) { + y->children[y->c + j] = z->children[j]; + } + y->c += z->c; + + /* Remove z from x */ + for (j = i+1; j < x->c; j++) { + x->children[j] = x->children[j+1]; + } + x->c--; + + /* remove k from x */ + /* TODO check if we need to use (x->n - 1 - i) instead */ + memmove(x->items + (elem_size * i), + x->items + (elem_size * (i+1)), + elem_size * (x->n - i)); + x->n--; + + dealloc(z); /* DO NOT USE THE RECURSIVE ONE AS CHILDREN WILL BE LOST!!! */ +} + +/* ASSUME i < x->c */ +void node_shift_left( + struct node *x, + ssize_t i, + const size_t elem_size) { + struct node* y = x->children[i ]; + struct node* z = x->children[i+1]; + byte *x_k = x->items + (elem_size * i); + + /* Append x.k[i] to y */ + memcpy(y->items + (elem_size * y->n++), + x_k, + elem_size); + + /* Move first element of z to x.k[i] */ + memcpy(x_k, + z->items, + elem_size); + + /* Shift z's items left */ + memmove(z->items, + z->items + elem_size, + elem_size * (z->n - 1)); + + if (!node_leaf(z)) { + ssize_t j; + /* append first child of z to y */ + y->children[y->c++] = z->children[0]; + + /* Shift z's children left */ + for (j = 0; j < z->c; j++) { + z->children[j] = z->children[j+1]; + } + z->c--; + } + + z->n--; +} + +void node_shift_right( + struct node *x, + ssize_t i, + const size_t elem_size) { + struct node* y = x->children[i ]; + struct node* z = x->children[i+1]; + byte *x_k = x->items + (elem_size * i); + + /* Shift z's items right */ + memmove(z->items + elem_size, + z->items, + elem_size * z->n); + + /* Prepend x.k[i] to z */ + memcpy(z->items, + x_k, + elem_size); + + /* Move last element of y to x.k[i] */ + memcpy(x_k, + y->items + (elem_size * --(y->n)), + elem_size); + + if (!node_leaf(z)) { + size_t j; + /* Shift z's children right */ + for (j = z->c; j > 0; j--) { + z->children[j] = z->children[j-1]; + } + z->c++; + + /* prepend last child of y to z */ + z->children[0] = y->children[--(y->c)]; + } + + z->n++; +} + +/* return: Returns the new root, if a split happens */ +void node_insert_nonfull( + struct node *root, + void *elem, + const ssize_t degree, + const size_t elem_size, + int (*cmp)(const void *a, const void *b)) { + + /* TODO check correctness */ + ssize_t i = root->n - 1; + + if (node_leaf(root)) { + size_t offset = elem_size * i; + while (i >= 0 && cmp(elem, root->items + offset) < 0) { + /* TODO This can be done with one memcpy */ + memcpy(root->items + offset + elem_size, + root->items + offset, + elem_size); + + i--; + offset = elem_size * i; + } + offset = elem_size * (++i); + memcpy(root->items + offset, elem, elem_size); + root->n++; + + } else { + size_t offset = elem_size * i; + struct node *nextchild = NULL; + while (i >= 0 && cmp(elem, root->items + offset) < 0) { + i--; + offset = elem_size * i; + } + i++; + nextchild = root->children[i]; + if (node_full(degree, nextchild)) { + /* TODO Check if the root has changed */ + node_tree_split_child(degree, elem_size, root, i); + if (cmp(elem, root->items + elem_size * i) > 0) { + nextchild = root->children[++i]; + } + } + node_insert_nonfull(nextchild, elem, degree, elem_size, cmp); + } +} + +/* Returns the new root, if a split occurs */ +struct node* node_insert( + struct node *root, + void *elem, + const ssize_t degree, + const size_t elem_size, + int (*cmp)(const void *a, const void *b)) { + + struct node *s = root; + + if (node_full(degree, root)) { + s = node_new(degree, elem_size); + if (s == NULL) { + fputs("BTree error: Failed to allocate new node for insertion!\n", stderr); + return NULL; + } + node_transition(s, degree); + s->children[s->c++] = root; + /* TODO Check if the root has changed */ + node_tree_split_child(degree, elem_size, s, 0); + node_insert_nonfull(s, elem, degree, elem_size, cmp); + } + else { + node_insert_nonfull(s, elem, degree, elem_size, cmp); + } + return s; +} + +void* node_search(struct node *x, + void *key, + int(*cmp)(const void *a, const void *b), + const size_t elem_size) { + /* We set to one, since we pre-emptively do a comparison with the assumption + * that there's already one in the items */ + ssize_t i = 0; + int last_cmp_res = 0; + + while (i < x->n + && (last_cmp_res = cmp(key, (const void*)(x->items + (i * elem_size)))) + > 0) { + i++; + } + + if ((ssize_t)i < x->n && last_cmp_res == 0) { + return (void*)(x->items + (i * elem_size)); + } else if (node_leaf(x)) { + return NULL; + } + + /* Assumption: ¬node_leaf(x) → x.children is allocated */ + return node_search(x->children[i], key, cmp, elem_size); +} + +int node_delete(struct node *x, + void *key, + int(*cmp)(const void *a, const void *b), + const ssize_t degree, + const size_t elem_size, void *(*alloc)(size_t), void (*dealloc)(void*)) { + /* Determine wether the key is in the node */ + int i = 0; /* Index of `k`, if found */ + int last_cmp_res = 0; + + while (i < x->n + && (last_cmp_res = cmp(key, (const void*)(x->items + (i * elem_size)))) + > 0) { + i++; + } + + if (last_cmp_res == 0) { + + if (node_leaf(x)) { + /* 1. k ϵ x && node_leaf(x) */ + /* Delete k from x */ + int j = i; + while (j + 1 < x->n) { + const size_t offset_dst = elem_size * j; + const size_t offset_src = elem_size * (j+1); + memcpy((x->items) + offset_dst, + (x->items) + offset_src, + elem_size); + j++; + } + x->n--; + return 1; + } else { + /* 2. k ϵ x && !node_leaf(x) */ + /* let i be the index of k in x */ + /* 2a: if size(child[i]) >= t; find the largest k' in child[i] */ + /* replace k with k' */ + if (x->children[i]->n >= degree) { + struct node* y = x->children[i]; + byte *kk = alloc(elem_size); + + /* Find the predecessor, k' of k in y */ + { + struct node* tmp = y; + while (!node_leaf(tmp)) { + tmp = tmp->children[tmp->n-1]; + } + + /* copy kk */ + memcpy(kk, tmp->items + elem_size * (tmp->n - 1), elem_size); + } + + /* Recursively delete kk from y */ + return node_delete(y, kk, cmp, degree, elem_size, alloc, dealloc); + + /* replace k with kk */ + memcpy(x->items + (elem_size * i), + kk, + elem_size); + + dealloc(kk); + + return 1; + + } else if (x->children[i+1]->n >= degree) { + struct node* z = x->children[i+1]; + byte *kk = alloc(elem_size); + + /* Find the successor, k' of k in z */ + { + struct node* tmp = z->children[0]; + while (!node_leaf(tmp)) { + tmp = tmp->children[0]; + } + + /* copy kk */ + memcpy(kk, tmp->items + elem_size * (tmp->n - 1), elem_size); + } + + /* Recursively delete kk from y */ + return node_delete(z, kk, cmp, degree, elem_size, alloc, dealloc); + + /* replace k with kk */ + memcpy(x->items + (elem_size * i), + kk, + elem_size); + + dealloc(kk); + + return 1; + } else { + /* Merge k and z into y */ + node_child_merge(x, i, elem_size, dealloc); + + /* recurse */ + return node_delete(x->children[i], key, cmp, degree, elem_size, alloc, dealloc); + } + } + } else if (node_leaf(x)) { + return 0; + } else { + /* 3. !(k ϵ x) */ + + /* if x is a leaf, then it is not in the tree */ + + struct node* y; + int ii; /* index of x.c[i] */ + + /* Determine x.c[i] that must contain k */ + /* if last cmp < 0, then the child must be in the left child of x.items[i]*/ + if (last_cmp_res < 0) ii = i; + /* Otherwise, it must be the very last child */ + else if (i < x->n) ii = i+1; + else ii = i; + + y = x->children[ii]; + + if (y->n < degree) { + /* we are left biased */ + if (ii > 0 && x->children[ii-1]->n >= degree) { + node_shift_right(x, ii-1, elem_size); + + } else if (ii < x->c - 1 && x->children[ii+1]->n >= degree) { + node_shift_left (x, ii, elem_size); + + } else { + /* We need to determine wether we merge left or right, if possible */ + if (ii > 0) { + node_child_merge(x, ii - 1, elem_size, dealloc); + y = x->children[ii - 1]; + } + else if (ii < x->c - 1) { + node_child_merge(x, ii, elem_size, dealloc); + } + else { + perror("Cannot merge!"); + } + } + + } + + return node_delete(y, key, cmp, degree, elem_size, alloc, dealloc); + } + return 0; +} + + +/***********************/ +/* Btree functionality */ +/***********************/ +struct btree* btree_new(size_t elem_size, + size_t t, + int(*cmp)(const void *a, const void *b)) { + return btree_new_with_allocator(elem_size, t, cmp, malloc, free); +} + +struct btree* btree_new_with_allocator(size_t elem_size, + size_t t, + int(*cmp)(const void *a, const void *b), + void *(*alloc)(size_t), + void (*dealloc)(void*)) { + struct btree *new_tree = alloc(sizeof(struct btree)); + + new_tree->alloc = alloc; + new_tree->dealloc = dealloc; + + new_tree->elem_size = elem_size; + new_tree->degree = t; + + new_tree->root = NULL; + + new_tree->cmp = cmp; + + return new_tree; +} + +void btree_free(struct btree **btree) { + node_free(&((*btree)->root), (*btree)->elem_size, (*btree)->dealloc); + (*btree)->dealloc(*btree); + *btree = NULL; +} + +void btree_insert(struct btree *btree, void *elem) { + if (btree == NULL) { + fputs("BTree error: Inserting into a NULL ptr!\n", stderr); + return; + } + if (elem == NULL) { + fputs("BTree error: Inserting NULL into a tree!\n", stderr); + return; + } + if (btree->root == NULL) { + btree->root = node_new(btree->degree, btree->elem_size); + if (btree->root == NULL) { + fputs("BTree error: Failed to create new root node!\n", stderr); + return; + } + node_insert(btree->root, + elem, + btree->degree, + btree->elem_size, + btree->cmp); + } + else { + btree->root = node_insert(btree->root, + elem, + btree->degree, + btree->elem_size, + btree->cmp); + } +} + +void* btree_search(struct btree *btree, void *elem) { + return node_search(btree->root, elem, btree->cmp, btree->elem_size); +} + +int btree_delete(struct btree *btree, void *elem) { + struct node *newroot = btree->root; + int res = node_delete(btree->root, elem, btree->cmp, btree->degree, btree->elem_size, btree->alloc, btree->dealloc); + if (newroot->n == 0) { + if (node_leaf(newroot)) return res; + /* shrink the tree */ + struct node *newroot_p = newroot->children[0]; + btree->dealloc(newroot); + btree->root = newroot_p; + } + return res; +} + +void node_print(struct node *root, const size_t elem_size, const int indent, void (*print_elem)(const void*)) { + ssize_t i; + int t; + + for (t = 0; t < indent - 1; t++) { fputs(" ┃ ", stdout); } + if (indent > 0) { fputs(" ┣┯", stdout); } + printf("printing node \x1b[33m%0lx\x1b[0m," + " c:%ld n:%ld\t\t" + "\x1b[30m%p\x1b[0m\n", + (unsigned long)((size_t)root % 0x10000), + root->c, + root->n, + (void*)root); + + if (node_leaf(root)) { + for (i = 0; i < root->n - 1; i++) { + const size_t ofst = i * elem_size; + for (t = 0; t < indent; t++) { fputs(" ┃├", stdout); } + print_elem(root->items + ofst); + } + for (t = 0; t < indent; t++) { fputs(" ┃└", stdout); } + print_elem(root->items + i * elem_size); + } else { + size_t ofst = 0; + for (i = 0; i < root->c - 1; i++) { + node_print(root->children[i], elem_size, indent + 1, print_elem); + for (t = 0; t < indent; t++) { fputs(" ┃ ", stdout); } + print_elem(root->items + ofst); + ofst += elem_size; + } + node_print(root->children[i], elem_size, indent + 1, print_elem); + } + +} + +void btree_print(struct btree *btree, void (*print_elem)(const void*)) { + printf("BTRee: degree:%ld\n", btree->degree); + if (btree->root == NULL) return; + node_print(btree->root, btree->elem_size, 0, print_elem); +} + +void* btree_first(struct btree *btree) { + struct node *root; + if (btree == NULL) return NULL; + root = btree->root; + + if (root == NULL) return NULL; + + while (!node_leaf(root)) root = root->children[0]; + + if (root->n == 0) return NULL; + return root->items; /* Return first element */ +} + +void* btree_last(struct btree *btree) { + struct node *root; + + if (btree == NULL) return NULL; + root = btree->root; + + if (root == NULL) return NULL; + + while (!node_leaf(root)) root = root->children[root->c]; + + if (root->n == 0) return NULL; + return root->items + btree->elem_size * (root->n - 1); /* Return first element */ +} + +size_t btree_height(struct btree *btree) { + struct node *root; + size_t height = 0; + + if (btree == NULL) return 0; + root = btree->root; + + if (root == NULL) return 0; + + while (!node_leaf(root)) { + root = root->children[0]; + height++; + } + + return height; +} + +size_t u32_pow(size_t base, size_t exponent) { + size_t res = 1; + while (exponent > 0) { + res *= base; + exponent--; + } + return res; +} + + +size_t btree_size(struct btree *btree) { + return u32_pow(2 * btree->degree, btree_height(btree)) - 1; +} + + +struct btree_iter_t* btree_iter_t_new(struct btree *tree) { + struct btree_iter_t *iter = NULL; + + if (tree == NULL) return NULL; + + iter = (struct btree_iter_t*)tree->alloc(sizeof(struct btree_iter_t)); + + if (tree != NULL) { + iter->head = 0; + memset(iter->stack, 0, 512 * sizeof(struct node*)); + + iter->stack[iter->head].pos = 0; + iter->stack[iter->head].node = tree->root; + } else { + perror("Cannot instantiate iterator from null-pointer tree"); + } + return iter; +} + + +void btree_iter_t_reset(struct btree *tree, struct btree_iter_t** it) { + (*it)->head = 0; + + (*it)->stack[0].pos = 0; + (*it)->stack[0].node = tree->root; +} + + +void* btree_iter(struct btree *tree, struct btree_iter_t *iter) { + register int pos = 0; + register ssize_t head = 0; + register ssize_t n = 0; + + if (iter->stack[head].node == NULL) return NULL; + + head = iter->head; + pos = iter->stack[head].pos; + n = iter->stack[head].node->n; + +#define BTREE_ITER_POP(it) { \ + iter->stack[head].pos = 0; \ + iter->stack[head].node = NULL; \ + iter->head--; head--; \ + iter->stack[head].pos++; \ + \ + pos = iter->stack[head].pos; \ + n = iter->stack[head].node->n; \ +} + + /* Check if we have reached the end of a node. + * Take note of the difference of inequality in the if statement and + * following while */ + + /* Leafs are a special case, as, if the only node is the root node, we might + * want to exit */ + if (node_leaf(iter->stack[iter->head].node) && pos >= 2 * n) { + if (head == 0) return NULL; + + /* Pop, if so */ + else BTREE_ITER_POP(iter); + } + + /* Otherwise, pop while we have reached the end of a node */ + while (pos > 2 * n) { + if (head == 0) return NULL; + + /* Pop, if so */ + else BTREE_ITER_POP(iter); + } + +#undef BTREE_ITER_POP + + /* On evens, we decent into children */ + if (!node_leaf(iter->stack[head].node)) { + if (pos % 2 == 0) { + /* push child node onto iter->stack */ + iter->stack[head + 1].pos = 0; + iter->stack[head + 1].node = iter->stack[head].node->children[pos / 2]; + iter->head++; head++; + + /* Decent all the way to the left, if pos == 0 */ + while (!node_leaf(iter->stack[iter->head].node)) { + iter->stack[head + 1].pos = 0; + iter->stack[head + 1].node = iter->stack[head].node->children[0]; + iter->head++; head++; + } + } + } + + /* Finally, update index and return a value */ + if (node_leaf(iter->stack[head].node)) { + iter->stack[head].pos += 2; + pos = iter->stack[head].pos; + } + else { + iter->stack[head].pos++; + pos = iter->stack[head].pos; + } + + return iter->stack[head].node->items + tree->elem_size * ( (pos - 1) / 2 ); +} diff --git a/src/engine.c b/src/engine.c new file mode 100644 index 0000000..fa528c0 --- /dev/null +++ b/src/engine.c @@ -0,0 +1,686 @@ +#include <stdlib.h> + +#include <SDL2/SDL.h> +#include <SDL2/SDL_image.h> +#include <SDL2/SDL_ttf.h> + +#if defined (_WIN32) || defined (__WIN32__) || defined (WIN32) + /* include winapi */ +#elif defined (__APPLE__) + /* mac includes */ +#elif defined (__linux) || defined (__linux__) || defined (linux) + +#include <unistd.h> + +#endif + + +#define ENGINE_INTERNALS +#include <engine/engine.h> +#include <engine/btree.h> +#include <engine/hashmap.h> +#include <engine/list.h> + +#include <engine/state.h> +//#include <states/titlescreen.h> +//#include <states/gameover.h> + +#define DEFAULT_NUM_PROCS 8 + +#ifdef BENCHMARK +#define BENCHEXPR(timevar, expr) { \ + u32 t = SDL_GetTicks(); \ + expr \ + timevar += SDL_GetTicks() - t; \ +} + +extern i32 drawcall_len; + +#else +#define BENCHEXPR(timevar, expr) expr +#endif + +i32 bindings_cmp(const Keybinding *a, const Keybinding *b) { + return a->modifiers ^ b->modifiers; +} + +i32 binding_to_int(const Keybinding *b) { + return b->keycode; +} + +/* Defines (struct) List_Keybinding */ +/* Defines (struct) hashmap_Keybinding, + * and hashmap_Keybinding_lookup, and hashmap_Keybinding_insert */ +DEFINE_HASHMAP(Keybinding, 64, bindings_cmp, binding_to_int) + +static u64 FPS_CAP = 50; +Platform *GLOBAL_PLATFORM = NULL; + +i32 nproc(void) { + return SDL_GetCPUCount(); +} + +i32 cmp_int(const void *a, const void *b) { + const i32 *x = a; + const i32 *y = b; + + return *x - *y; +} + +v2_i32 get_canvas_size(SDL_Renderer *renderer) { + v2_i32 realsize; + SDL_GetRendererOutputSize(renderer, &(realsize.x), &(realsize.y)); + + /* Set logical render size */ + return realsize; +} + +Texture *load_texture(SDL_Renderer *render, const TextureSpec *ts) { + SDL_Texture* new_texture = NULL; + SDL_Surface* loaded_surface = NULL; + Texture *t = NULL; + + if (ts == NULL) { + ERROR("Invalid TextureSpec\n"); + return NULL; + } + + loaded_surface = IMG_Load(ts->path); + if (loaded_surface == NULL) { + ERROR("Unable to load image \"%s\"!\n", ts->path); + ERROR("SDL_image Error: %s\n", IMG_GetError()); + return NULL; + } + + const i32 tw = loaded_surface->w / ts->width; + + SDL_SetColorKey(loaded_surface, SDL_TRUE, + SDL_MapRGB(loaded_surface->format, 0xFF, 0x00, 0xFF)); + + /*Create texture from surface pixels */ + new_texture = SDL_CreateTextureFromSurface(render, loaded_surface); + if (new_texture == NULL) { + ERROR("Unable to create texture from \"%s\"!\n", ts->path); + ERROR("SDL Error: %s\n", SDL_GetError()); + } + + /*Get rid of old loaded surface */ + SDL_FreeSurface(loaded_surface); + + t = (Texture*)malloc(sizeof(Texture)); + t->texture = new_texture; + /* Assigning const value */ + *(i32*)&t->tilesize = tw; + *(i32*)&t->width = ts->width; + *(i32*)&t->height = ts->height; + + return t; +} + +void engine_update_window(Window *w, SDL_WindowEvent *e) { + switch (e->event) { + case SDL_WINDOWEVENT_NONE: + case SDL_WINDOWEVENT_SHOWN: + case SDL_WINDOWEVENT_HIDDEN: + case SDL_WINDOWEVENT_EXPOSED: + case SDL_WINDOWEVENT_MOVED: + break; + case SDL_WINDOWEVENT_RESIZED: + w->windowsize = get_canvas_size(w->renderer); + LOG("Resized window to %dx%d", w->windowsize.x, w->windowsize.y); + ui_resolve_constraints(); + if (w->game_w != NULL && w->game_h != NULL) { + *w->game_h = w->windowsize.x; + *w->game_w = w->windowsize.y; + } + break; + case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_MINIMIZED: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + case SDL_WINDOWEVENT_ENTER: + case SDL_WINDOWEVENT_LEAVE: + case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_WINDOWEVENT_FOCUS_LOST: + case SDL_WINDOWEVENT_CLOSE: + case SDL_WINDOWEVENT_TAKE_FOCUS: + case SDL_WINDOWEVENT_HIT_TEST: + break; + default: + WARN("Unhandled window event 0x%04x", (i32)e->event); + break; + } + return; +} + +Platform *engine_init( + const char *windowtitle, + v2_i32 windowsize, + const f32 render_scale, + const u32 flags, + const usize initial_memory, + const FontSpec *fonts[], + const TextureSpec *textures[]) { + +#ifdef BENCHMARK + u32 init_start = SDL_GetTicks(); +#endif + +#if defined (__linux) || defined (__linux__) || defined (linux) + { + pid_t pid = getpid(); + INFO("Starting with pid %lu", pid); + } +#endif + + Platform *p = (Platform*)malloc(sizeof(Platform)); + Window *w = (Window*)malloc(sizeof(Window)); + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + + /* initialize resources */ + struct Resources *resources = (struct Resources*)malloc(sizeof(struct Resources)); + resources->textures_len = 0; + resources->fonts_len = 0; + resources->texturepaths_len = 0; + resources->fontpaths_len = 0; + resources->texture_paths = NULL; + resources->font_paths = NULL; + resources->textures = NULL; + resources->fonts = NULL; + + + { /* Init subsystems */ + INFO_("initializing sdl..."); + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + ERROR("failed to initialize sdl: %s\n", SDL_GetError()); + exit(EXIT_FAILURE); + } else printf("ok\n"); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + + INFO_("creating window..."); + window = SDL_CreateWindow(windowtitle, + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + windowsize.x, windowsize.y, + SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS | flags); + + if (window == NULL) { + ERROR("failed to create window: %s\n", SDL_GetError()); + exit(EXIT_FAILURE); + } else printf("ok\n"); + + INFO_("creating renderer..."); + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + if (renderer == NULL) { + ERROR("failed to create renderer: %s\n", SDL_GetError()); + exit(EXIT_FAILURE); + } else printf("ok\n"); + + INFO_("initializing sdl_image..."); + if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG) { + ERROR("failed to initialize sdl_image png support\n"); + exit(EXIT_FAILURE); + } else printf("ok\n"); + + INFO_("initializing sdl_ttf..."); + if (TTF_Init() == -1) { + ERROR("failed to initialize sdl_ttf: %s\n", TTF_GetError()); + exit(EXIT_FAILURE); + } else printf("ok\n"); + } + + + { /* Resource loading */ + + /* Count resources */ + usize n_textures = 0; + usize n_fonts = 0; + + if (textures != NULL) while (textures[n_textures] != NULL) n_textures++; + if (fonts != NULL) while (fonts[n_fonts] != NULL) n_fonts++; + + INFO("Number of textures: "TERM_COLOR_YELLOW"%d"TERM_COLOR_RESET, n_textures); + INFO("Number of fonts: "TERM_COLOR_YELLOW"%d"TERM_COLOR_RESET, n_fonts); + + + /* Save the textures and fonts, if we should need to reload them later */ + resources->texture_paths = (TextureSpec**)textures; + resources->font_paths = (FontSpec**)fonts; + + /* Allocate memory for textures and fonts */ + resources->textures = (Texture**)malloc(sizeof(Texture*) * n_textures); + resources->fonts = (TTF_Font**)malloc(sizeof(TTF_Font*) * n_fonts); + resources->textures_size = n_textures; + + for (usize i = 0; i < n_textures; i++) resources->textures[i] = NULL; + for (usize i = 0; i < n_fonts; i++) resources->fonts[i] = NULL; + + /* Load textures */ + for (usize i = 0; i < n_textures; i++) { + Texture *t = NULL; + INFO_("loading texture \"" + TERM_COLOR_YELLOW"%s"TERM_COLOR_RESET + "\"...", textures[i]->path); + + t = load_texture(renderer, textures[i]); + if (t == NULL) { + puts(""); + ERROR("failed to load texture\n"); + exit(EXIT_FAILURE); + } + + if (t->tilesize < 8) { + puts(""); + ERROR("texture too small!\n"); + exit(EXIT_FAILURE); + } + + if (t->texture == NULL) { + puts(""); + ERROR("failed to load texture\n"); + } else { + printf("ok\n"); + resources->textures[i] = t; + resources->textures_len++; + } + } + + /* Load fonts */ + for (usize i = 0; i < n_fonts; i++) { + INFO_("loading font \"" + TERM_COLOR_YELLOW"%s"TERM_COLOR_RESET + "\"...", fonts[i]->font_path); + + TTF_Font *font = TTF_OpenFont(fonts[i]->font_path + , fonts[i]->ptsize); + if (!font) { + ERROR("failed to load font: %s\n", TTF_GetError()); + } else { + printf("ok\n"); + resources->fonts[i] = font; + resources->fonts_len++; + } + } + + if (resources->textures_len != n_textures) { + WARN("Done. %d/%d textures loaded.", resources->textures_len, n_textures); + } else { + INFO("Done. All %d textures loaded.", n_textures); + } + + if (resources->fonts_len != n_fonts) { + WARN("Done. %d/%d fonts loaded.", resources->fonts_len, n_fonts); + } else { + INFO("Done. All %d fonts loaded.", n_fonts); + } + + resources->texturepaths_len = resources->textures_len; + resources->fontpaths_len = resources->fonts_len; + + } + + + { /* Adjust window and such */ + /* Set actual windowsize, which might be forced by OS */ + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + INFO("Adjusting window size..."); + windowsize = get_canvas_size(renderer); + + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + + INFO("Windowsize: <%d,%d>", windowsize.x, windowsize.y); + } + + w->renderer = renderer; + w->window = window; + w->render_scale = render_scale; + w->windowsize = windowsize; + w->game_w = NULL; + w->game_h = NULL; + + p->data = (void*)resources; + p->data_len = sizeof(struct Resources); + p->window = w; + p->quit = false; + + p->frame = 0; + p->fps_target = 60; + + p->mem = memory_new(initial_memory); + + /* Getting the mouse coords now resolves the issue where a click "isn't + * registered" when the mouse isn't moved before the user clicks */ + SDL_GetMouseState(&p->mouse_pos.x, &p->mouse_pos.y); + + p->mousedown = (v2_i32){-1, -1}; + p->mouseup = (v2_i32){-1, -1}; + + p->mouse_lclick = false; + p->mouse_rclick = false; + + p->camera_x = 0; + p->camera_y = 0; + + p->edit_text = NULL; + p->edit_pos = 0; + + p->bindings = NULL; + +#ifdef BENCHMARK + u32 init_stop = SDL_GetTicks(); + INFO("Initialization took %dms", init_stop - init_start); +#endif + + INFO("Available cores: %d", nproc()); + + GLOBAL_PLATFORM = p; + + return p; +} + +i32 engine_run(Platform *p, StateType initial_state) { + if (p == NULL) { + ERROR("Platform is uninitialized.\n"); + INFO("initialize with `engine_init`"); + return -1; + } + + memory* mem = p->mem; + + StateType state = initial_state; + + { + u32 state_init_time = SDL_GetTicks(); + State_init(state, mem); + INFO("Initializing state \"%s\" took %ldms", StateTypeStr[state], SDL_GetTicks() - state_init_time); + } + + u32 time = SDL_GetTicks(); + + // Update ticks + u64 ticks = 0; + + /* Profiling values */ +#ifdef BENCHMARK + u64 profile_tick_counter = 0; + //u64 profile_slack = 0; + u64 profile_rendering = 0; + u64 profile_gameloop = 0; + u64 profile_input = 0; + u64 profile_input_handling = 0; + u64 profile_num_drawcalls = 0; + u32 profile_interval_timer = time; + const u32 profile_interval_ms = 5000; + const f32 profile_interval_scale = (f32)(profile_interval_ms) / 100.0f; +#endif + + const f64 frame_interval = 1000.0 / FPS_CAP; + + StateType (*update_func)(void*) = State_updateFunc(state); + + /* Main loop */ + do { + const u32 now = SDL_GetTicks(); + const u64 dt = now - time; + time = now; + /* Wait frame_interval */ + if (dt < frame_interval) { +#ifndef BENCHMARK + SDL_Delay(frame_interval - dt); + +#else + /* We want to know how much time is spend sleeping */ + //profile_slack += frame_interval - dt; +#endif + } + +#ifdef BENCHMARK + if (time - profile_interval_timer > profile_interval_ms) { + /* Ticks/frames since last measurement */ + u32 fps = (ticks - profile_tick_counter) / profile_interval_scale; + u64 drawcalls = profile_num_drawcalls / profile_interval_scale / fps; + + u32 sum = + + profile_rendering + //+ profile_slack + + profile_input + + profile_input_handling + + profile_gameloop + ; + + + /* Log fps and slack percentage */ + LOG("fps:%d\t" + "rendering:%.2f%%\t" + //"slack:%.2f%%\t" + "input:%.2f%% (%.2f%%)\t" + "gameloop:%.2f%%\t" + "unaccounted:%llu / %llu ms\t" + "avg drawcalls:%llu", + fps, + 100.0f * (f32)profile_rendering / (f32)sum, + //100.0f * (f32)profile_slack / (f32)sum, + 100.0f * (f32)profile_input / (f32)sum, + 100.0f * (f32)profile_input_handling / (f32)sum, + 100.0f * (f32)profile_gameloop / (f32)sum, + time - profile_interval_timer - sum, sum, + drawcalls); + /* Reset values */ + profile_tick_counter = ticks; + profile_interval_timer = time; + //profile_slack = 0; + profile_rendering = 0; + profile_gameloop = 0; + profile_input = 0; + profile_input_handling = 0; + profile_num_drawcalls = 0; + } +#endif + + /* Events */ + BENCHEXPR(profile_input, { + + if (GLOBAL_PLATFORM->mouse_lclick) { + GLOBAL_PLATFORM->mouseup.x = -1; + GLOBAL_PLATFORM->mouseup.y = -1; + GLOBAL_PLATFORM->mousedown.x = -1; + GLOBAL_PLATFORM->mousedown.y = -1; + GLOBAL_PLATFORM->mouse_lclick = false; + } + if (GLOBAL_PLATFORM->mouse_rclick) { + GLOBAL_PLATFORM->mouse_rclick = false; + } + + /* Window events */ + SDL_Event e[8]; + i32 num_events; + SDL_PumpEvents(); + while ((num_events = SDL_PeepEvents(e, 8, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_SYSWMEVENT)) > 0) { + for (i32 i = 0; i < num_events; i++) { + switch (e[i].type) { + case SDL_QUIT: + state = STATE_quit; + break; + case SDL_WINDOWEVENT: + engine_update_window(p->window, &e[i].window); + break; + default: + WARN("Unhandled event 0x%04x", (i32)e[i].type); + } + } + } + + /* Mouse events */ + while ((num_events = SDL_PeepEvents(e, 8, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEWHEEL)) > 0) { + for (i32 i = 0; i < num_events; i++) { + switch (e[i].type) { + case SDL_MOUSEWHEEL: break; + case SDL_MOUSEMOTION: + { + SDL_MouseMotionEvent m = e[i].motion; + /* In case of a first-person game, use xrel and yrel */ + GLOBAL_PLATFORM->mouse_pos.x = m.x; + GLOBAL_PLATFORM->mouse_pos.y = m.y; + } + break; + case SDL_MOUSEBUTTONUP: + { + switch (e[i].button.button) { + case SDL_BUTTON_LEFT: + GLOBAL_PLATFORM->mouseup = GLOBAL_PLATFORM->mouse_pos; + + GLOBAL_PLATFORM->mouse_lclick = true; + case SDL_BUTTON_RIGHT: + break; + default: + break; + } + } + break; + case SDL_MOUSEBUTTONDOWN: + switch (e[i].button.button) { + case SDL_BUTTON_LEFT: + GLOBAL_PLATFORM->mousedown = GLOBAL_PLATFORM->mouse_pos; + break; + case SDL_BUTTON_RIGHT: + break; + default: + break; + } + break; + default: + WARN("Unhandled mouse event 0x%04x", (i32)e[i].type); + break; + } + } + } + + BENCHEXPR(profile_input_handling, { + while ((num_events = SDL_PeepEvents(e, 8, SDL_GETEVENT, SDL_KEYDOWN, SDL_KEYUP)) > 0) { + for (i32 i = 0; i < num_events; i++) { + switch (e[i].type) { + case SDL_KEYDOWN: { + Keybinding lookupkey = ((Keybinding){ + .keycode = e[i].key.keysym.sym, + .modifiers = e[i].key.keysym.mod + }); + Keybinding *kb = hashmap_Keybinding_lookup(GLOBAL_PLATFORM->bindings, &lookupkey); + if (kb != NULL && kb->action != NULL) kb->action(mem->data); + } + break; + case SDL_KEYUP: + break; + default: + WARN("Unhandled mouse event 0x%04x", (i32)e[i].type); + break; + } + } + } + }); + }); + + /* update */ + StateType next_state; + BENCHEXPR(profile_gameloop, {next_state = update_func((void*)(mem->data));} );//State_update(state, mem);}); + + if (next_state != STATE_null) { + if (next_state == STATE_quit) break; + + drawcall_reset(); + + engine_window_resize_pointers_reset(); + State_free(state, mem); + memory_clear(mem); + + GLOBAL_PLATFORM->bindings = NULL; + + state = next_state; + update_func = State_updateFunc(state); +#ifdef BENCHMARK + { + u32 t = SDL_GetTicks(); + State_init(state, mem); + LOG("Initializing %s took %dms", StateTypeStr[state], SDL_GetTicks() - t); + } +#else + State_init(state, mem); +#endif + } else { +#ifdef BENCHMARK + profile_num_drawcalls += drawcall_len; +#endif + render_begin(p->window); + BENCHEXPR(profile_rendering, {render_present(p->window);}) + } + + + ticks++; + } while (state != STATE_quit); + + return 0; +} + + +void stop(Platform *p) { + if (p == NULL) return; + + { /* Deallocate resources */ + struct Resources *r = (struct Resources*)p->data; + if (r != NULL) { + /* Destroy textures */ + for (usize i = 0; i < r->textures_len; i++) { + if (r->textures[i] != NULL) { + SDL_DestroyTexture(r->textures[i]->texture); + r->textures[i] = NULL; + } + } + free(r->textures); + + /* Destroy Fonts */ + for (usize i = 0; i < r->fonts_len; i++) { + if (r->fonts[i] != NULL) { + TTF_CloseFont(r->fonts[i]); + r->fonts[i] = NULL; + } + } + free(r->fonts); + } + } + + { /* Deallocate window */ + Window *w = p->window; + if (w != NULL) { + if (w->window != NULL) { SDL_DestroyWindow(w->window); w->window = NULL; } + if (w->renderer != NULL) { SDL_DestroyRenderer(w->renderer); w->renderer = NULL; } + } + } + + TTF_Quit(); + IMG_Quit(); + SDL_Quit(); +} + +void engine_fps_max(u64 cap) { FPS_CAP = cap; } + +void engine_bindkey(i32 key, void (*action)(void*)) { + /* TODO: Make a hashmap ++ linked list */ + //if (key >= 512) { + // ERROR("Key value too high! (got %d)\n", key); + //} + if (GLOBAL_PLATFORM->bindings == NULL) { + //GLOBAL_PLATFORM->bindings = memory_allocate(GLOBAL_PLATFORM->mem, 512 * sizeof(Keybinding)); + GLOBAL_PLATFORM->bindings = memory_allocate(GLOBAL_PLATFORM->mem, sizeof(hashmap_Keybinding)); + } + + Keybinding kb = (Keybinding){.keycode = key, .action = action}; + hashmap_Keybinding_insert(GLOBAL_PLATFORM->mem, GLOBAL_PLATFORM->bindings, &kb); +} + + +u32 get_time(void) {return SDL_GetTicks();} +v2_i32 get_windowsize(void) {return GLOBAL_PLATFORM->window->windowsize;} +v2_i32 *get_mousepos(void) { return &GLOBAL_PLATFORM->mouse_pos; } diff --git a/src/fov.c b/src/fov.c new file mode 100644 index 0000000..cdc7dc8 --- /dev/null +++ b/src/fov.c @@ -0,0 +1,97 @@ +#include <stdint.h> +#include <math.h> +#include <engine/fov.h> +#include <engine/utils.h> + +void fov_shadowcast_rec(const void *map, const v2_i32 mapsize, + bool (*visblocking)(const void*), i32 *lightmap, + const i32 range, v2_i32 src, + const i32 row, + f32 start, const f32 end, + const i8 xx, const i8 xy, const i8 yx, const i8 yy) { + + if (start < end) return; + + const i32 range_2 = range * range; + f32 new_start = start; + + for (i32 i = row; i <= range; i++) { + i32 dx = (-1 * i) - 1; + i32 dy = -1 * i; + + bool blocked = false; + + while (dx <= 0) { + dx += 1; + + const i32 mapx = src.x + dx * xx + dy * xy; + const i32 mapy = src.y + dx * yx + dy * yy; + + const f32 slope_l = (((f32)dx) - 0.5f) / (((f32)dy) + 0.5f); + const f32 slope_r = (((f32)dx) + 0.5f) / (((f32)dy) - 0.5f); + + if (start < slope_r) continue; + if (end > slope_l) break; + + if (dx * dx + dy * dy < range_2) { + /* set as visible */ + if (mapx >= 0 && mapx < (long)mapsize.x + && mapy >= 0 && mapy < (long)mapsize.y) { + // TODO: Calculate proper dist from source + f32 x_2 = (src.x - mapx) * (src.x - mapx); + f32 y_2 = (src.y - mapy) * (src.y - mapy); + lightmap[mapy*mapsize.x + mapx] = MAX(lightmap[mapy*mapsize.x + mapx], range - sqrt((f32)(x_2 + y_2))); + } + } + + /* sizeof(i32) is the size of enums */ + /* -- unless the compiler doesn't follow standard behaviour */ + const bool is_blocked = visblocking( + (void*)((u64)map + + sizeof(i32) /* ~ enum size */ + * (mapsize.x * mapy + mapx) /* index */ + )); + + if (blocked) { + if (!is_blocked) { + new_start = slope_r; + } else { + blocked = false; + start = new_start; + } + } else if (!is_blocked && i < range) { + blocked = true; + fov_shadowcast_rec(map, mapsize, visblocking, lightmap, range, src, i+1, start, slope_l, xx, xy, yx, yy); + new_start = slope_r; + } + } + + if (blocked) break; + } +} + +/* http://www.roguebasin.com/index.php?title=FOV_using_recursive_shadowcasting */ +void fov_shadowcast(const void *map, const v2_i32 mapsize, + bool (*visblocking)(const void*), i32 *lightmap, + const i32 range, const v2_i32 src) { + + const i8 m[4][8] = { + {1, 0, 0, -1, -1, 0, 0, 1}, + {0, 1, -1, 0, 0, -1, 1, 0}, + {0, 1, 1, 0, 0, -1, -1, 0}, + {1, 0, 0, 1, -1, 0, 0, -1}, + }; + + for (i32 oct = 0; oct < 8; oct++) { + fov_shadowcast_rec( + map, mapsize, visblocking, lightmap, range, src, + 1, 1.0, 0.0, + m[0][oct], + m[1][oct], + m[2][oct], + m[3][oct]); + } + + /* The center is the most lit square */ + lightmap[src.y * mapsize.x + src.x] = range; +} diff --git a/src/hashmap.c b/src/hashmap.c new file mode 100644 index 0000000..edcf9cb --- /dev/null +++ b/src/hashmap.c @@ -0,0 +1,5 @@ +#include <engine/hashmap.h> + +i32 lolhash(const usize s, i32 v) { + return v % s; +} diff --git a/src/logging.c b/src/logging.c new file mode 100644 index 0000000..2ca5368 --- /dev/null +++ b/src/logging.c @@ -0,0 +1,80 @@ +#include <stdlib.h> +#include <engine/logging.h> + +char *itoa(i32 x) { + const i32 size = (((i32)ceil(log10((f64)x)))+1) * sizeof(char); + char *retval = (char*)malloc(size); + if (retval == NULL) { + perror("Failed to allocate memory for itoa"); + exit(EXIT_FAILURE); + } + sprintf(retval, "%d", x); + return retval; +} + +void _log(FILE *stream, const char *prefix, const char *fmt, va_list ap) { + if (stream == NULL) { + fprintf(stderr, "_log got NULL in stream argument\n"); + exit(EXIT_FAILURE); + } + fputs(prefix, stream); + vfprintf(stream, fmt, ap); +} + +void LOG(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + _log(stdout, "[" TERM_COLOR_BLUE "LOG" TERM_COLOR_RESET "] ", fmt, ap); + va_end(ap); + puts(""); +} + +void INFO_(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + _log(stdout, "[" TERM_COLOR_GREEN "INFO" TERM_COLOR_RESET "] ", fmt, ap); + va_end(ap); +} + +void INFO(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + _log(stdout, "[" TERM_COLOR_GREEN "INFO" TERM_COLOR_RESET "] ", fmt, ap); + va_end(ap); + puts(""); +} + +void __DEBUG(const char* file, const i32 line, const char* func, const char *fmt, ...) { + va_list ap; + + const usize prefix_len = 1024; + + char *prefix = malloc(sizeof(char) * 1024); + + snprintf(prefix, + prefix_len, + "["TERM_COLOR_YELLOW"DEBUG"TERM_COLOR_RESET"] " + "%s:%d <%s> ", file, line, func); + + va_start(ap, fmt); + _log(stdout, prefix, fmt, ap); + va_end(ap); + + free(prefix); +} + +void WARN(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + _log(stderr, "[" TERM_COLOR_PURPLE "WARN" TERM_COLOR_RESET "] ", fmt, ap); + va_end(ap); + puts(""); +} + +void ERROR(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + _log(stderr, "[" TERM_COLOR_RED "ERROR" TERM_COLOR_RESET "] ", fmt, ap); + va_end(ap); + puts(""); +} diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..6ef63ce --- /dev/null +++ b/src/memory.c @@ -0,0 +1,62 @@ +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <engine/logging.h> + +#include <engine/memory.h> + +memory *memory_new(usize max_size) { + memory *m = malloc(sizeof(memory)); + m->data = malloc(max_size); + m->size = max_size; + m->pos = 0; + m->free = max_size; + + memset(m->data, 0, max_size); + + return m; +} + +/* Returns a pointer to the allocated data */ +void *memory_allocate(memory* mem, usize size) { + void* data = NULL; + + if (mem->pos + size <= mem->size) { + data = (void*)((usize)mem->data + mem->pos); + mem->pos += size; + mem->free -= size; + } else { + ERROR("Trying to allocate %lu in a %lu sized memory block", size, mem->size); + ERROR("No more room!"); + exit(EXIT_FAILURE); + } + + return data; +} + +memory memory_init(void *data, usize size) { + memory m = {0}; + m.data = data; + m.size = size; + m.free = 0; + return m; +} + +void memory_free(memory *mem, usize size) { + if (size > mem->pos) { + perror("Freeing too much memory!"); + exit(EXIT_FAILURE); + } else { + mem->pos -= size; + mem->free += size; + } +} + +void memory_clear(memory *mem) { + mem->pos = 0; + mem->free = mem->size; + /* Reset the memory? */ + memset(mem->data, 0, mem->size); +} diff --git a/src/rendering.c b/src/rendering.c new file mode 100644 index 0000000..755eda0 --- /dev/null +++ b/src/rendering.c @@ -0,0 +1,152 @@ +#include <stdio.h> +#include <string.h> + +#include <SDL2/SDL.h> +#include <SDL2/SDL_image.h> +#include <SDL2/SDL_ttf.h> + +#define ENGINE_INTERNALS +#include <engine/engine.h> +#include <engine/rendering.h> + +/* Extern globals */ +extern Platform *GLOBAL_PLATFORM; + +/* Globals */ +#define drawcall_limit (64 * 1024) +RenderDrawCall drawcalls[drawcall_limit]; +i32 drawcall_len = 0; + +/* Implementations */ + +/* Clear the screen, + * To be used inbetween draw calls */ +void render_begin(Window *w) { +#ifdef DEBUG + SDL_SetRenderDrawColor(w->renderer, 0x10, 0x0a, 0x33, 0x00); +#else + SDL_SetRenderDrawColor(w->renderer, 0x00, 0x00, 0x00, 0x00); +#endif + SDL_RenderClear(w->renderer); +} + +void render_present(Window *w) { + for (i32 i = 0; i < drawcall_len; i++) { + RenderDrawCall dc = drawcalls[i]; + switch (dc.type) { + case RenderDrawCallType_UITree: + render_uitree(w, dc.data.data); + break; + case RenderDrawCallType_Text: + LOG("RenderDrawCallType_Text rendering not implemented!"); + break; + case RenderDrawCallType_Sprite: + { +#ifdef DEBUG + if (dc.data.sprite.sprite == NULL) { + __asm__("int3;"); + WARN("Sprite %lx in drawcall %d/%d had NULL reference", dc.data.sprite.sprite, i, drawcall_len); + + drawcall_len = 0; + SDL_RenderPresent(w->renderer); + exit(EXIT_FAILURE); + } +#endif + Sprite s = *dc.data.sprite.sprite; + Texture *t = ((struct Resources*)GLOBAL_PLATFORM->data)->textures[s.texture_id]; + i32 ts = t->tilesize; + SDL_Rect src = { + s.coord.x, + s.coord.y, + ts,ts + }; + SDL_Rect dst = { + dc.data.sprite.x, + dc.data.sprite.y, + ts * dc.data.sprite.scale, + ts * dc.data.sprite.scale, + }; + SDL_SetTextureColorMod(t->texture, + dc.data.sprite.mod.r, + dc.data.sprite.mod.g, + dc.data.sprite.mod.b); + SDL_RenderCopy( + w->renderer, + t->texture, + &src, &dst); + } + break; + default: + break; + } + } + + drawcall_len = 0; + + SDL_RenderPresent(w->renderer); +} + +void drawcall_reset(void) { + drawcall_len = 0; +} + +void engine_window_resize_pointers(i32* w, i32* h) { + GLOBAL_PLATFORM->window->game_w = w; + GLOBAL_PLATFORM->window->game_h = h; +} + +void engine_window_resize_pointers_reset(void) { + GLOBAL_PLATFORM->window->game_w = NULL; + GLOBAL_PLATFORM->window->game_h = NULL; +} + +void engine_draw_uitree(UITree *t) { + if (drawcall_len + 1 >= drawcall_limit) return; + drawcalls[drawcall_len++] = (RenderDrawCall){ + .type = RenderDrawCallType_UITree, + .data.data = (void*)t + }; +} + +void engine_draw_sprite(Sprite *s, v2_i32 *pos, f32 scale) { + if (drawcall_len + 1 >= drawcall_limit) return; +#ifdef DEBUG + if (s == NULL) __asm__("int3;"); +#endif + drawcalls[drawcall_len++] = (RenderDrawCall){ + .type = RenderDrawCallType_Sprite, + .data.sprite = { + .sprite = s, + .x = pos->x, + .y = pos->y, + .scale = scale, + .mod = {0xFF, 0xFF, 0xFF, 0xFF}, + } + }; +} + +void engine_draw_sprite_ex(Sprite *s, v2_i32 *pos, f32 scale, Engine_color colormod) { + if (drawcall_len + 1 >= drawcall_limit) return; +#ifdef DEBUG + if (s == NULL) __asm__("int3;"); +#endif + drawcalls[drawcall_len++] = (RenderDrawCall){ + .type = RenderDrawCallType_Sprite, + .data.sprite = { + .sprite = s, + .x = pos->x, + .y = pos->y, + .scale = scale, + .mod = {colormod.r, colormod.g, colormod.b, colormod.a}, + } + }; +} + + +Sprite sprite_new(u64 tid, u8 coord) { + const i32 ts = ((struct Resources*)GLOBAL_PLATFORM->data)->textures[tid]->tilesize; + return (Sprite){.texture_id = tid, (v2_i32){ + .x = ts * (coord & 0x0F), + .y = ts * ((coord & 0xF0) >> 4), + }}; +} diff --git a/src/stack.c b/src/stack.c new file mode 100644 index 0000000..edc2d9f --- /dev/null +++ b/src/stack.c @@ -0,0 +1,88 @@ +#include <stdlib.h> +#include <engine/logging.h> +#include <engine/stack.h> + + +Stack stack_new_ex(const usize element_size, const usize size) { + Stack s = { + .head = 0, + .elem_size = element_size, + .size = element_size * size, + .chunk_size = element_size * size, + .data = NULL, + }; + + s.data = (void*)calloc(element_size, size); + return s; +} + + +Stack stack_new(const usize element_size) { + return stack_new_ex(element_size, 512); +} + + +void stack_free(Stack *s) { + if (s->data == NULL) return; + free(s->data); + s->data = NULL; +} + + +void *stack_pop(Stack *s) { + if (s->head == 0) return NULL; /* Empty stack */ + return (u8*)s->data + (--(s->head) * s->elem_size); +} + + +void stack_push(Stack *s, void *elem) { + if (elem == NULL) {WARN("%s received a nullptr", __func__); return;} + if (s->head > 0 && s->head * s->elem_size >= s->size) { + WARN("Allocating more stack memory"); + /* Reallocate more memory and update size */ + void* ptr = realloc(s->data, s->size + s->chunk_size); + if (ptr == NULL) { + ERROR("Failed to resize memory for stack"); + exit(EXIT_FAILURE); + } + s->data = ptr; + //memset((void*)((u64)s->data + (s->size - s->elem_size)), 0, s->chunk_size); + s->size += s->chunk_size; + } + memcpy((u8*)s->data + s->head * s->elem_size, elem, s->elem_size); + s->head++; +} + + +void *stack_peek(Stack *s) { + if (s->head <= 0) return NULL; /* Empty stack */ + return (u8*)s->data + ((s->head - 1) * s->elem_size); +} + + +isize stack_size(const Stack *s) { + return s->head; +} + + +void stack_swap(Stack *s, Stack *t) { + if (s->size > t->size) { + t->data = realloc(t->data, s->size); + } else if (t->size > s->size) { + s->data = realloc(s->data, t->size); + } + void* tmp = malloc(s->size); + if (tmp == NULL) { + ERROR("Failed to allocate memory for stack swapping!"); + exit(EXIT_FAILURE); + } + isize shead = s->head; + + memcpy(tmp, s->data, s->size); + memcpy(s->data, t->data, t->size); + memcpy(t->data, tmp, s->size); + + s->head = t->head; + t->head = shead; + free(tmp); +} diff --git a/src/state.c b/src/state.c new file mode 100644 index 0000000..6b1f9c8 --- /dev/null +++ b/src/state.c @@ -0,0 +1,91 @@ +#include <engine/state.h> + +#include <include_states.h> + +typedef StateType state_update_t (void*); + +const char *StateTypeStr[] = { + "null", +#define State(name) #name, +#include <state_type_list.h> +#undef State + "quit", +}; + +void State_init(StateType type, memory *mem) { + switch (type) { +#define State(name) \ + case (STATE_##name): { \ + name##_init(memory_allocate(mem, sizeof(name##_state))); \ + break; \ + } +#include <state_type_list.h> +#undef State + case STATE_null: + case STATE_quit: + _DEBUG("Got %s state.\n", StateTypeStr[type]); + break; + default: + exit(EXIT_FAILURE); + } +} + + +void State_free(StateType type, memory *mem) { + switch (type) { +#define State(name) \ + case (STATE_##name): { \ + name##_free(mem->data); \ + break; \ + } +#include <state_type_list.h> +#undef State + case STATE_null: + case STATE_quit: + _DEBUG("Got %s state.\n", StateTypeStr[type]); + break; + default: + exit(EXIT_FAILURE); + } + memory_clear(mem); +} + + +StateType (*State_updateFunc(StateType type))(void*) { + switch (type) { +#define State(name) \ + case (STATE_##name): { \ + return (state_update_t*)&name##_update; \ + break; \ + } +#include <state_type_list.h> +#undef State + case STATE_null: + case STATE_quit: + return NULL; //_DEBUG("Got %s state.\n", StateTypeStr[type]); + break; + default: + exit(EXIT_FAILURE); + } + return NULL; +} + +StateType State_update(StateType type, memory *mem) { + StateType next_state = STATE_null; + switch (type) { +#define State(name) \ + case (STATE_##name): { \ + next_state = name##_update(mem->data); \ + break; \ + } +#include <state_type_list.h> +#undef State + case STATE_null: + case STATE_quit: + _DEBUG("Got %s state.\n", StateTypeStr[type]); + break; + default: + exit(EXIT_FAILURE); + } + return next_state; +} diff --git a/src/ui_positioning.c b/src/ui_positioning.c new file mode 100644 index 0000000..fc8ab0a --- /dev/null +++ b/src/ui_positioning.c @@ -0,0 +1,877 @@ +#include <SDL2/SDL.h> +#include <SDL2/SDL_image.h> +#include <SDL2/SDL_ttf.h> + +#include <stdint.h> +#define ENGINE_INTERNALS + +#include <engine/engine.h> +#include <engine/btree.h> + +static Engine_color DEFAULT_FG = {0xFF, 0xFF, 0xFF, 0xFF}; +static Engine_color DEFAULT_BG = {0x00, 0x00, 0x00, 0xFF}; +static i32 DEFAULT_PADDING = 8; +static i32 DEFAULT_MARGIN = 0; + +/* We want to keep track of the roots of the UI elements, s.t. we can resize and + * reposition the elements if needed be. */ +struct btree *GLOBAL_UIROOTS = NULL; + +extern Platform *GLOBAL_PLATFORM; + +const char *uitype_str[] = { + [uitype_container] = "uitype_container", + [uitype_button] = "uitype_button", + [uitype_title] = "uitype_title", + [uitype_text] = "uitype_text", + [uitype_MAX] = "uitype_MAX", + NULL, +}; + +const char *constraint_str[] = { + [ui_constraint_width] = "ui_constraint_width", + [ui_constraint_height] = "ui_constraint_height", + + [ui_constraint_horizontal_align] = "ui_constraint_horizontal_align", + [ui_constraint_vertical_align] = "ui_constraint_vertical_align", + + [ui_constraint_left] = "ui_constraint_left", + [ui_constraint_right] = "ui_constraint_right", + [ui_constraint_top] = "ui_constraint_top", + [ui_constraint_bottom] = "ui_constraint_bottom", +}; + +static i32 pointer_cmp(const void *a, const void *b) { + const u64 *x = a; + const u64 *y = b; + /* We just use the address as index */ + return *x - *y; +} + +static void pointer_print(const void *a) { + const u64 *t = a; + printf("%lx\n", *t); +} + +void ui_rearrange(UITree *root, v2_i32 ppos, v2_i32 psize); +i32 get_padding(UITree *t); +i32 get_margin(UITree *t); +v2_i32 get_pos(UITree *t); +v2_i32 get_size(UITree *t); + +f32 ui_size_pixel_to_percent_width(i32 pixels) { + return (f32)pixels / (f32)GLOBAL_PLATFORM->window->windowsize.x; +} + +f32 ui_size_pixel_to_percent_height(i32 pixels) { + return (f32)pixels / (f32)GLOBAL_PLATFORM->window->windowsize.y; +} + +f32 ui_size_pixel_to_percent(i32 pixels) { return ui_size_percent_height_to_pixel(pixels); } + +i32 ui_size_percent_width_to_pixel(f32 percent) { + return (i32)(percent * GLOBAL_PLATFORM->window->windowsize.x); +} + +i32 ui_size_percent_height_to_pixel(f32 percent) { + return (i32)(percent * GLOBAL_PLATFORM->window->windowsize.y); +} + +i32 ui_size_percent_to_pixel(f32 percent) {return ui_size_percent_height_to_pixel(percent); } + +i32 ui_size_to_pixel_ex(ui_size s, bool vertical, v2_i32 psize) { + if (s.type == ui_size_pixel) { + return s.pixel.value; + } else { + psize.x = MAX(1, psize.x); + psize.y = MAX(1, psize.y); + if (vertical) { + return psize.y * s.percent.value; + } + return psize.x * s.percent.value; + } +} + +i32 ui_size_to_pixel(ui_size s) { + if (s.type == ui_size_pixel) { + return s.pixel.value; + } else { + return ui_size_percent_height_to_pixel(s.percent.value); + } +} + +void ui_add(UITree *t) { + + if (GLOBAL_UIROOTS == NULL) { + GLOBAL_UIROOTS = btree_new(sizeof(void*), 16, &pointer_cmp); + } + + u64 tmp = (u64)t; + btree_insert(GLOBAL_UIROOTS, &tmp); +} + +void clear_ui(void) { + UITree *t = NULL; + while((t = btree_first(GLOBAL_UIROOTS)) != NULL) { + uitree_free(t); + if (!btree_delete(GLOBAL_UIROOTS, t)) { + WARN("Failed deletion"); + UITree *elem = (UITree*)t; + WARN("Failed to remove %s from global ui-table", uitype_str[elem->type]); + } + } +} + + +/* Unwraps the size to a v2_i32 */ +v2_i32 uitree_get_size(UITree *t) { + if (t == NULL) return (v2_i32){.x=-1, .y=-1}; + switch (t->type) { + case uitype_container: return (v2_i32){.x=t->container.w, .y=t->container.h}; + case uitype_button: return (v2_i32){.x=t->button.w, .y=t->button.h}; + case uitype_title: return (v2_i32){.x=t->title.w, .y=t->title.h}; + case uitype_text: return (v2_i32){.x=t->text.w, .y=t->text.h}; + default: return (v2_i32){.x=-1, .y=-1}; + } +} + + +/* elem_size also takes into account the padding and other stuffs */ +v2_i32 elem_size(UITree *root) { + if (root == NULL) return (v2_i32){0,0}; + + const i32 total_padding = 2 * get_padding(root); + return v2_i32_add_i(uitree_get_size(root), total_padding); +} + +v2_i32 elem_size_spacing(UITree *root) { + if (root == NULL) return (v2_i32){0,0}; + + const i32 total_margin = 2 * get_padding(root) + 2 * get_margin(root); + return v2_i32_add_i(uitree_get_size(root), total_margin); +} + +UITree *ui_container_new_ex( + Engine_color fg, + Engine_color bg, + Engine_color border, + bool direction, + struct List_ui_constraint *constraints) { + + UITree *t = malloc(sizeof(UITree)); + + t->container.type = uitype_container; + t->container.visible = true; + t->container.direction = direction; + + /* -1 == unsolved constraint */ + t->container.x = -1; + t->container.y = -1; + t->container.w = -1; + t->container.h = -1; + t->container.padding = 0; + t->container.margin = 0; + + t->container.fg = fg; + t->container.bg = bg; + t->container.bordercolor = border; + + + t->container.children = NULL; + t->container.children_len = 0; + t->container.children_size = 0; + + t->container.constraints = constraints; + + ui_rearrange(t, (v2_i32){0,0}, (v2_i32){0,0}); + + ui_add(t); + + return t; +} + +UITree *ui_container(bool direction, struct List_ui_constraint *constraints) { + return ui_container_new_ex( + /* We set border color to fg by default */ + DEFAULT_FG, DEFAULT_BG, DEFAULT_FG, direction, + constraints); +} + +UITree *ui_title(i32 font_id, const char *text, struct List_ui_constraint *constraints) { + UITree_title *t = malloc(sizeof(UITree)); + t->type = uitype_title; + + t->x = -1; + t->y = -1; + + t->padding = DEFAULT_PADDING; + t->margin = DEFAULT_MARGIN; + + t->fg = DEFAULT_FG; + + t->font_id = font_id; + t->text_original = text; + t->text_texture_index = + engine_render_text(font_id, t->fg, (char*)text, &t->texture_size, false); + + t->w = t->texture_size.x; + t->h = t->texture_size.y; + + t->constraints = constraints; + + ui_rearrange((UITree*)t, (v2_i32){0,0}, (v2_i32){0,0}); + + ui_add((UITree*)t); + + return (UITree*)t; +} + +UITree *ui_text(i32 font_id, const char *text, struct List_ui_constraint *constraints) { + UITree *t = malloc(sizeof(UITree)); + t->text.type = uitype_text; + + t->text.x = -1; + t->text.y = -1; + + t->text.padding = 0; + t->text.margin = DEFAULT_MARGIN / 2; + + t->text.fg = DEFAULT_FG; + + t->text.font_id = font_id; + t->text.text_original = text; + + /* Postpone rendering until we have a clear set of size-constraints */ + + //t->text.text_texture_index = + // engine_render_text(font_id, DEFAULT_FG, (char*)text, &t->text.texture_size, true); + + //t->text.w = t->text.texture_size.x; + //t->text.h = t->text.texture_size.y; + + t->text.constraints = constraints; + + ui_rearrange(t, (v2_i32){0,0}, (v2_i32){0,0}); + + ui_add(t); + + return t; +} + +UITree *ui_button(u64 id, i32 font_id, char *text, struct List_ui_constraint *constraints) { + UITree_button *t = malloc(sizeof(UITree)); + t->type = uitype_button; + + t->enabled = true; + t->id = id; + t->x = -1; + t->y = -1; + t->padding = DEFAULT_PADDING; + t->margin = DEFAULT_MARGIN; + + t->fg = DEFAULT_FG; + t->bg = DEFAULT_BG; + + t->font_id = font_id; + t->text_original = text; + t->text_texture_index = + engine_render_text(font_id, t->fg, (char*)text, &t->texture_size, false); + + t->w = t->texture_size.x; + t->h = t->texture_size.y; + + t->constraints = constraints; + + ui_rearrange((UITree*)t, (v2_i32){0,0}, (v2_i32){0,0}); + + ui_add((UITree*)t); + + return (UITree*)t; +} + +void uitree_free(UITree *t) { + switch (t->type) { + case uitype_container: + if (t->container.children != NULL && t->container.children_len > 0) { + for (usize i = 0; i < t->container.children_len; i++) { + uitree_free(t->container.children[i]); + } + free(t->container.children); + } + break; + case uitype_button: + case uitype_title: + case uitype_text: + free(t); + break; + default: break; + } +} + + +struct List_ui_constraint* get_constraints(UITree *t) { + switch (t->type) { + case uitype_container: return t->container.constraints; + case uitype_button: return t->button.constraints; + case uitype_title: return t->title.constraints; + case uitype_text: return t->text.constraints; + default: return NULL; + } +} + + +i32 get_padding(UITree *t) { + switch (t->type) { + case uitype_container: return t->container.padding; + case uitype_button: return t->button.padding; + case uitype_title: return t->title.padding; + case uitype_text: return t->text.padding; + default: return 0; + } +} + + +i32 get_margin(UITree *t) { + switch (t->type) { + case uitype_container: return t->container.margin; + case uitype_button: return t->button.margin; + case uitype_title: return t->title.margin; + case uitype_text: return t->text.margin; + default: return 0; + } +} + +v2_i32 get_pos(UITree *t) { + switch (t->type) { + case uitype_container: return (v2_i32){t->container.x, t->container.y}; + case uitype_button: return (v2_i32){t->button.x, t->button.y}; + case uitype_title: return (v2_i32){t->title.x, t->title.y}; + case uitype_text: return (v2_i32){t->text.x, t->text.y}; + default: return (v2_i32){0, 0}; + } +} + +v2_i32 get_size(UITree *t) { + switch (t->type) { + case uitype_container: return (v2_i32){t->container.w, t->container.h}; + case uitype_button: return (v2_i32){t->button.w, t->button.h}; + case uitype_title: return (v2_i32){t->title.w, t->title.h}; + case uitype_text: return (v2_i32){t->text.w, t->text.h}; + default: return (v2_i32){0, 0}; + } +} + +#define SETVAL(VAL, tt) { if ((VAL) > 0) t->tt = VAL; } +void uitree_set_pos(UITree *t, v2_i32 newpos) { + switch (t->type) { + case uitype_container: SETVAL( newpos.x, container.x); SETVAL( newpos.y, container.y); break; + case uitype_button: SETVAL( newpos.x, button.x ); SETVAL( newpos.y, button.y ); break; + case uitype_title: SETVAL( newpos.x, title.x ); SETVAL( newpos.y, title.y ); break; + case uitype_text: SETVAL( newpos.x, text.x ); SETVAL( newpos.y, text.y ); break; + default: return; + } +} + + +void uitree_set_size(UITree *t, v2_i32 size) { + switch (t->type) { + case uitype_container: SETVAL( size.x, container.w); SETVAL( size.y, container.h); break; + case uitype_button: SETVAL( size.x, button.w ); SETVAL( size.y, button.h ); break; + case uitype_title: SETVAL( size.x, title.w ); SETVAL( size.y, title.h ); break; + case uitype_text: SETVAL( size.x, text.w ); SETVAL( size.y, text.h ); break; + default: return; + } +} + + +bool uitree_has_constraint(UITree *t, const ui_constraint_t constraint) { + struct List_ui_constraint* c = NULL; + struct List_ui_constraint* first_constraint; + if (t == NULL) {ERROR("%s got nullptr argument!", __func__); exit(EXIT_FAILURE);} + + switch (t->type) { + case uitype_container: c = t->container.constraints; break; + case uitype_button: c = t->button.constraints; break; + case uitype_title: c = t->title.constraints; break; + case uitype_text: c = t->text.constraints; break; + default: break; + } + + first_constraint = c; + + while (c != NULL) { + if ((void*)c->next == (void*)first_constraint) { + ERROR("Infinite constraint loop detected!"); + exit(EXIT_FAILURE); + } + if (constraint == c->value.type) return true; + + c = c->next; + } + return false; +} + + +void ui_constraints_apply(UITree *t, v2_i32 ppos, v2_i32 psize) { + struct List_ui_constraint* constraints = get_constraints(t); + struct List_ui_constraint* first_constraint = constraints; + v2_i32 tsize = uitree_get_size(t); + + if (constraints == NULL) return; + + while (constraints != NULL) { + if ((void*)constraints->next == (void*)first_constraint) { + ERROR("Infinite constraint loop detected!"); + ERROR(" Type: %s", constraint_str[constraints->value.type]); + exit(EXIT_FAILURE); + } + + const constraintval_t con = constraints->value.constraint; + + switch(constraints->value.type) { + case ui_constraint_width: + { + i32 ww = ui_size_to_pixel_ex(con.width.size, false, psize); + if (con.width.size.type == ui_size_percent) ww -= 2 * get_padding(t); + /* FIXME: Resizing the window makes some elements retain their + * size on window resize. + * This is fixed by replacing MAX(...) with just `ww`, + * This in turn makes elements that are supposed to have some + * relative width not take up the full width. */ + uitree_set_size(t, (v2_i32){.x=MAX(tsize.x, ww), .y=0}); + tsize = uitree_get_size(t); + } + break; + + case ui_constraint_height: + { + i32 ww = ui_size_to_pixel_ex(con.height.size, true, psize); + if (con.height.size.type == ui_size_percent) ww -= 2 * get_padding(t); + /* FIXME: Same as above */ + uitree_set_size(t, (v2_i32){.y=MAX(tsize.y, ww), .x=0}); + tsize = uitree_get_size(t); + } + break; + + + case ui_constraint_horizontal_align: + { + const i32 size = ui_size_to_pixel_ex(con.horizontal_align.spacing, false, psize); + i32 new_x = ppos.x; + switch (con.horizontal_align.align) { + case ui_constraint_horizontal_align_left: + new_x += size + get_margin(t); + break; + case ui_constraint_horizontal_align_center: + new_x += (psize.x / 2) - (tsize.x / 2); + break; + case ui_constraint_horizontal_align_right: + new_x += psize.x - size - tsize.x - get_margin(t); + break; + } + uitree_set_pos(t, (v2_i32){.x = new_x, .y = 0}); + } + break; + + case ui_constraint_vertical_align: + { + i32 size = ui_size_to_pixel_ex(con.horizontal_align.spacing, true, psize); + i32 new_y = ppos.y; + switch (con.vertical_align.align) { + case ui_constraint_vertical_align_top: + new_y += size + get_margin(t); + break; + case ui_constraint_vertical_align_center: + new_y += ((psize.y) / 2) - (tsize.y / 2); + break; + case ui_constraint_vertical_align_bottom: + new_y += psize.y - size - tsize.y - get_margin(t); + break; + } + uitree_set_pos(t, (v2_i32){.x = 0, .y = new_y}); + } + break; + + case ui_constraint_left: + uitree_set_pos(t, (v2_i32){ + .x = ppos.x + ui_size_to_pixel_ex(con.left.pos, false, psize), + .y=0 + }); + break; + + case ui_constraint_top: + uitree_set_pos(t, (v2_i32){ + .y = ppos.y + ui_size_to_pixel_ex(con.top.pos, true, psize), + .x=0 + }); + break; + + case ui_constraint_right: + if (tsize.x > 0) + uitree_set_pos(t, (v2_i32){ + .x = ppos.x + psize.x - ui_size_to_pixel_ex(con.right.pos, false, psize) - tsize.x, + .y=0 + }); + break; + + case ui_constraint_bottom: + if (tsize.y > 0) + uitree_set_pos(t, (v2_i32){ + .y = ppos.y + psize.y - ui_size_to_pixel_ex(con.bottom.pos, true, psize) - tsize.y, + .x = 0 + }); + break; + + default: + ERROR("Unknown constraint: %d", constraints->value.type); + exit(EXIT_FAILURE); + break; + } + + constraints = constraints->next; + } +} + +void ui_rearrange_container(UITree_container *c, v2_i32 ppos, v2_i32 psize) { + struct List_ui_constraint* constraints = c->constraints; + struct List_ui_constraint* first_constraint = constraints; + bool fitwidth = !uitree_has_constraint((UITree*)c, ui_constraint_width); + bool fitheight = !uitree_has_constraint((UITree*)c, ui_constraint_height); + + if (fitwidth) c->w = 0; + if (fitheight) c->h = 0; + + ui_constraints_apply((UITree*)c, ppos, psize); + /* Apply constraints to children here? */ + { + const v2_i32 pos = v2_i32_sub_i((v2_i32){.x=c->x, .y=c->y}, 0);//c->margin); + const v2_i32 size = v2_i32_sub_i((v2_i32){.x=c->w, .y=c->h}, (2+MAX(c->children_len-1, 0)) * c->padding); + for (usize cc = 0; cc < c->children_len; cc++) { + ui_rearrange(c->children[cc], pos, size); + } + } + + /* TODO: We want to update children regardless of whether we are doing a fit + * on the size of children. Switch the conditions (fitwidth||fitheight) and + * (children_len > 0); the size-fit doesn't matter if there's no children, + * the childrens size only matters if we do a size-fit. */ + + /* If we do not have a size > -1 we need to adapt the size to all children + * and add a size constraint on the largest of one of them */ + if (fitwidth || fitheight) { + + i32 maxwidth = 2 * c->padding; + i32 maxheight = maxwidth; + + if (c->children_len > 0) { + if (c->children == NULL) { + ERROR("Container has children_len(%d) > 0 but children pointer is NULL!", c->children_len); + exit(EXIT_FAILURE); + } + + /* Calculate new posisitions and save the largest one */ + v2_i32 startpos = (v2_i32){.x=c->x + c->margin, .y=c->y + c->margin}; + v2_i32 startsize = (v2_i32){.x=c->w - (2 * c->padding), .y=c->h - (2 * c->padding)}; + + for (usize cc = 0; cc < c->children_len; cc++) { + const v2_i32 childsize = elem_size(c->children[cc]); + i32 margin = (cc + 1 == c->children_len) ? 0 : MAX(c->padding, get_margin(c->children[cc])); + + /* We just sum up in the direction we are going */ + if (c->direction == DIRECTION_HORIZONTAL) { + maxwidth += childsize.x + margin; + maxheight = MAX(childsize.y + 2 * c->padding, maxheight); + + startpos.x += childsize.x + margin; + startsize.x -= childsize.x + margin; + } else { + maxwidth = MAX(childsize.x + 2 * c->padding, maxwidth); + maxheight += childsize.y + margin; + + startpos.y += childsize.y + margin; + startsize.y -= childsize.y + margin; + } + } + + struct List_ui_constraint* constraint_head = c->constraints; + struct List_ui_constraint constraint_width; + struct List_ui_constraint constraint_height; + + if (fitwidth) { + /* Create new size constraint */ + constraint_width = (struct List_ui_constraint){ + .value = { + .type = ui_constraint_width, + .constraint.width = { + .size = ui_size_pixel_new(maxwidth), + }, + }, + .next = constraint_head, + }; + + /* Prepend it to the list of constraints */ + c->constraints = &constraint_width; + constraint_head = c->constraints; + } + if (fitheight) { + constraint_height = (struct List_ui_constraint){ + .value = { + .type = ui_constraint_height, + .constraint.height = { + .size = ui_size_pixel_new(maxheight), + }, + }, + .next = constraint_head, + }; + /* Prepend it to the list of constraints */ + c->constraints = &constraint_height; + //constraint_head = c->constraints; + /* TODO: Convert to scrollable container if the height is + * greater than some constant relative to the screen height */ + } + + /* Recurse with new constraints */ + ui_rearrange_container(c, ppos, psize); + + /* Replace the original set of constraints, as the new ones will be + * out of scope once we return from the current call frame */ + c->constraints = first_constraint; + + /* Update child elements */ + /* This is 1:1 (almost) to the previous iteration of children */ + startpos = (v2_i32){c->x + c->padding, c->y + c->padding}; + startsize = (v2_i32){c->w - 2 * c->padding, c->h - 2 * c->padding}; + + for (usize cc = 0; cc < c->children_len; cc++) { + v2_i32 childsize = elem_size(c->children[cc]); + i32 margin = MAX(c->padding, get_margin(c->children[cc])); + + v2_i32 startsize_2; + if (c->direction == DIRECTION_HORIZONTAL) { + startsize_2 = (v2_i32){childsize.x, startsize.y}; + } else { + startsize_2 = (v2_i32){startsize.x, childsize.y}; + } + ui_rearrange(c->children[cc], startpos, startsize_2); + + if (c->direction == DIRECTION_HORIZONTAL) { + maxwidth += childsize.x + margin; + maxheight = MAX(childsize.y, maxheight); + + startpos.x += childsize.x + margin; + startsize.x -= childsize.x + margin; + + } else { + maxwidth = MAX(childsize.x + 2 * c->padding, maxwidth); + maxheight += childsize.y + margin; + + startpos.y += childsize.y + margin; + startsize.y -= childsize.y + margin; + } + } + } + } +} + +void ui_rearrange_button(UITree_button *t, v2_i32 ppos, v2_i32 psize) { + t->x = ppos.x; + t->y = ppos.y; + + ui_constraints_apply((UITree*)t, ppos, psize); +} + +void ui_rearrange_title(UITree_title *t, v2_i32 ppos, v2_i32 psize) { + /*Defaults to just sit in the top-left corner of the parent*/ + t->x = ppos.x; + t->y = ppos.y; + + ui_constraints_apply((UITree*)t, ppos, psize); +} + +void ui_rearrange_text(UITree_text *t, v2_i32 ppos, v2_i32 psize) { + t->x = ppos.x; + t->y = ppos.y; + t->w = psize.x; + t->h = psize.y; + + t->texture_size.x = psize.x; + t->texture_size.y = psize.y; + + /* Free the old texture */ + /* Re-render the text */ + t->text_texture_index = + engine_render_text(t->font_id, t->fg, (char*)t->text_original, &t->texture_size, true); + + t->w = t->texture_size.x; + t->h = t->texture_size.y; + + ui_constraints_apply((UITree*)t, ppos, psize); +} + + +void ui_rearrange(UITree *root, v2_i32 ppos, v2_i32 psize) { + if (root == NULL) { + WARN("%s received a null pointer, ignoring", __func__); + return; + } + + switch (root->type) { + case uitype_container: + ui_rearrange_container(&root->container, ppos, psize); + break; + case uitype_button: + ui_rearrange_button(&root->button, ppos, psize); + break; + case uitype_title: + ui_rearrange_title(&root->title, ppos, psize); + break; + case uitype_text: + ui_rearrange_text(&root->text, ppos, psize); + break; + default: + if (root->type >= uitype_MAX) { + ERROR("Unknown uitype: %d", root->type); + exit(EXIT_FAILURE); + } else { + WARN("Rendering not implemented for %s", uitype_str[root->type]); + } + break; + } +} + +void ui_resolve_constraints(void) { + u64 *i = NULL; + struct btree_iter_t *it = NULL; + + /*absolutely bonkers way to do this*/ + if (GLOBAL_UIROOTS == NULL) { + WARN("UIROOTS not initialized"); + } + + it = btree_iter_t_new(GLOBAL_UIROOTS); + + while ((i = btree_iter(GLOBAL_UIROOTS, it)) != NULL) { + ui_rearrange((UITree*)*i, (v2_i32){0,0}, GLOBAL_PLATFORM->window->windowsize); + } + free(it); +} + +void ui_container_attach(UITree *root, UITree *child) { + if (root == NULL) { + WARN("%s received a null pointer", __func__); + return; + } + if (root->type != uitype_container) { + WARN("Trying to attach a child to a non-container UI element"); + return; + } + + UITree_container *c = &(root->container); + + static const usize size_increment = 32; + static const usize uitree_sz = sizeof(UITree*); + + if (c->children == NULL) { + /* Allocate space for children */ + c->children = malloc(uitree_sz * size_increment); + + } else if ((c->children_len + 1) * uitree_sz >= c->children_size) { + /* If there's not enough room for more children we will need to + * reallocate some more memory */ + c->children = realloc(c->children, (c->children_len + size_increment) * uitree_sz); + c->children_size += size_increment * uitree_sz; + } + + /* Finally: attach the new element */ + c->children[c->children_len++] = child; + + /* Since the child is no longer a part of the root-ui we can delete it from + * the btree-mapping */ + { + u64 tmp = (u64)child; + if (!btree_delete(GLOBAL_UIROOTS, &tmp)) { + WARN("Could not find child %p in global lookup table", child); + btree_print(GLOBAL_UIROOTS, &pointer_print); + } + } +} + +void ui_button_enable(UITree_button *b) { + b->enabled = true; + SDL_Texture *t = ((struct Resources*)GLOBAL_PLATFORM->data)->textures[b->text_texture_index]->texture; + SDL_SetTextureColorMod(t, 0xff, 0xff, 0xff); +} + +void ui_button_disable(UITree_button *b) { + b->enabled = false; + + SDL_Texture *t = ((struct Resources*)GLOBAL_PLATFORM->data)->textures[b->text_texture_index]->texture; + SDL_SetTextureColorMod(t, 0x7f, 0x7f, 0x7f); +} + +void ui_set_default_colors(Engine_color fg, Engine_color bg) { + DEFAULT_FG = fg; + DEFAULT_BG = bg; +} + +void ui_set_default_padding(i32 padding) { DEFAULT_PADDING = padding; } + +Engine_color ui_get_default_fg(void) { return DEFAULT_FG; } +Engine_color ui_get_default_bg(void) { return DEFAULT_BG; } + +bool check_point_in_rect(const v2_i32 *p, const SDL_Rect *r) { + if (p->x > r->x && p->x < r->x + r->w + && p->y > r->y && p->y < r->y + r->h) return true; + return false; +} + +u64 ui_check_click_internal(UITree *root, v2_i32 pos) { + v2_i32 ps = get_pos(root); + v2_i32 sz = get_size(root); + SDL_Rect elem_rect = {.x=ps.x, .y=ps.y + ,.w=sz.x, .h=sz.y}; + /* */ + switch (root->type) { + case uitype_container: + if (root->container.children != NULL && root->container.children_len > 0) { + const UITree_container *container = &root->container; + + if (check_point_in_rect(&pos, &elem_rect)) { + + for (usize i = 0; i < container->children_len; i++) { + u64 res = ui_check_click_internal(container->children[i], pos); + if (res != (u64)ELEM_NOT_FOUND) { + return res; + } + } + } + } + break; + case uitype_button: + if (root->button.enabled) { + WARN("Checking button"); + elem_rect.h += root->button.padding * 2; + elem_rect.w += root->button.padding * 2; + WARN("Button specs: <%d,%d>+%d,%d", elem_rect.w, elem_rect.h, elem_rect.x, elem_rect.y); + if (check_point_in_rect(&pos, &elem_rect)) { + return root->button.id; + } + } + break; + case uitype_title: + case uitype_text: + default: + break; + } + return ELEM_NOT_FOUND; +} + +u64 ui_check_click(UITree *root) { + if (!GLOBAL_PLATFORM->mouse_lclick) return NO_CLICK; + + v2_i32 pos = GLOBAL_PLATFORM->mouse_pos; + + INFO("Mousepos: <%d,%d>", pos.x, pos.y); + + return ui_check_click_internal(root, pos); +} diff --git a/src/ui_rendering.c b/src/ui_rendering.c new file mode 100644 index 0000000..7024e0b --- /dev/null +++ b/src/ui_rendering.c @@ -0,0 +1,295 @@ +#include <stdio.h> +#include <string.h> +#include <stdbool.h> + +#include <SDL2/SDL.h> +#include <SDL2/SDL_image.h> +#include <SDL2/SDL_ttf.h> + +#define ENGINE_INTERNALS + +#include <engine/engine.h> +#include <engine/rendering.h> + + +extern Platform *GLOBAL_PLATFORM; + +extern const char *uitype_str[]; + +void render_uitree(Window *w, UITree *t) { + switch (t->type) { + case uitype_container: + render_container(w, &t->container); + break; + case uitype_button: + render_button(w, &t->button); + break; + case uitype_title: + render_title(w, &t->title); + break; + case uitype_text: + render_text(w, &t->text); + break; + default: + if (t->type >= uitype_MAX) { + ERROR("Unknown uitype: %d", t->type); + } else { + WARN("Rendering not implemented for %s", uitype_str[t->type]); + } + break; + } +} + +void render_container(Window *w, UITree_container *t) { + SDL_Rect r = { + .x = t->x, + .y = t->y, + .w = t->w, + .h = t->h + }; + + SDL_SetRenderDrawColor(w->renderer, + t->bg.r, + t->bg.g, + t->bg.b, + t->bg.a); + + SDL_RenderFillRect(w->renderer, &r); + + SDL_SetRenderDrawColor(w->renderer, + t->fg.r, + t->fg.g, + t->fg.b, + t->fg.a); + + SDL_RenderDrawRect(w->renderer, &r); + +#ifdef DEBUG + r.x += t->padding; + r.y += t->padding; + r.w -= t->padding * 2; + r.h -= t->padding * 2; + SDL_SetRenderDrawColor(w->renderer, + 0xFF, + 0xFF, + 0xFF, + 0x7A); + SDL_RenderDrawRect(w->renderer, &r); +#endif + + if (t->children != NULL && t->children_len > 0) { + for (usize i = 0; i < t->children_len; i++) { + render_uitree(w, t->children[i]); + } + } +} + +void render_button(Window *w, UITree_button *t) { + + SDL_Rect inner_r = { + .x = t->x + t->padding + (( t->w - t->texture_size.x) / 2), + .y = t->y + t->padding + (( t->h - t->texture_size.y) / 2), + .w = t->texture_size.x, + .h = t->texture_size.y, + }; + + SDL_Rect outer_r = { + .x = t->x, + .y = t->y, + .w = t->w + t->padding * 2, + .h = t->h + t->padding * 2, + }; + + SDL_Color fg = { + t->fg.r, + t->fg.g, + t->fg.b, + t->fg.a, + }; + + if (t->enabled == false) { + fg.r /= 2; + fg.g /= 2; + fg.b /= 2; + } + + SDL_SetRenderDrawColor(w->renderer, + t->bg.r, + t->bg.g, + t->bg.b, + t->bg.a); + + SDL_RenderFillRect(w->renderer, &outer_r); + + SDL_SetRenderDrawColor(w->renderer, + fg.r, + fg.g, + fg.b, + fg.a); + + SDL_RenderDrawRect(w->renderer, &outer_r); + SDL_Texture *texture = ((struct Resources*)GLOBAL_PLATFORM->data) + ->textures[t->text_texture_index]->texture; + SDL_RenderCopy( + w->renderer, + texture, + NULL, + &inner_r); + +} + + +void render_title(Window *w, UITree_title *t) { + SDL_Rect r = { + .x = t->x, + .y = t->y, + .w = t->texture_size.x, + .h = t->texture_size.y, + }; + + SDL_SetRenderDrawColor(w->renderer, + t->fg.r, + t->fg.g, + t->fg.b, + t->fg.a); + + SDL_Texture *texture = ((struct Resources*)GLOBAL_PLATFORM->data) + ->textures[t->text_texture_index]->texture; + + SDL_RenderCopy( + w->renderer, + texture, + NULL, + &r); + +#ifdef DEBUG + SDL_SetRenderDrawColor(w->renderer, + 0xFF, + 0xFF, + 0xFF, + 0x7A); + + SDL_RenderDrawRect(w->renderer, &r); +#endif +} + +void render_text(Window *w, UITree_text *t) { + SDL_Rect r = { + .x = t->x, + .y = t->y, + .w = t->texture_size.x, + .h = t->texture_size.y, + }; + + SDL_SetRenderDrawColor(w->renderer, + t->fg.r, + t->fg.g, + t->fg.b, + t->fg.a); + + SDL_Texture *texture = ((struct Resources*)GLOBAL_PLATFORM->data) + ->textures[t->text_texture_index]->texture; + + SDL_RenderCopy( + w->renderer, + texture, + NULL, + &r); + +#ifdef DEBUG + SDL_SetRenderDrawColor(w->renderer, + 0xFF, + 0xFF, + 0xFF, + 0x7A); + + SDL_RenderDrawRect(w->renderer, &r); +#endif + +} + +i64 add_texture(struct Resources *resptr, Texture *t) { + if (NULL == resptr + || NULL == t) return -1; + + if (resptr->textures == NULL) { + resptr->textures = malloc(sizeof(Texture*) * TEXTURES_INCREMENT); + } else if (resptr->textures_len + 1 >= resptr->textures_size) { + resptr->textures_size += TEXTURES_INCREMENT; + resptr->textures = realloc(resptr->textures, sizeof(Texture*) * resptr->textures_size); + memset(resptr->textures + resptr->textures_size - TEXTURES_INCREMENT, 0, TEXTURES_INCREMENT); + } + + resptr->textures[resptr->textures_len] = t; + return resptr->textures_len++; +} + +i64 engine_render_text(i32 font_id, Engine_color fg, char *text, v2_i32 *size_out, bool wrapped) { + const SDL_Color sdl_fg = {.r = fg.r, .g = fg.g, .b = fg.b, .a = fg.a}; + Texture *t = malloc(sizeof(Texture)); + struct Resources *r; + + if (t == NULL) { + ERROR("Failed to allocate memory for texture!\n"); + exit(EXIT_FAILURE); + } + + if (GLOBAL_PLATFORM == NULL) { + ERROR("Platform is uninitialized.\n"); + exit(EXIT_FAILURE); + } + + r = (struct Resources*)GLOBAL_PLATFORM->data; + + if (r == NULL) { + ERROR("Resources not loaded!\n"); + exit(EXIT_FAILURE); + } + + if (r->fonts == NULL) { + ERROR("No fonts are initialized!\n"); + exit(EXIT_FAILURE); + } + + if ((usize)font_id >= r->fonts_len) { + ERROR("font-id \"%d\" is out of bounds!\n", font_id); + ERROR("Couldn't render text \"%s\"\n", text); + } + + SDL_Surface *s = NULL; + + if (wrapped) { + s = TTF_RenderUTF8_Solid_Wrapped(r->fonts[font_id], text, sdl_fg, size_out->x); + + if (s == NULL) { + ERROR("Failed call to TTF_RenderUTF8_Solid_Wrapped: %s\n", TTF_GetError()); + exit(EXIT_FAILURE); + } + } else { + s = TTF_RenderUTF8_Solid(r->fonts[font_id], text, sdl_fg); + + if (s == NULL) { + ERROR("Failed call to TTF_RenderUTF8_Solid: %s\n", TTF_GetError()); + exit(EXIT_FAILURE); + } + } + + t->texture = SDL_CreateTextureFromSurface(GLOBAL_PLATFORM->window->renderer, s); + + if (t->texture == NULL) { + ERROR("Failed call to SDL_CreateTextureFromSurface: %s\n", SDL_GetError()); + INFO("FontID: %d", font_id); + INFO("Text: \"%s\" [%lu]", text, strlen(text)); + exit(EXIT_FAILURE); + } + + *(i32*)&t->width = s->w; + *(i32*)&t->height = s->h; + + size_out->x = s->w; + size_out->y = s->h; + + SDL_FreeSurface(s); + + return add_texture(r, t); +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..748b82f --- /dev/null +++ b/src/utils.c @@ -0,0 +1,85 @@ +#include <stdlib.h> +#include <stdint.h> + +#include <string.h> + +#include <engine/utils.h> +#include <engine/logging.h> + +f32 lerp(f32 dt, f32 a, f32 b) { + return (a * (1.0f - dt)) + (b * dt); +} + +i32 int_lerp(f32 dt, i32 a, i32 b) { + return ((f32)a * (1.0f - dt)) + ((f32)b * dt); +} + +u32 hash(char *str) { + u32 sum = 0; + while (*str != '\0') { + sum ^= (*str) * 0xdeece66d + 0xb; + str++; + } + return sum; +} + +/* Populates dstmap + * on success: return pointer to dstmap + * on failure: return NULL */ +i32* kernmap(const void *map, i32 *dstmap, const v2_i32 mapsize, bool (*predicate)(const void*)) { + const i32 w = mapsize.x; + const i32 h = mapsize.y; + i32 mask[w*h]; + + if (w * h < 1) return NULL; + + for (i32 i = 0; i < w * h; i++) { + mask[i] = predicate((void*)((u64)map + sizeof(i32) * i)) ? 1 : 0; + } + + for (i32 y = 1; y < h - 1; y++) { + for (i32 x = 1; x < w - 1; x++) { + const i32 global_idx = (y * w) + x; + const i32 offs = global_idx - w - 1; + i32 _sum = 0; + + i32 shift = 0; + + /* We go in the following order */ + /* ....|0|1|2|....*/ + /* ....|3|4|5|....*/ + /* ....|6|7|8|....*/ + /* Where `4` is in the center, MASK_C */ + for (i32 yy = offs; yy <= offs + w + w; yy += w) { + for (i32 xx = yy; xx < yy + 3; xx++) { + _sum = _sum | (mask[xx] << shift++); + } + } + + dstmap[global_idx] = _sum; + } + } + return dstmap; +} + +/* Returns an index from the given weights. */ +i32 pick_from_sample(const i32 *weights, i32 len) { + if (len <= 0) return 0; + + /* Cumulative sum */ + i32 cumweights[len]; + i32 sum = 0; + for (i32 i = 0; i < len; i++) { + sum += weights[i]; + cumweights[i] = sum; + } + + if (sum == 0) return 0; + + i32 pick = rand() % sum; + + for (i32 i = 0; i < len; i++) { + if (pick < cumweights[i]) return i; + } + return -1; +} diff --git a/src/vector.c b/src/vector.c new file mode 100644 index 0000000..1a4cc61 --- /dev/null +++ b/src/vector.c @@ -0,0 +1,28 @@ +#include <engine/vector.h> +#include <engine/utils.h> + +bool v2_i32_eq (const v2_i32 a, + const v2_i32 b) { return (a.x == b.x) && (a.y == b.y); } +v2_i32 v2_i32_add (v2_i32 a, v2_i32 b) { return (v2_i32){a.x + b.x, a.y + b.y}; } +v2_i32 v2_i32_add_i(v2_i32 a, i32 b) { return (v2_i32){a.x + b , a.y + b }; } +v2_i32 v2_i32_sub (v2_i32 a, v2_i32 b) { return (v2_i32){a.x - b.x, a.y - b.y}; } +v2_i32 v2_i32_sub_i(v2_i32 a, i32 b) { return (v2_i32){a.x - b , a.y - b }; } +v2_i32 v2_i32_div (v2_i32 a, v2_i32 b) { return (v2_i32){a.x / b.x, a.y / b.y}; } +v2_i32 v2_i32_div_i(v2_i32 a, i32 b) { return (v2_i32){a.x / b , a.y / b }; } +v2_i32 v2_i32_mul (v2_i32 a, v2_i32 b) { return (v2_i32){a.x * b.x, a.y * b.y}; } +v2_i32 v2_i32_mul_i(v2_i32 a, i32 b) { return (v2_i32){a.x * b , a.y * b }; } +v2_i32 v2_i32_mod (v2_i32 a, v2_i32 b) { return (v2_i32){a.x % b.x, a.y % b.y}; } +v2_i32 v2_i32_mod_i(v2_i32 a, i32 b) { return (v2_i32){a.x % b , a.y % b }; } +v2_i32 v2_i32_max (v2_i32 a, v2_i32 b) { return (v2_i32){MAX(a.x, b.x), MAX(a.y, b.y)}; } +v2_i32 v2_i32_min (v2_i32 a, v2_i32 b) { return (v2_i32){MIN(a.x, b.x), MIN(a.y, b.y)}; } +v2_i32 v2_i32_lerp (f32 dt, v2_i32 a, v2_i32 b) { + return (v2_i32){ + .x = lerp(dt, (f32)a.x, (f32)b.x), + .y = lerp(dt, (f32)a.y, (f32)b.y), + }; +} + + +void v2_i32_fprintf(FILE *stream, v2_i32 a) { + fprintf(stream, "<%d,%d>", a.x, a.y); +} |
